File Coverage

deps/libgit2/src/transports/httpclient.c
Criterion Covered Total %
statement 0 669 0.0
branch 0 474 0.0
condition n/a
subroutine n/a
pod n/a
total 0 1143 0.0


line stmt bran cond sub pod time code
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              
8             #include "common.h"
9             #include "git2.h"
10             #include "http_parser.h"
11             #include "vector.h"
12             #include "trace.h"
13             #include "global.h"
14             #include "httpclient.h"
15             #include "http.h"
16             #include "auth.h"
17             #include "auth_negotiate.h"
18             #include "auth_ntlm.h"
19             #include "git2/sys/credential.h"
20             #include "net.h"
21             #include "stream.h"
22             #include "streams/socket.h"
23             #include "streams/tls.h"
24             #include "auth.h"
25              
26             static git_http_auth_scheme auth_schemes[] = {
27             { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate },
28             { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_ntlm },
29             { GIT_HTTP_AUTH_BASIC, "Basic", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_basic },
30             };
31              
32             /*
33             * Use a 16kb read buffer to match the maximum size of a TLS packet. This
34             * is critical for compatibility with SecureTransport, which will always do
35             * a network read on every call, even if it has data buffered to return to
36             * you. That buffered data may be the _end_ of a keep-alive response, so
37             * if SecureTransport performs another network read, it will wait until the
38             * server ultimately times out before it returns that buffered data to you.
39             * Since SecureTransport only reads a single TLS packet at a time, by
40             * calling it with a read buffer that is the maximum size of a TLS packet,
41             * we ensure that it will never buffer.
42             */
43             #define GIT_READ_BUFFER_SIZE (16 * 1024)
44              
45             typedef struct {
46             git_net_url url;
47             git_stream *stream;
48              
49             git_vector auth_challenges;
50             git_http_auth_context *auth_context;
51             } git_http_server;
52              
53             typedef enum {
54             PROXY = 1,
55             SERVER
56             } git_http_server_t;
57              
58             typedef enum {
59             NONE = 0,
60             SENDING_REQUEST,
61             SENDING_BODY,
62             SENT_REQUEST,
63             HAS_EARLY_RESPONSE,
64             READING_RESPONSE,
65             READING_BODY,
66             DONE
67             } http_client_state;
68              
69             /* Parser state */
70             typedef enum {
71             PARSE_HEADER_NONE = 0,
72             PARSE_HEADER_NAME,
73             PARSE_HEADER_VALUE,
74             PARSE_HEADER_COMPLETE
75             } parse_header_state;
76              
77             typedef enum {
78             PARSE_STATUS_OK,
79             PARSE_STATUS_NO_OUTPUT,
80             PARSE_STATUS_ERROR
81             } parse_status;
82              
83             typedef struct {
84             git_http_client *client;
85             git_http_response *response;
86              
87             /* Temporary buffers to avoid extra mallocs */
88             git_buf parse_header_name;
89             git_buf parse_header_value;
90              
91             /* Parser state */
92             int error;
93             parse_status parse_status;
94              
95             /* Headers parsing */
96             parse_header_state parse_header_state;
97              
98             /* Body parsing */
99             char *output_buf; /* Caller's output buffer */
100             size_t output_size; /* Size of caller's output buffer */
101             size_t output_written; /* Bytes we've written to output buffer */
102             } http_parser_context;
103              
104             /* HTTP client connection */
105             struct git_http_client {
106             git_http_client_options opts;
107              
108             /* Are we writing to the proxy or server, and state of the client. */
109             git_http_server_t current_server;
110             http_client_state state;
111              
112             http_parser parser;
113              
114             git_http_server server;
115             git_http_server proxy;
116              
117             unsigned request_count;
118             unsigned connected : 1,
119             proxy_connected : 1,
120             keepalive : 1,
121             request_chunked : 1;
122              
123             /* Temporary buffers to avoid extra mallocs */
124             git_buf request_msg;
125             git_buf read_buf;
126              
127             /* A subset of information from the request */
128             size_t request_body_len,
129             request_body_remain;
130              
131             /*
132             * When state == HAS_EARLY_RESPONSE, the response of our proxy
133             * that we have buffered and will deliver during read_response.
134             */
135             git_http_response early_response;
136             };
137              
138 0           bool git_http_response_is_redirect(git_http_response *response)
139             {
140 0 0         return (response->status == GIT_HTTP_MOVED_PERMANENTLY ||
141 0 0         response->status == GIT_HTTP_FOUND ||
142 0 0         response->status == GIT_HTTP_SEE_OTHER ||
143 0 0         response->status == GIT_HTTP_TEMPORARY_REDIRECT ||
    0          
144 0           response->status == GIT_HTTP_PERMANENT_REDIRECT);
145             }
146              
147 0           void git_http_response_dispose(git_http_response *response)
148             {
149 0 0         assert(response);
150              
151 0           git__free(response->content_type);
152 0           git__free(response->location);
153              
154 0           memset(response, 0, sizeof(git_http_response));
155 0           }
156              
157 0           static int on_header_complete(http_parser *parser)
158             {
159 0           http_parser_context *ctx = (http_parser_context *) parser->data;
160 0           git_http_client *client = ctx->client;
161 0           git_http_response *response = ctx->response;
162              
163 0           git_buf *name = &ctx->parse_header_name;
164 0           git_buf *value = &ctx->parse_header_value;
165              
166 0 0         if (!strcasecmp("Content-Type", name->ptr)) {
167 0 0         if (response->content_type) {
168 0           git_error_set(GIT_ERROR_HTTP,
169             "multiple content-type headers");
170 0           return -1;
171             }
172              
173 0           response->content_type =
174 0           git__strndup(value->ptr, value->size);
175 0 0         GIT_ERROR_CHECK_ALLOC(ctx->response->content_type);
176 0 0         } else if (!strcasecmp("Content-Length", name->ptr)) {
177             int64_t len;
178              
179 0 0         if (response->content_length) {
180 0           git_error_set(GIT_ERROR_HTTP,
181             "multiple content-length headers");
182 0           return -1;
183             }
184              
185 0 0         if (git__strntol64(&len, value->ptr, value->size,
186 0 0         NULL, 10) < 0 || len < 0) {
187 0           git_error_set(GIT_ERROR_HTTP,
188             "invalid content-length");
189 0           return -1;
190             }
191              
192 0           response->content_length = (size_t)len;
193 0 0         } else if (!strcasecmp("Transfer-Encoding", name->ptr) &&
    0          
194 0           !strcasecmp("chunked", value->ptr)) {
195 0           ctx->response->chunked = 1;
196 0 0         } else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) {
197 0           char *dup = git__strndup(value->ptr, value->size);
198 0 0         GIT_ERROR_CHECK_ALLOC(dup);
199              
200 0 0         if (git_vector_insert(&client->proxy.auth_challenges, dup) < 0)
201 0           return -1;
202 0 0         } else if (!strcasecmp("WWW-Authenticate", name->ptr)) {
203 0           char *dup = git__strndup(value->ptr, value->size);
204 0 0         GIT_ERROR_CHECK_ALLOC(dup);
205              
206 0 0         if (git_vector_insert(&client->server.auth_challenges, dup) < 0)
207 0           return -1;
208 0 0         } else if (!strcasecmp("Location", name->ptr)) {
209 0 0         if (response->location) {
210 0           git_error_set(GIT_ERROR_HTTP,
211             "multiple location headers");
212 0           return -1;
213             }
214              
215 0           response->location = git__strndup(value->ptr, value->size);
216 0 0         GIT_ERROR_CHECK_ALLOC(response->location);
217             }
218              
219 0           return 0;
220             }
221              
222 0           static int on_header_field(http_parser *parser, const char *str, size_t len)
223             {
224 0           http_parser_context *ctx = (http_parser_context *) parser->data;
225              
226 0           switch (ctx->parse_header_state) {
227             /*
228             * We last saw a header value, process the name/value pair and
229             * get ready to handle this new name.
230             */
231             case PARSE_HEADER_VALUE:
232 0 0         if (on_header_complete(parser) < 0)
233 0           return ctx->parse_status = PARSE_STATUS_ERROR;
234              
235 0           git_buf_clear(&ctx->parse_header_name);
236 0           git_buf_clear(&ctx->parse_header_value);
237             /* Fall through */
238              
239             case PARSE_HEADER_NONE:
240             case PARSE_HEADER_NAME:
241 0           ctx->parse_header_state = PARSE_HEADER_NAME;
242              
243 0 0         if (git_buf_put(&ctx->parse_header_name, str, len) < 0)
244 0           return ctx->parse_status = PARSE_STATUS_ERROR;
245              
246 0           break;
247              
248             default:
249 0           git_error_set(GIT_ERROR_HTTP,
250             "header name seen at unexpected time");
251 0           return ctx->parse_status = PARSE_STATUS_ERROR;
252             }
253              
254 0           return 0;
255             }
256              
257 0           static int on_header_value(http_parser *parser, const char *str, size_t len)
258             {
259 0           http_parser_context *ctx = (http_parser_context *) parser->data;
260              
261 0 0         switch (ctx->parse_header_state) {
262             case PARSE_HEADER_NAME:
263             case PARSE_HEADER_VALUE:
264 0           ctx->parse_header_state = PARSE_HEADER_VALUE;
265              
266 0 0         if (git_buf_put(&ctx->parse_header_value, str, len) < 0)
267 0           return ctx->parse_status = PARSE_STATUS_ERROR;
268              
269 0           break;
270              
271             default:
272 0           git_error_set(GIT_ERROR_HTTP,
273             "header value seen at unexpected time");
274 0           return ctx->parse_status = PARSE_STATUS_ERROR;
275             }
276              
277 0           return 0;
278             }
279              
280 0           GIT_INLINE(bool) challenge_matches_scheme(
281             const char *challenge,
282             git_http_auth_scheme *scheme)
283             {
284 0           const char *scheme_name = scheme->name;
285 0           size_t scheme_len = strlen(scheme_name);
286              
287 0 0         if (!strncasecmp(challenge, scheme_name, scheme_len) &&
    0          
288 0 0         (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '))
289 0           return true;
290              
291 0           return false;
292             }
293              
294 0           static git_http_auth_scheme *scheme_for_challenge(const char *challenge)
295             {
296             size_t i;
297              
298 0 0         for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
299 0 0         if (challenge_matches_scheme(challenge, &auth_schemes[i]))
300 0           return &auth_schemes[i];
301             }
302              
303 0           return NULL;
304             }
305              
306 0           GIT_INLINE(void) collect_authinfo(
307             unsigned int *schemetypes,
308             unsigned int *credtypes,
309             git_vector *challenges)
310             {
311             git_http_auth_scheme *scheme;
312             const char *challenge;
313             size_t i;
314              
315 0           *schemetypes = 0;
316 0           *credtypes = 0;
317              
318 0 0         git_vector_foreach(challenges, i, challenge) {
319 0 0         if ((scheme = scheme_for_challenge(challenge)) != NULL) {
320 0           *schemetypes |= scheme->type;
321 0           *credtypes |= scheme->credtypes;
322             }
323             }
324 0           }
325              
326 0           static int resend_needed(git_http_client *client, git_http_response *response)
327             {
328             git_http_auth_context *auth_context;
329              
330 0 0         if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED &&
    0          
331 0 0         (auth_context = client->server.auth_context) &&
332 0 0         auth_context->is_complete &&
333 0           !auth_context->is_complete(auth_context))
334 0           return 1;
335              
336 0 0         if (response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED &&
    0          
337 0 0         (auth_context = client->proxy.auth_context) &&
338 0 0         auth_context->is_complete &&
339 0           !auth_context->is_complete(auth_context))
340 0           return 1;
341              
342 0           return 0;
343             }
344              
345 0           static int on_headers_complete(http_parser *parser)
346             {
347 0           http_parser_context *ctx = (http_parser_context *) parser->data;
348              
349             /* Finalize the last seen header */
350 0           switch (ctx->parse_header_state) {
351             case PARSE_HEADER_VALUE:
352 0 0         if (on_header_complete(parser) < 0)
353 0           return ctx->parse_status = PARSE_STATUS_ERROR;
354              
355             /* Fall through */
356              
357             case PARSE_HEADER_NONE:
358 0           ctx->parse_header_state = PARSE_HEADER_COMPLETE;
359 0           break;
360              
361             default:
362 0           git_error_set(GIT_ERROR_HTTP,
363             "header completion at unexpected time");
364 0           return ctx->parse_status = PARSE_STATUS_ERROR;
365             }
366              
367 0           ctx->response->status = parser->status_code;
368 0           ctx->client->keepalive = http_should_keep_alive(parser);
369              
370             /* Prepare for authentication */
371 0           collect_authinfo(&ctx->response->server_auth_schemetypes,
372 0           &ctx->response->server_auth_credtypes,
373 0           &ctx->client->server.auth_challenges);
374 0           collect_authinfo(&ctx->response->proxy_auth_schemetypes,
375 0           &ctx->response->proxy_auth_credtypes,
376 0           &ctx->client->proxy.auth_challenges);
377              
378 0           ctx->response->resend_credentials = resend_needed(ctx->client,
379             ctx->response);
380              
381             /* Stop parsing. */
382 0           http_parser_pause(parser, 1);
383              
384 0 0         if (ctx->response->content_type || ctx->response->chunked)
    0          
385 0           ctx->client->state = READING_BODY;
386             else
387 0           ctx->client->state = DONE;
388              
389 0           return 0;
390             }
391              
392 0           static int on_body(http_parser *parser, const char *buf, size_t len)
393             {
394 0           http_parser_context *ctx = (http_parser_context *) parser->data;
395             size_t max_len;
396              
397             /* Saw data when we expected not to (eg, in consume_response_body) */
398 0 0         if (ctx->output_buf == NULL && ctx->output_size == 0) {
    0          
399 0           ctx->parse_status = PARSE_STATUS_NO_OUTPUT;
400 0           return 0;
401             }
402              
403 0 0         assert(ctx->output_size >= ctx->output_written);
404              
405 0           max_len = min(ctx->output_size - ctx->output_written, len);
406 0           max_len = min(max_len, INT_MAX);
407              
408 0           memcpy(ctx->output_buf + ctx->output_written, buf, max_len);
409 0           ctx->output_written += max_len;
410              
411 0           return 0;
412             }
413              
414 0           static int on_message_complete(http_parser *parser)
415             {
416 0           http_parser_context *ctx = (http_parser_context *) parser->data;
417              
418 0           ctx->client->state = DONE;
419 0           return 0;
420             }
421              
422 0           GIT_INLINE(int) stream_write(
423             git_http_server *server,
424             const char *data,
425             size_t len)
426             {
427 0           git_trace(GIT_TRACE_TRACE,
428             "Sending request:\n%.*s", (int)len, data);
429              
430 0           return git_stream__write_full(server->stream, data, len, 0);
431             }
432              
433 0           GIT_INLINE(int) client_write_request(git_http_client *client)
434             {
435 0           git_stream *stream = client->current_server == PROXY ?
436 0 0         client->proxy.stream : client->server.stream;
437              
438 0           git_trace(GIT_TRACE_TRACE,
439             "Sending request:\n%.*s",
440 0           (int)client->request_msg.size, client->request_msg.ptr);
441              
442 0           return git_stream__write_full(stream,
443 0           client->request_msg.ptr,
444             client->request_msg.size,
445             0);
446             }
447              
448 0           const char *name_for_method(git_http_method method)
449             {
450 0           switch (method) {
451             case GIT_HTTP_METHOD_GET:
452 0           return "GET";
453             case GIT_HTTP_METHOD_POST:
454 0           return "POST";
455             case GIT_HTTP_METHOD_CONNECT:
456 0           return "CONNECT";
457             }
458              
459 0           return NULL;
460             }
461              
462             /*
463             * Find the scheme that is suitable for the given credentials, based on the
464             * server's auth challenges.
465             */
466 0           static bool best_scheme_and_challenge(
467             git_http_auth_scheme **scheme_out,
468             const char **challenge_out,
469             git_vector *challenges,
470             git_credential *credentials)
471             {
472             const char *challenge;
473             size_t i, j;
474              
475 0 0         for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
476 0 0         git_vector_foreach(challenges, j, challenge) {
477 0           git_http_auth_scheme *scheme = &auth_schemes[i];
478              
479 0 0         if (challenge_matches_scheme(challenge, scheme) &&
    0          
480 0           (scheme->credtypes & credentials->credtype)) {
481 0           *scheme_out = scheme;
482 0           *challenge_out = challenge;
483 0           return true;
484             }
485             }
486             }
487              
488 0           return false;
489             }
490              
491             /*
492             * Find the challenge from the server for our current auth context.
493             */
494 0           static const char *challenge_for_context(
495             git_vector *challenges,
496             git_http_auth_context *auth_ctx)
497             {
498             const char *challenge;
499             size_t i, j;
500              
501 0 0         for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
502 0 0         if (auth_schemes[i].type == auth_ctx->type) {
503 0           git_http_auth_scheme *scheme = &auth_schemes[i];
504              
505 0 0         git_vector_foreach(challenges, j, challenge) {
506 0 0         if (challenge_matches_scheme(challenge, scheme))
507 0           return challenge;
508             }
509             }
510             }
511              
512 0           return NULL;
513             }
514              
515 0           static const char *init_auth_context(
516             git_http_server *server,
517             git_vector *challenges,
518             git_credential *credentials)
519             {
520             git_http_auth_scheme *scheme;
521             const char *challenge;
522             int error;
523              
524 0 0         if (!best_scheme_and_challenge(&scheme, &challenge, challenges, credentials)) {
525 0           git_error_set(GIT_ERROR_HTTP, "could not find appropriate mechanism for credentials");
526 0           return NULL;
527             }
528              
529 0           error = scheme->init_context(&server->auth_context, &server->url);
530              
531 0 0         if (error == GIT_PASSTHROUGH) {
532 0           git_error_set(GIT_ERROR_HTTP, "'%s' authentication is not supported", scheme->name);
533 0           return NULL;
534             }
535              
536 0           return challenge;
537             }
538              
539 0           static void free_auth_context(git_http_server *server)
540             {
541 0 0         if (!server->auth_context)
542 0           return;
543              
544 0 0         if (server->auth_context->free)
545 0           server->auth_context->free(server->auth_context);
546              
547 0           server->auth_context = NULL;
548             }
549              
550 0           static int apply_credentials(
551             git_buf *buf,
552             git_http_server *server,
553             const char *header_name,
554             git_credential *credentials)
555             {
556 0           git_http_auth_context *auth = server->auth_context;
557 0           git_vector *challenges = &server->auth_challenges;
558             const char *challenge;
559 0           git_buf token = GIT_BUF_INIT;
560 0           int error = 0;
561              
562             /* We've started a new request without creds; free the context. */
563 0 0         if (auth && !credentials) {
    0          
564 0           free_auth_context(server);
565 0           return 0;
566             }
567              
568             /* We haven't authenticated, nor were we asked to. Nothing to do. */
569 0 0         if (!auth && !git_vector_length(challenges))
    0          
570 0           return 0;
571              
572 0 0         if (!auth) {
573 0           challenge = init_auth_context(server, challenges, credentials);
574 0           auth = server->auth_context;
575              
576 0 0         if (!challenge || !auth) {
    0          
577 0           error = -1;
578 0           goto done;
579             }
580 0 0         } else if (auth->set_challenge) {
581 0           challenge = challenge_for_context(challenges, auth);
582             }
583              
584 0 0         if (auth->set_challenge && challenge &&
    0          
    0          
585 0           (error = auth->set_challenge(auth, challenge)) < 0)
586 0           goto done;
587              
588 0 0         if ((error = auth->next_token(&token, auth, credentials)) < 0)
589 0           goto done;
590              
591 0 0         if (auth->is_complete && auth->is_complete(auth)) {
    0          
592             /*
593             * If we're done with an auth mechanism with connection affinity,
594             * we don't need to send any more headers and can dispose the context.
595             */
596 0 0         if (auth->connection_affinity)
597 0           free_auth_context(server);
598 0 0         } else if (!token.size) {
599 0           git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge");
600 0           error = -1;
601 0           goto done;
602             }
603              
604 0 0         if (token.size > 0)
605 0           error = git_buf_printf(buf, "%s: %s\r\n", header_name, token.ptr);
606              
607             done:
608 0           git_buf_dispose(&token);
609 0           return error;
610             }
611              
612 0           GIT_INLINE(int) apply_server_credentials(
613             git_buf *buf,
614             git_http_client *client,
615             git_http_request *request)
616             {
617 0           return apply_credentials(buf,
618             &client->server,
619             "Authorization",
620             request->credentials);
621             }
622              
623 0           GIT_INLINE(int) apply_proxy_credentials(
624             git_buf *buf,
625             git_http_client *client,
626             git_http_request *request)
627             {
628 0           return apply_credentials(buf,
629             &client->proxy,
630             "Proxy-Authorization",
631             request->proxy_credentials);
632             }
633              
634 0           static int generate_connect_request(
635             git_http_client *client,
636             git_http_request *request)
637             {
638             git_buf *buf;
639             int error;
640              
641 0           git_buf_clear(&client->request_msg);
642 0           buf = &client->request_msg;
643              
644 0           git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n",
645             client->server.url.host, client->server.url.port);
646              
647 0           git_buf_puts(buf, "User-Agent: ");
648 0           git_http__user_agent(buf);
649 0           git_buf_puts(buf, "\r\n");
650              
651 0           git_buf_printf(buf, "Host: %s\r\n", client->proxy.url.host);
652              
653 0 0         if ((error = apply_proxy_credentials(buf, client, request) < 0))
654 0           return -1;
655              
656 0           git_buf_puts(buf, "\r\n");
657              
658 0 0         return git_buf_oom(buf) ? -1 : 0;
659             }
660              
661 0           static int generate_request(
662             git_http_client *client,
663             git_http_request *request)
664             {
665             git_buf *buf;
666             size_t i;
667             int error;
668              
669 0 0         assert(client && request);
    0          
670              
671 0           git_buf_clear(&client->request_msg);
672 0           buf = &client->request_msg;
673              
674             /* GET|POST path HTTP/1.1 */
675 0           git_buf_puts(buf, name_for_method(request->method));
676 0           git_buf_putc(buf, ' ');
677              
678 0 0         if (request->proxy && strcmp(request->url->scheme, "https"))
    0          
679 0           git_net_url_fmt(buf, request->url);
680             else
681 0           git_net_url_fmt_path(buf, request->url);
682              
683 0           git_buf_puts(buf, " HTTP/1.1\r\n");
684              
685 0           git_buf_puts(buf, "User-Agent: ");
686 0           git_http__user_agent(buf);
687 0           git_buf_puts(buf, "\r\n");
688              
689 0           git_buf_printf(buf, "Host: %s", request->url->host);
690              
691 0 0         if (!git_net_url_is_default_port(request->url))
692 0           git_buf_printf(buf, ":%s", request->url->port);
693              
694 0           git_buf_puts(buf, "\r\n");
695              
696 0 0         if (request->accept)
697 0           git_buf_printf(buf, "Accept: %s\r\n", request->accept);
698             else
699 0           git_buf_puts(buf, "Accept: */*\r\n");
700              
701 0 0         if (request->content_type)
702 0           git_buf_printf(buf, "Content-Type: %s\r\n",
703             request->content_type);
704              
705 0 0         if (request->chunked)
706 0           git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
707              
708 0 0         if (request->content_length > 0)
709 0           git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n",
710             request->content_length);
711              
712 0 0         if (request->expect_continue)
713 0           git_buf_printf(buf, "Expect: 100-continue\r\n");
714              
715 0 0         if ((error = apply_server_credentials(buf, client, request)) < 0 ||
    0          
716             (error = apply_proxy_credentials(buf, client, request)) < 0)
717 0           return error;
718              
719 0 0         if (request->custom_headers) {
720 0 0         for (i = 0; i < request->custom_headers->count; i++) {
721 0           const char *hdr = request->custom_headers->strings[i];
722              
723 0 0         if (hdr)
724 0           git_buf_printf(buf, "%s\r\n", hdr);
725             }
726             }
727              
728 0           git_buf_puts(buf, "\r\n");
729              
730 0 0         if (git_buf_oom(buf))
731 0           return -1;
732              
733 0           return 0;
734             }
735              
736 0           static int check_certificate(
737             git_stream *stream,
738             git_net_url *url,
739             int is_valid,
740             git_transport_certificate_check_cb cert_cb,
741             void *cert_cb_payload)
742             {
743             git_cert *cert;
744 0           git_error_state last_error = {0};
745             int error;
746              
747 0 0         if ((error = git_stream_certificate(&cert, stream)) < 0)
748 0           return error;
749              
750 0           git_error_state_capture(&last_error, GIT_ECERTIFICATE);
751              
752 0           error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
753              
754 0 0         if (error == GIT_PASSTHROUGH && !is_valid)
    0          
755 0           return git_error_state_restore(&last_error);
756 0 0         else if (error == GIT_PASSTHROUGH)
757 0           error = 0;
758 0 0         else if (error && !git_error_last())
    0          
759 0           git_error_set(GIT_ERROR_HTTP,
760             "user rejected certificate for %s", url->host);
761              
762 0           git_error_state_free(&last_error);
763 0           return error;
764             }
765              
766 0           static int server_connect_stream(
767             git_http_server *server,
768             git_transport_certificate_check_cb cert_cb,
769             void *cb_payload)
770             {
771             int error;
772              
773 0 0         GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream");
774              
775 0           error = git_stream_connect(server->stream);
776              
777 0 0         if (error && error != GIT_ECERTIFICATE)
    0          
778 0           return error;
779              
780 0 0         if (git_stream_is_encrypted(server->stream) && cert_cb != NULL)
    0          
781 0           error = check_certificate(server->stream, &server->url, !error,
782             cert_cb, cb_payload);
783              
784 0           return error;
785             }
786              
787 0           static void reset_auth_connection(git_http_server *server)
788             {
789             /*
790             * If we've authenticated and we're doing "normal"
791             * authentication with a request affinity (Basic, Digest)
792             * then we want to _keep_ our context, since authentication
793             * survives even through non-keep-alive connections. If
794             * we've authenticated and we're doing connection-based
795             * authentication (NTLM, Negotiate) - indicated by the presence
796             * of an `is_complete` callback - then we need to restart
797             * authentication on a new connection.
798             */
799              
800 0 0         if (server->auth_context &&
    0          
801 0           server->auth_context->connection_affinity)
802 0           free_auth_context(server);
803 0           }
804              
805             /*
806             * Updates the server data structure with the new URL; returns 1 if the server
807             * has changed and we need to reconnect, returns 0 otherwise.
808             */
809 0           GIT_INLINE(int) server_setup_from_url(
810             git_http_server *server,
811             git_net_url *url)
812             {
813 0 0         if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) ||
    0          
    0          
814 0 0         !server->url.host || strcmp(server->url.host, url->host) ||
    0          
815 0 0         !server->url.port || strcmp(server->url.port, url->port)) {
816 0           git__free(server->url.scheme);
817 0           git__free(server->url.host);
818 0           git__free(server->url.port);
819              
820 0           server->url.scheme = git__strdup(url->scheme);
821 0 0         GIT_ERROR_CHECK_ALLOC(server->url.scheme);
822              
823 0           server->url.host = git__strdup(url->host);
824 0 0         GIT_ERROR_CHECK_ALLOC(server->url.host);
825              
826 0           server->url.port = git__strdup(url->port);
827 0 0         GIT_ERROR_CHECK_ALLOC(server->url.port);
828              
829 0           return 1;
830             }
831              
832 0           return 0;
833             }
834              
835 0           static void reset_parser(git_http_client *client)
836             {
837 0           http_parser_init(&client->parser, HTTP_RESPONSE);
838 0           }
839              
840 0           static int setup_hosts(
841             git_http_client *client,
842             git_http_request *request)
843             {
844 0           int ret, diff = 0;
845              
846 0 0         assert(client && request && request->url);
    0          
    0          
847              
848 0 0         if ((ret = server_setup_from_url(&client->server, request->url)) < 0)
849 0           return ret;
850              
851 0           diff |= ret;
852              
853 0 0         if (request->proxy &&
    0          
854 0           (ret = server_setup_from_url(&client->proxy, request->proxy)) < 0)
855 0           return ret;
856              
857 0           diff |= ret;
858              
859 0 0         if (diff) {
860 0           free_auth_context(&client->server);
861 0           free_auth_context(&client->proxy);
862              
863 0           client->connected = 0;
864             }
865              
866 0           return 0;
867             }
868              
869 0           GIT_INLINE(int) server_create_stream(git_http_server *server)
870             {
871 0           git_net_url *url = &server->url;
872              
873 0 0         if (strcasecmp(url->scheme, "https") == 0)
874 0           return git_tls_stream_new(&server->stream, url->host, url->port);
875 0 0         else if (strcasecmp(url->scheme, "http") == 0)
876 0           return git_socket_stream_new(&server->stream, url->host, url->port);
877              
878 0           git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme);
879 0           return -1;
880             }
881              
882 0           GIT_INLINE(void) save_early_response(
883             git_http_client *client,
884             git_http_response *response)
885             {
886             /* Buffer the response so we can return it in read_response */
887 0           client->state = HAS_EARLY_RESPONSE;
888              
889 0           memcpy(&client->early_response, response, sizeof(git_http_response));
890 0           memset(response, 0, sizeof(git_http_response));
891 0           }
892              
893 0           static int proxy_connect(
894             git_http_client *client,
895             git_http_request *request)
896             {
897 0           git_http_response response = {0};
898             int error;
899              
900 0 0         if (!client->proxy_connected || !client->keepalive) {
    0          
901 0           git_trace(GIT_TRACE_DEBUG, "Connecting to proxy %s:%s",
902             client->proxy.url.host, client->proxy.url.port);
903              
904 0 0         if ((error = server_create_stream(&client->proxy)) < 0 ||
    0          
905 0           (error = server_connect_stream(&client->proxy,
906             client->opts.proxy_certificate_check_cb,
907             client->opts.proxy_certificate_check_payload)) < 0)
908             goto done;
909              
910 0           client->proxy_connected = 1;
911             }
912              
913 0           client->current_server = PROXY;
914 0           client->state = SENDING_REQUEST;
915              
916 0 0         if ((error = generate_connect_request(client, request)) < 0 ||
    0          
917             (error = client_write_request(client)) < 0)
918             goto done;
919              
920 0           client->state = SENT_REQUEST;
921              
922 0 0         if ((error = git_http_client_read_response(&response, client)) < 0 ||
    0          
923             (error = git_http_client_skip_body(client)) < 0)
924             goto done;
925              
926 0 0         assert(client->state == DONE);
927              
928 0 0         if (response.status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
929 0           save_early_response(client, &response);
930              
931 0           error = GIT_RETRY;
932 0           goto done;
933 0 0         } else if (response.status != GIT_HTTP_STATUS_OK) {
934 0           git_error_set(GIT_ERROR_HTTP, "proxy returned unexpected status: %d", response.status);
935 0           error = -1;
936 0           goto done;
937             }
938              
939 0           reset_parser(client);
940 0           client->state = NONE;
941              
942             done:
943 0           git_http_response_dispose(&response);
944 0           return error;
945             }
946              
947 0           static int server_connect(git_http_client *client)
948             {
949 0           git_net_url *url = &client->server.url;
950             git_transport_certificate_check_cb cert_cb;
951             void *cert_payload;
952             int error;
953              
954 0           client->current_server = SERVER;
955              
956 0 0         if (client->proxy.stream)
957 0           error = git_tls_stream_wrap(&client->server.stream, client->proxy.stream, url->host);
958             else
959 0           error = server_create_stream(&client->server);
960              
961 0 0         if (error < 0)
962 0           goto done;
963              
964 0           cert_cb = client->opts.server_certificate_check_cb;
965 0           cert_payload = client->opts.server_certificate_check_payload;
966              
967 0           error = server_connect_stream(&client->server, cert_cb, cert_payload);
968              
969             done:
970 0           return error;
971             }
972              
973 0           GIT_INLINE(void) close_stream(git_http_server *server)
974             {
975 0 0         if (server->stream) {
976 0           git_stream_close(server->stream);
977 0           git_stream_free(server->stream);
978 0           server->stream = NULL;
979             }
980 0           }
981              
982 0           static int http_client_connect(
983             git_http_client *client,
984             git_http_request *request)
985             {
986 0           bool use_proxy = false;
987             int error;
988              
989 0 0         if ((error = setup_hosts(client, request)) < 0)
990 0           goto on_error;
991              
992             /* We're connected to our destination server; no need to reconnect */
993 0 0         if (client->connected && client->keepalive &&
    0          
    0          
994 0 0         (client->state == NONE || client->state == DONE))
995 0           return 0;
996              
997 0           client->connected = 0;
998 0           client->request_count = 0;
999              
1000 0           close_stream(&client->server);
1001 0           reset_auth_connection(&client->server);
1002              
1003 0           reset_parser(client);
1004              
1005             /* Reconnect to the proxy if necessary. */
1006 0 0         use_proxy = client->proxy.url.host &&
    0          
1007 0           !strcmp(client->server.url.scheme, "https");
1008              
1009 0 0         if (use_proxy) {
1010 0 0         if (!client->proxy_connected || !client->keepalive ||
    0          
    0          
1011 0 0         (client->state != NONE && client->state != DONE)) {
1012 0           close_stream(&client->proxy);
1013 0           reset_auth_connection(&client->proxy);
1014              
1015 0           client->proxy_connected = 0;
1016             }
1017              
1018 0 0         if ((error = proxy_connect(client, request)) < 0)
1019 0           goto on_error;
1020             }
1021              
1022 0           git_trace(GIT_TRACE_DEBUG, "Connecting to remote %s:%s",
1023             client->server.url.host, client->server.url.port);
1024              
1025 0 0         if ((error = server_connect(client)) < 0)
1026 0           goto on_error;
1027              
1028 0           client->connected = 1;
1029 0           return error;
1030              
1031             on_error:
1032 0 0         if (error != GIT_RETRY)
1033 0           close_stream(&client->proxy);
1034              
1035 0           close_stream(&client->server);
1036 0           return error;
1037             }
1038              
1039 0           GIT_INLINE(int) client_read(git_http_client *client)
1040             {
1041             git_stream *stream;
1042 0           char *buf = client->read_buf.ptr + client->read_buf.size;
1043             size_t max_len;
1044             ssize_t read_len;
1045              
1046 0           stream = client->current_server == PROXY ?
1047 0 0         client->proxy.stream : client->server.stream;
1048              
1049             /*
1050             * We use a git_buf for convenience, but statically allocate it and
1051             * don't resize. Limit our consumption to INT_MAX since calling
1052             * functions use an int return type to return number of bytes read.
1053             */
1054 0           max_len = client->read_buf.asize - client->read_buf.size;
1055 0           max_len = min(max_len, INT_MAX);
1056              
1057 0 0         if (max_len == 0) {
1058 0           git_error_set(GIT_ERROR_HTTP, "no room in output buffer");
1059 0           return -1;
1060             }
1061              
1062 0           read_len = git_stream_read(stream, buf, max_len);
1063              
1064 0 0         if (read_len >= 0) {
1065 0           client->read_buf.size += read_len;
1066              
1067 0           git_trace(GIT_TRACE_TRACE, "Received:\n%.*s",
1068             (int)read_len, buf);
1069             }
1070              
1071 0           return (int)read_len;
1072             }
1073              
1074             static bool parser_settings_initialized;
1075             static http_parser_settings parser_settings;
1076              
1077 0           GIT_INLINE(http_parser_settings *) http_client_parser_settings(void)
1078             {
1079 0 0         if (!parser_settings_initialized) {
1080 0           parser_settings.on_header_field = on_header_field;
1081 0           parser_settings.on_header_value = on_header_value;
1082 0           parser_settings.on_headers_complete = on_headers_complete;
1083 0           parser_settings.on_body = on_body;
1084 0           parser_settings.on_message_complete = on_message_complete;
1085              
1086 0           parser_settings_initialized = true;
1087             }
1088              
1089 0           return &parser_settings;
1090             }
1091              
1092 0           GIT_INLINE(int) client_read_and_parse(git_http_client *client)
1093             {
1094 0           http_parser *parser = &client->parser;
1095 0           http_parser_context *ctx = (http_parser_context *) parser->data;
1096             unsigned char http_errno;
1097             int read_len;
1098             size_t parsed_len;
1099              
1100             /*
1101             * If we have data in our read buffer, that means we stopped early
1102             * when parsing headers. Use the data in the read buffer instead of
1103             * reading more from the socket.
1104             */
1105 0 0         if (!client->read_buf.size && (read_len = client_read(client)) < 0)
    0          
1106 0           return read_len;
1107              
1108 0           parsed_len = http_parser_execute(parser,
1109 0           http_client_parser_settings(),
1110 0           client->read_buf.ptr,
1111             client->read_buf.size);
1112 0           http_errno = client->parser.http_errno;
1113              
1114 0 0         if (parsed_len > INT_MAX) {
1115 0           git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse");
1116 0           return -1;
1117             }
1118              
1119 0 0         if (parser->upgrade) {
1120 0           git_error_set(GIT_ERROR_HTTP, "server requested upgrade");
1121 0           return -1;
1122             }
1123              
1124 0 0         if (ctx->parse_status == PARSE_STATUS_ERROR) {
1125 0           client->connected = 0;
1126 0 0         return ctx->error ? ctx->error : -1;
1127             }
1128              
1129             /*
1130             * If we finished reading the headers or body, we paused parsing.
1131             * Otherwise the parser will start filling the body, or even parse
1132             * a new response if the server pipelined us multiple responses.
1133             * (This can happen in response to an expect/continue request,
1134             * where the server gives you a 100 and 200 simultaneously.)
1135             */
1136 0 0         if (http_errno == HPE_PAUSED) {
1137             /*
1138             * http-parser has a "feature" where it will not deliver the
1139             * final byte when paused in a callback. Consume that byte.
1140             * https://github.com/nodejs/http-parser/issues/97
1141             */
1142 0 0         assert(client->read_buf.size > parsed_len);
1143              
1144 0           http_parser_pause(parser, 0);
1145              
1146 0           parsed_len += http_parser_execute(parser,
1147 0           http_client_parser_settings(),
1148 0           client->read_buf.ptr + parsed_len,
1149             1);
1150             }
1151              
1152             /* Most failures will be reported in http_errno */
1153 0 0         else if (parser->http_errno != HPE_OK) {
1154 0           git_error_set(GIT_ERROR_HTTP, "http parser error: %s",
1155             http_errno_description(http_errno));
1156 0           return -1;
1157             }
1158              
1159             /* Otherwise we should have consumed the entire buffer. */
1160 0 0         else if (parsed_len != client->read_buf.size) {
1161 0           git_error_set(GIT_ERROR_HTTP,
1162             "http parser did not consume entire buffer: %s",
1163             http_errno_description(http_errno));
1164 0           return -1;
1165             }
1166              
1167             /* recv returned 0, the server hung up on us */
1168 0 0         else if (!parsed_len) {
1169 0           git_error_set(GIT_ERROR_HTTP, "unexpected EOF");
1170 0           return -1;
1171             }
1172              
1173 0           git_buf_consume_bytes(&client->read_buf, parsed_len);
1174              
1175 0           return (int)parsed_len;
1176             }
1177              
1178             /*
1179             * See if we've consumed the entire response body. If the client was
1180             * reading the body but did not consume it entirely, it's possible that
1181             * they knew that the stream had finished (in a git response, seeing a
1182             * final flush) and stopped reading. But if the response was chunked,
1183             * we may have not consumed the final chunk marker. Consume it to
1184             * ensure that we don't have it waiting in our socket. If there's
1185             * more than just a chunk marker, close the connection.
1186             */
1187 0           static void complete_response_body(git_http_client *client)
1188             {
1189 0           http_parser_context parser_context = {0};
1190              
1191             /* If we're not keeping alive, don't bother. */
1192 0 0         if (!client->keepalive) {
1193 0           client->connected = 0;
1194 0           return;
1195             }
1196              
1197 0           parser_context.client = client;
1198 0           client->parser.data = &parser_context;
1199              
1200             /* If there was an error, just close the connection. */
1201 0 0         if (client_read_and_parse(client) < 0 ||
    0          
1202 0 0         parser_context.error != HPE_OK ||
1203 0 0         (parser_context.parse_status != PARSE_STATUS_OK &&
1204 0           parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
1205 0           git_error_clear();
1206 0           client->connected = 0;
1207             }
1208             }
1209              
1210 0           int git_http_client_send_request(
1211             git_http_client *client,
1212             git_http_request *request)
1213             {
1214 0           git_http_response response = {0};
1215 0           int error = -1;
1216              
1217 0 0         assert(client && request);
    0          
1218              
1219             /* If the client did not finish reading, clean up the stream. */
1220 0 0         if (client->state == READING_BODY)
1221 0           complete_response_body(client);
1222              
1223             /* If we're waiting for proxy auth, don't sending more requests. */
1224 0 0         if (client->state == HAS_EARLY_RESPONSE)
1225 0           return 0;
1226              
1227             if (git_trace_level() >= GIT_TRACE_DEBUG) {
1228             git_buf url = GIT_BUF_INIT;
1229             git_net_url_fmt(&url, request->url);
1230             git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s",
1231             name_for_method(request->method),
1232             url.ptr ? url.ptr : "");
1233             git_buf_dispose(&url);
1234             }
1235              
1236 0 0         if ((error = http_client_connect(client, request)) < 0 ||
    0          
1237 0 0         (error = generate_request(client, request)) < 0 ||
1238             (error = client_write_request(client)) < 0)
1239             goto done;
1240              
1241 0           client->state = SENT_REQUEST;
1242              
1243 0 0         if (request->expect_continue) {
1244 0 0         if ((error = git_http_client_read_response(&response, client)) < 0 ||
    0          
1245             (error = git_http_client_skip_body(client)) < 0)
1246             goto done;
1247              
1248 0           error = 0;
1249              
1250 0 0         if (response.status != GIT_HTTP_STATUS_CONTINUE) {
1251 0           save_early_response(client, &response);
1252 0           goto done;
1253             }
1254             }
1255              
1256 0 0         if (request->content_length || request->chunked) {
    0          
1257 0           client->state = SENDING_BODY;
1258 0           client->request_body_len = request->content_length;
1259 0           client->request_body_remain = request->content_length;
1260 0           client->request_chunked = request->chunked;
1261             }
1262              
1263 0           reset_parser(client);
1264              
1265             done:
1266 0 0         if (error == GIT_RETRY)
1267 0           error = 0;
1268              
1269 0           git_http_response_dispose(&response);
1270 0           return error;
1271             }
1272              
1273 0           bool git_http_client_has_response(git_http_client *client)
1274             {
1275 0 0         return (client->state == HAS_EARLY_RESPONSE ||
    0          
1276 0           client->state > SENT_REQUEST);
1277             }
1278              
1279 0           int git_http_client_send_body(
1280             git_http_client *client,
1281             const char *buffer,
1282             size_t buffer_len)
1283             {
1284             git_http_server *server;
1285 0           git_buf hdr = GIT_BUF_INIT;
1286             int error;
1287              
1288 0 0         assert(client);
1289              
1290             /* If we're waiting for proxy auth, don't sending more requests. */
1291 0 0         if (client->state == HAS_EARLY_RESPONSE)
1292 0           return 0;
1293              
1294 0 0         if (client->state != SENDING_BODY) {
1295 0           git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1296 0           return -1;
1297             }
1298              
1299 0 0         if (!buffer_len)
1300 0           return 0;
1301              
1302 0           server = &client->server;
1303              
1304 0 0         if (client->request_body_len) {
1305 0 0         assert(buffer_len <= client->request_body_remain);
1306              
1307 0 0         if ((error = stream_write(server, buffer, buffer_len)) < 0)
1308 0           goto done;
1309              
1310 0           client->request_body_remain -= buffer_len;
1311             } else {
1312 0 0         if ((error = git_buf_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 ||
    0          
1313 0 0         (error = stream_write(server, hdr.ptr, hdr.size)) < 0 ||
1314 0           (error = stream_write(server, buffer, buffer_len)) < 0 ||
1315             (error = stream_write(server, "\r\n", 2)) < 0)
1316             goto done;
1317             }
1318              
1319             done:
1320 0           git_buf_dispose(&hdr);
1321 0           return error;
1322             }
1323              
1324 0           static int complete_request(git_http_client *client)
1325             {
1326 0           int error = 0;
1327              
1328 0 0         assert(client && client->state == SENDING_BODY);
    0          
1329              
1330 0 0         if (client->request_body_len && client->request_body_remain) {
    0          
1331 0           git_error_set(GIT_ERROR_HTTP, "truncated write");
1332 0           error = -1;
1333 0 0         } else if (client->request_chunked) {
1334 0           error = stream_write(&client->server, "0\r\n\r\n", 5);
1335             }
1336              
1337 0           client->state = SENT_REQUEST;
1338 0           return error;
1339             }
1340              
1341 0           int git_http_client_read_response(
1342             git_http_response *response,
1343             git_http_client *client)
1344             {
1345 0           http_parser_context parser_context = {0};
1346             int error;
1347              
1348 0 0         assert(response && client);
    0          
1349              
1350 0 0         if (client->state == SENDING_BODY) {
1351 0 0         if ((error = complete_request(client)) < 0)
1352 0           goto done;
1353             }
1354              
1355 0 0         if (client->state == HAS_EARLY_RESPONSE) {
1356 0           memcpy(response, &client->early_response, sizeof(git_http_response));
1357 0           memset(&client->early_response, 0, sizeof(git_http_response));
1358 0           client->state = DONE;
1359 0           return 0;
1360             }
1361              
1362 0 0         if (client->state != SENT_REQUEST) {
1363 0           git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1364 0           error = -1;
1365 0           goto done;
1366             }
1367              
1368 0           git_http_response_dispose(response);
1369              
1370 0           git_vector_free_deep(&client->server.auth_challenges);
1371 0           git_vector_free_deep(&client->proxy.auth_challenges);
1372              
1373 0           client->state = READING_RESPONSE;
1374 0           client->keepalive = 0;
1375 0           client->parser.data = &parser_context;
1376              
1377 0           parser_context.client = client;
1378 0           parser_context.response = response;
1379              
1380 0 0         while (client->state == READING_RESPONSE) {
1381 0 0         if ((error = client_read_and_parse(client)) < 0)
1382 0           goto done;
1383             }
1384              
1385 0 0         assert(client->state == READING_BODY || client->state == DONE);
    0          
1386              
1387             done:
1388 0           git_buf_dispose(&parser_context.parse_header_name);
1389 0           git_buf_dispose(&parser_context.parse_header_value);
1390              
1391 0           return error;
1392             }
1393              
1394 0           int git_http_client_read_body(
1395             git_http_client *client,
1396             char *buffer,
1397             size_t buffer_size)
1398             {
1399 0           http_parser_context parser_context = {0};
1400 0           int error = 0;
1401              
1402 0 0         if (client->state == DONE)
1403 0           return 0;
1404              
1405 0 0         if (client->state != READING_BODY) {
1406 0           git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1407 0           return -1;
1408             }
1409              
1410             /*
1411             * Now we'll read from the socket and http_parser will pipeline the
1412             * data directly to the client.
1413             */
1414              
1415 0           parser_context.client = client;
1416 0           parser_context.output_buf = buffer;
1417 0           parser_context.output_size = buffer_size;
1418              
1419 0           client->parser.data = &parser_context;
1420              
1421             /*
1422             * Clients expect to get a non-zero amount of data from us.
1423             * With a sufficiently small buffer, one might only read a chunk
1424             * length. Loop until we actually have data to return.
1425             */
1426 0 0         while (!parser_context.output_written) {
1427 0           error = client_read_and_parse(client);
1428              
1429 0 0         if (error <= 0)
1430 0           goto done;
1431             }
1432              
1433 0 0         assert(parser_context.output_written <= INT_MAX);
1434 0           error = (int)parser_context.output_written;
1435              
1436             done:
1437 0 0         if (error < 0)
1438 0           client->connected = 0;
1439              
1440 0           return error;
1441             }
1442              
1443 0           int git_http_client_skip_body(git_http_client *client)
1444             {
1445 0           http_parser_context parser_context = {0};
1446             int error;
1447              
1448 0 0         if (client->state == DONE)
1449 0           return 0;
1450              
1451 0 0         if (client->state != READING_BODY) {
1452 0           git_error_set(GIT_ERROR_HTTP, "client is in invalid state");
1453 0           return -1;
1454             }
1455              
1456 0           parser_context.client = client;
1457 0           client->parser.data = &parser_context;
1458              
1459             do {
1460 0           error = client_read_and_parse(client);
1461              
1462 0 0         if (parser_context.error != HPE_OK ||
    0          
1463 0 0         (parser_context.parse_status != PARSE_STATUS_OK &&
1464 0           parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) {
1465 0           git_error_set(GIT_ERROR_HTTP,
1466             "unexpected data handled in callback");
1467 0           error = -1;
1468             }
1469 0 0         } while (!error);
1470              
1471 0 0         if (error < 0)
1472 0           client->connected = 0;
1473              
1474 0           return error;
1475             }
1476              
1477             /*
1478             * Create an http_client capable of communicating with the given remote
1479             * host.
1480             */
1481 0           int git_http_client_new(
1482             git_http_client **out,
1483             git_http_client_options *opts)
1484             {
1485             git_http_client *client;
1486              
1487 0 0         assert(out);
1488              
1489 0           client = git__calloc(1, sizeof(git_http_client));
1490 0 0         GIT_ERROR_CHECK_ALLOC(client);
1491              
1492 0           git_buf_init(&client->read_buf, GIT_READ_BUFFER_SIZE);
1493 0 0         GIT_ERROR_CHECK_ALLOC(client->read_buf.ptr);
1494              
1495 0 0         if (opts)
1496 0           memcpy(&client->opts, opts, sizeof(git_http_client_options));
1497              
1498 0           *out = client;
1499 0           return 0;
1500             }
1501              
1502 0           GIT_INLINE(void) http_server_close(git_http_server *server)
1503             {
1504 0 0         if (server->stream) {
1505 0           git_stream_close(server->stream);
1506 0           git_stream_free(server->stream);
1507 0           server->stream = NULL;
1508             }
1509              
1510 0           git_net_url_dispose(&server->url);
1511              
1512 0           git_vector_free_deep(&server->auth_challenges);
1513 0           free_auth_context(server);
1514 0           }
1515              
1516 0           static void http_client_close(git_http_client *client)
1517             {
1518 0           http_server_close(&client->server);
1519 0           http_server_close(&client->proxy);
1520              
1521 0           git_buf_dispose(&client->request_msg);
1522              
1523 0           client->state = 0;
1524 0           client->request_count = 0;
1525 0           client->connected = 0;
1526 0           client->keepalive = 0;
1527 0           }
1528              
1529 0           void git_http_client_free(git_http_client *client)
1530             {
1531 0 0         if (!client)
1532 0           return;
1533              
1534 0           http_client_close(client);
1535 0           git_buf_dispose(&client->read_buf);
1536 0           git__free(client);
1537             }