]> git.proxmox.com Git - libgit2.git/blob - src/transports/http.c
Merge pull request #4061 from libgit2/ethomson/merge_opts
[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.type = GIT_PROXY_SPECIFIED;
579 opts.url = url;
580 error = git_stream_set_proxy(t->io, &opts);
581 git__free(url);
582
583 return error;
584 }
585
586 return git_stream_set_proxy(t->io, &t->owner->proxy);
587 }
588
589 static int http_connect(http_subtransport *t)
590 {
591 int error;
592
593 if (t->connected &&
594 http_should_keep_alive(&t->parser) &&
595 t->parse_finished)
596 return 0;
597
598 if (t->io) {
599 git_stream_close(t->io);
600 git_stream_free(t->io);
601 t->io = NULL;
602 t->connected = 0;
603 }
604
605 if (t->connection_data.use_ssl) {
606 error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
607 } else {
608 #ifdef GIT_CURL
609 error = git_curl_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
610 #else
611 error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
612 #endif
613 }
614
615 if (error < 0)
616 return error;
617
618 GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
619
620 apply_proxy_config(t);
621
622 error = git_stream_connect(t->io);
623
624 if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
625 git_stream_is_encrypted(t->io)) {
626 git_cert *cert;
627 int is_valid = (error == GIT_OK);
628
629 if ((error = git_stream_certificate(&cert, t->io)) < 0)
630 return error;
631
632 giterr_clear();
633 error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
634
635 if (error < 0) {
636 if (!giterr_last())
637 giterr_set(GITERR_NET, "user cancelled certificate check");
638
639 return error;
640 }
641 }
642
643 if (error < 0)
644 return error;
645
646 t->connected = 1;
647 return 0;
648 }
649
650 static int http_stream_read(
651 git_smart_subtransport_stream *stream,
652 char *buffer,
653 size_t buf_size,
654 size_t *bytes_read)
655 {
656 http_stream *s = (http_stream *)stream;
657 http_subtransport *t = OWNING_SUBTRANSPORT(s);
658 parser_context ctx;
659 size_t bytes_parsed;
660
661 replay:
662 *bytes_read = 0;
663
664 assert(t->connected);
665
666 if (!s->sent_request) {
667 git_buf request = GIT_BUF_INIT;
668
669 clear_parser_state(t);
670
671 if (gen_request(&request, s, 0) < 0)
672 return -1;
673
674 if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
675 git_buf_free(&request);
676 return -1;
677 }
678
679 git_buf_free(&request);
680
681 s->sent_request = 1;
682 }
683
684 if (!s->received_response) {
685 if (s->chunked) {
686 assert(s->verb == post_verb);
687
688 /* Flush, if necessary */
689 if (s->chunk_buffer_len > 0 &&
690 write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
691 return -1;
692
693 s->chunk_buffer_len = 0;
694
695 /* Write the final chunk. */
696 if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0)
697 return -1;
698 }
699
700 s->received_response = 1;
701 }
702
703 while (!*bytes_read && !t->parse_finished) {
704 size_t data_offset;
705 int error;
706
707 /*
708 * Make the parse_buffer think it's as full of data as
709 * the buffer, so it won't try to recv more data than
710 * we can put into it.
711 *
712 * data_offset is the actual data offset from which we
713 * should tell the parser to start reading.
714 */
715 if (buf_size >= t->parse_buffer.len) {
716 t->parse_buffer.offset = 0;
717 } else {
718 t->parse_buffer.offset = t->parse_buffer.len - buf_size;
719 }
720
721 data_offset = t->parse_buffer.offset;
722
723 if (gitno_recv(&t->parse_buffer) < 0)
724 return -1;
725
726 /* This call to http_parser_execute will result in invocations of the
727 * on_* family of callbacks. The most interesting of these is
728 * on_body_fill_buffer, which is called when data is ready to be copied
729 * into the target buffer. We need to marshal the buffer, buf_size, and
730 * bytes_read parameters to this callback. */
731 ctx.t = t;
732 ctx.s = s;
733 ctx.buffer = buffer;
734 ctx.buf_size = buf_size;
735 ctx.bytes_read = bytes_read;
736
737 /* Set the context, call the parser, then unset the context. */
738 t->parser.data = &ctx;
739
740 bytes_parsed = http_parser_execute(&t->parser,
741 &t->settings,
742 t->parse_buffer.data + data_offset,
743 t->parse_buffer.offset - data_offset);
744
745 t->parser.data = NULL;
746
747 /* If there was a handled authentication failure, then parse_error
748 * will have signaled us that we should replay the request. */
749 if (PARSE_ERROR_REPLAY == t->parse_error) {
750 s->sent_request = 0;
751
752 if ((error = http_connect(t)) < 0)
753 return error;
754
755 goto replay;
756 }
757
758 if (t->parse_error == PARSE_ERROR_EXT) {
759 return t->error;
760 }
761
762 if (t->parse_error < 0)
763 return -1;
764
765 if (bytes_parsed != t->parse_buffer.offset - data_offset) {
766 giterr_set(GITERR_NET,
767 "HTTP parser error: %s",
768 http_errno_description((enum http_errno)t->parser.http_errno));
769 return -1;
770 }
771 }
772
773 return 0;
774 }
775
776 static int http_stream_write_chunked(
777 git_smart_subtransport_stream *stream,
778 const char *buffer,
779 size_t len)
780 {
781 http_stream *s = (http_stream *)stream;
782 http_subtransport *t = OWNING_SUBTRANSPORT(s);
783
784 assert(t->connected);
785
786 /* Send the request, if necessary */
787 if (!s->sent_request) {
788 git_buf request = GIT_BUF_INIT;
789
790 clear_parser_state(t);
791
792 if (gen_request(&request, s, 0) < 0)
793 return -1;
794
795 if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
796 git_buf_free(&request);
797 return -1;
798 }
799
800 git_buf_free(&request);
801
802 s->sent_request = 1;
803 }
804
805 if (len > CHUNK_SIZE) {
806 /* Flush, if necessary */
807 if (s->chunk_buffer_len > 0) {
808 if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
809 return -1;
810
811 s->chunk_buffer_len = 0;
812 }
813
814 /* Write chunk directly */
815 if (write_chunk(t->io, buffer, len) < 0)
816 return -1;
817 }
818 else {
819 /* Append as much to the buffer as we can */
820 int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
821
822 if (!s->chunk_buffer)
823 s->chunk_buffer = git__malloc(CHUNK_SIZE);
824
825 memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
826 s->chunk_buffer_len += count;
827 buffer += count;
828 len -= count;
829
830 /* Is the buffer full? If so, then flush */
831 if (CHUNK_SIZE == s->chunk_buffer_len) {
832 if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
833 return -1;
834
835 s->chunk_buffer_len = 0;
836
837 if (len > 0) {
838 memcpy(s->chunk_buffer, buffer, len);
839 s->chunk_buffer_len = len;
840 }
841 }
842 }
843
844 return 0;
845 }
846
847 static int http_stream_write_single(
848 git_smart_subtransport_stream *stream,
849 const char *buffer,
850 size_t len)
851 {
852 http_stream *s = (http_stream *)stream;
853 http_subtransport *t = OWNING_SUBTRANSPORT(s);
854 git_buf request = GIT_BUF_INIT;
855
856 assert(t->connected);
857
858 if (s->sent_request) {
859 giterr_set(GITERR_NET, "subtransport configured for only one write");
860 return -1;
861 }
862
863 clear_parser_state(t);
864
865 if (gen_request(&request, s, len) < 0)
866 return -1;
867
868 if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
869 goto on_error;
870
871 if (len && git_stream_write(t->io, buffer, len, 0) < 0)
872 goto on_error;
873
874 git_buf_free(&request);
875 s->sent_request = 1;
876
877 return 0;
878
879 on_error:
880 git_buf_free(&request);
881 return -1;
882 }
883
884 static void http_stream_free(git_smart_subtransport_stream *stream)
885 {
886 http_stream *s = (http_stream *)stream;
887
888 if (s->chunk_buffer)
889 git__free(s->chunk_buffer);
890
891 if (s->redirect_url)
892 git__free(s->redirect_url);
893
894 git__free(s);
895 }
896
897 static int http_stream_alloc(http_subtransport *t,
898 git_smart_subtransport_stream **stream)
899 {
900 http_stream *s;
901
902 if (!stream)
903 return -1;
904
905 s = git__calloc(sizeof(http_stream), 1);
906 GITERR_CHECK_ALLOC(s);
907
908 s->parent.subtransport = &t->parent;
909 s->parent.read = http_stream_read;
910 s->parent.write = http_stream_write_single;
911 s->parent.free = http_stream_free;
912
913 *stream = (git_smart_subtransport_stream *)s;
914 return 0;
915 }
916
917 static int http_uploadpack_ls(
918 http_subtransport *t,
919 git_smart_subtransport_stream **stream)
920 {
921 http_stream *s;
922
923 if (http_stream_alloc(t, stream) < 0)
924 return -1;
925
926 s = (http_stream *)*stream;
927
928 s->service = upload_pack_service;
929 s->service_url = upload_pack_ls_service_url;
930 s->verb = get_verb;
931
932 return 0;
933 }
934
935 static int http_uploadpack(
936 http_subtransport *t,
937 git_smart_subtransport_stream **stream)
938 {
939 http_stream *s;
940
941 if (http_stream_alloc(t, stream) < 0)
942 return -1;
943
944 s = (http_stream *)*stream;
945
946 s->service = upload_pack_service;
947 s->service_url = upload_pack_service_url;
948 s->verb = post_verb;
949
950 return 0;
951 }
952
953 static int http_receivepack_ls(
954 http_subtransport *t,
955 git_smart_subtransport_stream **stream)
956 {
957 http_stream *s;
958
959 if (http_stream_alloc(t, stream) < 0)
960 return -1;
961
962 s = (http_stream *)*stream;
963
964 s->service = receive_pack_service;
965 s->service_url = receive_pack_ls_service_url;
966 s->verb = get_verb;
967
968 return 0;
969 }
970
971 static int http_receivepack(
972 http_subtransport *t,
973 git_smart_subtransport_stream **stream)
974 {
975 http_stream *s;
976
977 if (http_stream_alloc(t, stream) < 0)
978 return -1;
979
980 s = (http_stream *)*stream;
981
982 /* Use Transfer-Encoding: chunked for this request */
983 s->chunked = 1;
984 s->parent.write = http_stream_write_chunked;
985
986 s->service = receive_pack_service;
987 s->service_url = receive_pack_service_url;
988 s->verb = post_verb;
989
990 return 0;
991 }
992
993 static int http_action(
994 git_smart_subtransport_stream **stream,
995 git_smart_subtransport *subtransport,
996 const char *url,
997 git_smart_service_t action)
998 {
999 http_subtransport *t = (http_subtransport *)subtransport;
1000 int ret;
1001
1002 if (!stream)
1003 return -1;
1004
1005 if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
1006 (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
1007 return ret;
1008
1009 if ((ret = http_connect(t)) < 0)
1010 return ret;
1011
1012 switch (action) {
1013 case GIT_SERVICE_UPLOADPACK_LS:
1014 return http_uploadpack_ls(t, stream);
1015
1016 case GIT_SERVICE_UPLOADPACK:
1017 return http_uploadpack(t, stream);
1018
1019 case GIT_SERVICE_RECEIVEPACK_LS:
1020 return http_receivepack_ls(t, stream);
1021
1022 case GIT_SERVICE_RECEIVEPACK:
1023 return http_receivepack(t, stream);
1024 }
1025
1026 *stream = NULL;
1027 return -1;
1028 }
1029
1030 static int http_close(git_smart_subtransport *subtransport)
1031 {
1032 http_subtransport *t = (http_subtransport *) subtransport;
1033 git_http_auth_context *context;
1034 size_t i;
1035
1036 clear_parser_state(t);
1037
1038 t->connected = 0;
1039
1040 if (t->io) {
1041 git_stream_close(t->io);
1042 git_stream_free(t->io);
1043 t->io = NULL;
1044 }
1045
1046 if (t->cred) {
1047 t->cred->free(t->cred);
1048 t->cred = NULL;
1049 }
1050
1051 if (t->url_cred) {
1052 t->url_cred->free(t->url_cred);
1053 t->url_cred = NULL;
1054 }
1055
1056 git_vector_foreach(&t->auth_contexts, i, context) {
1057 if (context->free)
1058 context->free(context);
1059 }
1060
1061 git_vector_clear(&t->auth_contexts);
1062
1063 gitno_connection_data_free_ptrs(&t->connection_data);
1064 memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
1065
1066 return 0;
1067 }
1068
1069 static void http_free(git_smart_subtransport *subtransport)
1070 {
1071 http_subtransport *t = (http_subtransport *) subtransport;
1072
1073 http_close(subtransport);
1074
1075 git_vector_free(&t->auth_contexts);
1076 git__free(t);
1077 }
1078
1079 int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
1080 {
1081 http_subtransport *t;
1082
1083 GIT_UNUSED(param);
1084
1085 if (!out)
1086 return -1;
1087
1088 t = git__calloc(sizeof(http_subtransport), 1);
1089 GITERR_CHECK_ALLOC(t);
1090
1091 t->owner = (transport_smart *)owner;
1092 t->parent.action = http_action;
1093 t->parent.close = http_close;
1094 t->parent.free = http_free;
1095
1096 t->settings.on_header_field = on_header_field;
1097 t->settings.on_header_value = on_header_value;
1098 t->settings.on_headers_complete = on_headers_complete;
1099 t->settings.on_body = on_body_fill_buffer;
1100 t->settings.on_message_complete = on_message_complete;
1101
1102 *out = (git_smart_subtransport *) t;
1103 return 0;
1104 }
1105
1106 #endif /* !GIT_WINHTTP */