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