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