source src/transports/http.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 | - | |||
10 | - | #ifndef GIT_WINHTTP | ||
11 | - | |||
12 | - | #include "git2.h" | ||
13 | - | #include "http_parser.h" | ||
14 | - | #include "buffer.h" | ||
15 | - | #include "net.h" | ||
16 | - | #include "netops.h" | ||
17 | - | #include "global.h" | ||
18 | - | #include "remote.h" | ||
19 | - | #include "git2/sys/credential.h" | ||
20 | - | #include "smart.h" | ||
21 | - | #include "auth.h" | ||
22 | - | #include "http.h" | ||
23 | - | #include "auth_negotiate.h" | ||
24 | - | #include "auth_ntlm.h" | ||
25 | - | #include "trace.h" | ||
26 | - | #include "streams/tls.h" | ||
27 | - | #include "streams/socket.h" | ||
28 | - | #include "httpclient.h" | ||
29 | - | |||
30 | - | bool git_http__expect_continue = false; | ||
31 | - | |||
32 | - | typedef enum { | ||
33 | - | HTTP_STATE_NONE = 0, | ||
34 | - | HTTP_STATE_SENDING_REQUEST, | ||
35 | - | HTTP_STATE_RECEIVING_RESPONSE, | ||
36 | - | HTTP_STATE_DONE | ||
37 | - | } http_state; | ||
38 | - | |||
39 | - | typedef struct { | ||
40 | - | git_http_method method; | ||
41 | - | const char *url; | ||
42 | - | const char *request_type; | ||
43 | - | const char *response_type; | ||
44 | - | unsigned chunked : 1; | ||
45 | - | } http_service; | ||
46 | - | |||
47 | - | typedef struct { | ||
48 | - | git_smart_subtransport_stream parent; | ||
49 | - | const http_service *service; | ||
50 | - | http_state state; | ||
51 | - | unsigned replay_count; | ||
52 | - | } http_stream; | ||
53 | - | |||
54 | - | typedef struct { | ||
55 | - | git_net_url url; | ||
56 | - | |||
57 | - | git_credential *cred; | ||
58 | - | unsigned auth_schemetypes; | ||
59 | - | unsigned url_cred_presented : 1; | ||
60 | - | } http_server; | ||
61 | - | |||
62 | - | typedef struct { | ||
63 | - | git_smart_subtransport parent; | ||
64 | - | transport_smart *owner; | ||
65 | - | |||
66 | - | http_server server; | ||
67 | - | http_server proxy; | ||
68 | - | |||
69 | - | git_http_client *http_client; | ||
70 | - | } http_subtransport; | ||
71 | - | |||
72 | - | static const http_service upload_pack_ls_service = { | ||
73 | - | GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack", | ||
74 | - | NULL, | ||
75 | - | "application/x-git-upload-pack-advertisement", | ||
76 | - | 0 | ||
77 | - | }; | ||
78 | - | static const http_service upload_pack_service = { | ||
79 | - | GIT_HTTP_METHOD_POST, "/git-upload-pack", | ||
80 | - | "application/x-git-upload-pack-request", | ||
81 | - | "application/x-git-upload-pack-result", | ||
82 | - | 0 | ||
83 | - | }; | ||
84 | - | static const http_service receive_pack_ls_service = { | ||
85 | - | GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack", | ||
86 | - | NULL, | ||
87 | - | "application/x-git-receive-pack-advertisement", | ||
88 | - | 0 | ||
89 | - | }; | ||
90 | - | static const http_service receive_pack_service = { | ||
91 | - | GIT_HTTP_METHOD_POST, "/git-receive-pack", | ||
92 | - | "application/x-git-receive-pack-request", | ||
93 | - | "application/x-git-receive-pack-result", | ||
94 | - | 1 | ||
95 | - | }; | ||
96 | - | |||
97 | - | #define SERVER_TYPE_REMOTE "remote" | ||
98 | - | #define SERVER_TYPE_PROXY "proxy" | ||
99 | - | |||
100 | - | #define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) | ||
101 | - | |||
102 | 1 | 2 | static int apply_url_credentials( | |
103 | - | git_credential **cred, | ||
104 | - | unsigned int allowed_types, | ||
105 | - | const char *username, | ||
106 | - | const char *password) | ||
107 | - | { | ||
108 | 1 | 2 | if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) | |
109 | 1 | 3 | return git_credential_userpass_plaintext_new(cred, username, password); | |
110 | - | |||
111 | ##### | 4-6 | if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') | |
112 | ##### | 7 | return git_credential_default_new(cred); | |
113 | - | |||
114 | ##### | 8 | return GIT_PASSTHROUGH; | |
115 | - | } | ||
116 | - | |||
117 | 328 | 2 | GIT_INLINE(void) free_cred(git_credential **cred) | |
118 | - | { | ||
119 | 328 | 2 | if (*cred) { | |
120 | 6 | 3 | git_credential_free(*cred); | |
121 | 6 | 4 | (*cred) = NULL; | |
122 | - | } | ||
123 | 328 | 5 | } | |
124 | - | |||
125 | 10 | 2 | static int handle_auth( | |
126 | - | http_server *server, | ||
127 | - | const char *server_type, | ||
128 | - | const char *url, | ||
129 | - | unsigned int allowed_schemetypes, | ||
130 | - | unsigned int allowed_credtypes, | ||
131 | - | git_credential_acquire_cb callback, | ||
132 | - | void *callback_payload) | ||
133 | - | { | ||
134 | 10 | 2 | int error = 1; | |
135 | - | |||
136 | 10 | 2 | if (server->cred) | |
137 | 4 | 3 | free_cred(&server->cred); | |
138 | - | |||
139 | - | /* Start with URL-specified credentials, if there were any. */ | ||
140 | 10 | 4,5 | if ((allowed_credtypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT) && | |
141 | 10 | 5,6 | !server->url_cred_presented && | |
142 | 10 | 6,7 | server->url.username && | |
143 | 2 | 7 | server->url.password) { | |
144 | 1 | 8 | error = apply_url_credentials(&server->cred, allowed_credtypes, server->url.username, server->url.password); | |
145 | 1 | 9 | server->url_cred_presented = 1; | |
146 | - | |||
147 | - | /* treat GIT_PASSTHROUGH as if callback isn't set */ | ||
148 | 1 | 9 | if (error == GIT_PASSTHROUGH) | |
149 | ##### | 10 | error = 1; | |
150 | - | } | ||
151 | - | |||
152 | 10 | 11,12 | if (error > 0 && callback) { | |
153 | 9 | 13 | error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload); | |
154 | - | |||
155 | - | /* treat GIT_PASSTHROUGH as if callback isn't set */ | ||
156 | 9 | 14 | if (error == GIT_PASSTHROUGH) | |
157 | ##### | 15 | error = 1; | |
158 | - | } | ||
159 | - | |||
160 | 10 | 16 | if (error > 0) { | |
161 | ##### | 17 | git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); | |
162 | ##### | 18 | error = -1; | |
163 | - | } | ||
164 | - | |||
165 | 10 | 19 | if (!error) | |
166 | 6 | 20 | server->auth_schemetypes = allowed_schemetypes; | |
167 | - | |||
168 | 10 | 21 | return error; | |
169 | - | } | ||
170 | - | |||
171 | 10 | 2 | GIT_INLINE(int) handle_remote_auth( | |
172 | - | http_stream *stream, | ||
173 | - | git_http_response *response) | ||
174 | - | { | ||
175 | 10 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
176 | - | |||
177 | 10 | 2 | if (response->server_auth_credtypes == 0) { | |
178 | ##### | 3 | git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); | |
179 | ##### | 4 | return -1; | |
180 | - | } | ||
181 | - | |||
182 | - | /* Otherwise, prompt for credentials. */ | ||
183 | 10 | 5,5,5 | return handle_auth( | |
184 | - | &transport->server, | ||
185 | - | SERVER_TYPE_REMOTE, | ||
186 | 10 | 5 | transport->owner->url, | |
187 | - | response->server_auth_schemetypes, | ||
188 | - | response->server_auth_credtypes, | ||
189 | 10 | 5 | transport->owner->cred_acquire_cb, | |
190 | 10 | 5 | transport->owner->cred_acquire_payload); | |
191 | - | } | ||
192 | - | |||
193 | ##### | 2 | GIT_INLINE(int) handle_proxy_auth( | |
194 | - | http_stream *stream, | ||
195 | - | git_http_response *response) | ||
196 | - | { | ||
197 | ##### | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
198 | - | |||
199 | ##### | 2 | if (response->proxy_auth_credtypes == 0) { | |
200 | ##### | 3 | git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); | |
201 | ##### | 4 | return -1; | |
202 | - | } | ||
203 | - | |||
204 | - | /* Otherwise, prompt for credentials. */ | ||
205 | ##### | 5,5,5 | return handle_auth( | |
206 | - | &transport->proxy, | ||
207 | - | SERVER_TYPE_PROXY, | ||
208 | ##### | 5 | transport->owner->proxy.url, | |
209 | - | response->server_auth_schemetypes, | ||
210 | - | response->proxy_auth_credtypes, | ||
211 | ##### | 5 | transport->owner->proxy.credentials, | |
212 | ##### | 5 | transport->owner->proxy.payload); | |
213 | - | } | ||
214 | - | |||
215 | - | |||
216 | 85 | 2 | static int handle_response( | |
217 | - | bool *complete, | ||
218 | - | http_stream *stream, | ||
219 | - | git_http_response *response, | ||
220 | - | bool allow_replay) | ||
221 | - | { | ||
222 | 85 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
223 | - | int error; | ||
224 | - | |||
225 | 85 | 2 | *complete = false; | |
226 | - | |||
227 | 85 | 2-4 | if (allow_replay && git_http_response_is_redirect(response)) { | |
228 | 20 | 5 | if (!response->location) { | |
229 | ##### | 6 | git_error_set(GIT_ERROR_HTTP, "redirect without location"); | |
230 | ##### | 7 | return -1; | |
231 | - | } | ||
232 | - | |||
233 | 20 | 8,9 | if (git_net_url_apply_redirect(&transport->server.url, response->location, stream->service->url) < 0) { | |
234 | ##### | 10 | return -1; | |
235 | - | } | ||
236 | - | |||
237 | 20 | 11 | return 0; | |
238 | 65 | 12,13 | } else if (git_http_response_is_redirect(response)) { | |
239 | ##### | 14 | git_error_set(GIT_ERROR_HTTP, "unexpected redirect"); | |
240 | ##### | 15 | return -1; | |
241 | - | } | ||
242 | - | |||
243 | - | /* If we're in the middle of challenge/response auth, continue. */ | ||
244 | 65 | 16,17 | if (allow_replay && response->resend_credentials) { | |
245 | ##### | 18 | return 0; | |
246 | 65 | 19,20 | } else if (allow_replay && response->status == GIT_HTTP_STATUS_UNAUTHORIZED) { | |
247 | 10 | 21,22 | if ((error = handle_remote_auth(stream, response)) < 0) | |
248 | 4 | 23 | return error; | |
249 | - | |||
250 | 6 | 24 | return git_http_client_skip_body(transport->http_client); | |
251 | 55 | 25,26 | } else if (allow_replay && response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { | |
252 | ##### | 27,28 | if ((error = handle_proxy_auth(stream, response)) < 0) | |
253 | ##### | 29 | return error; | |
254 | - | |||
255 | ##### | 30 | return git_http_client_skip_body(transport->http_client); | |
256 | 55 | 31,32 | } else if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED || | |
257 | 55 | 32 | response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { | |
258 | ##### | 33 | git_error_set(GIT_ERROR_HTTP, "unexpected authentication failure"); | |
259 | ##### | 34 | return -1; | |
260 | - | } | ||
261 | - | |||
262 | 55 | 35 | if (response->status != GIT_HTTP_STATUS_OK) { | |
263 | ##### | 36 | git_error_set(GIT_ERROR_HTTP, "unexpected http status code: %d", response->status); | |
264 | ##### | 37 | return -1; | |
265 | - | } | ||
266 | - | |||
267 | - | /* The response must contain a Content-Type header. */ | ||
268 | 55 | 38 | if (!response->content_type) { | |
269 | ##### | 39 | git_error_set(GIT_ERROR_HTTP, "no content-type header in response"); | |
270 | ##### | 40 | return -1; | |
271 | - | } | ||
272 | - | |||
273 | - | /* The Content-Type header must match our expectation. */ | ||
274 | 55 | 41 | if (strcmp(response->content_type, stream->service->response_type) != 0) { | |
275 | ##### | 42 | git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type); | |
276 | ##### | 43 | return -1; | |
277 | - | } | ||
278 | - | |||
279 | 55 | 44 | *complete = true; | |
280 | 55 | 44 | stream->state = HTTP_STATE_RECEIVING_RESPONSE; | |
281 | 55 | 44 | return 0; | |
282 | - | } | ||
283 | - | |||
284 | 92 | 2 | static int lookup_proxy( | |
285 | - | bool *out_use, | ||
286 | - | http_subtransport *transport) | ||
287 | - | { | ||
288 | - | const char *proxy; | ||
289 | - | git_remote *remote; | ||
290 | - | bool use_ssl; | ||
291 | 92 | 2 | char *config = NULL; | |
292 | 92 | 2 | int error = 0; | |
293 | - | |||
294 | 92 | 2 | *out_use = false; | |
295 | 92 | 2 | git_net_url_dispose(&transport->proxy.url); | |
296 | - | |||
297 | 92 | 3 | switch (transport->owner->proxy.type) { | |
298 | - | case GIT_PROXY_SPECIFIED: | ||
299 | ##### | 4 | proxy = transport->owner->proxy.url; | |
300 | ##### | 4 | break; | |
301 | - | |||
302 | - | case GIT_PROXY_AUTO: | ||
303 | 6 | 5 | remote = transport->owner->owner; | |
304 | 6 | 5 | use_ssl = !strcmp(transport->server.url.scheme, "https"); | |
305 | - | |||
306 | 6 | 5 | error = git_remote__get_http_proxy(remote, use_ssl, &config); | |
307 | - | |||
308 | 6 | 6,7 | if (error || !config) | |
309 | - | goto done; | ||
310 | - | |||
311 | ##### | 8 | proxy = config; | |
312 | ##### | 8 | break; | |
313 | - | |||
314 | - | default: | ||
315 | 86 | 9 | return 0; | |
316 | - | } | ||
317 | - | |||
318 | ##### | 10-12 | if (!proxy || | |
319 | ##### | 11 | (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) | |
320 | - | goto done; | ||
321 | - | |||
322 | ##### | 13 | *out_use = true; | |
323 | - | |||
324 | - | done: | ||
325 | 6 | 14 | git__free(config); | |
326 | 6 | 15 | return error; | |
327 | - | } | ||
328 | - | |||
329 | 92 | 2 | static int generate_request( | |
330 | - | git_net_url *url, | ||
331 | - | git_http_request *request, | ||
332 | - | http_stream *stream, | ||
333 | - | size_t len) | ||
334 | - | { | ||
335 | 92 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
336 | 92 | 2 | bool use_proxy = false; | |
337 | - | int error; | ||
338 | - | |||
339 | 92 | 2,3 | if ((error = git_net_url_joinpath(url, | |
340 | 92 | 2,4,5 | &transport->server.url, stream->service->url)) < 0 || | |
341 | - | (error = lookup_proxy(&use_proxy, transport)) < 0) | ||
342 | ##### | 6 | return error; | |
343 | - | |||
344 | 92 | 7 | request->method = stream->service->method; | |
345 | 92 | 7 | request->url = url; | |
346 | 92 | 7 | request->credentials = transport->server.cred; | |
347 | 92 | 7-9 | request->proxy = use_proxy ? &transport->proxy.url : NULL; | |
348 | 92 | 10 | request->proxy_credentials = transport->proxy.cred; | |
349 | 92 | 10 | request->custom_headers = &transport->owner->custom_headers; | |
350 | - | |||
351 | 92 | 10 | if (stream->service->method == GIT_HTTP_METHOD_POST) { | |
352 | 25 | 11 | request->chunked = stream->service->chunked; | |
353 | 25 | 11-13 | request->content_length = stream->service->chunked ? 0 : len; | |
354 | 25 | 14 | request->content_type = stream->service->request_type; | |
355 | 25 | 14 | request->accept = stream->service->response_type; | |
356 | 25 | 14 | request->expect_continue = git_http__expect_continue; | |
357 | - | } | ||
358 | - | |||
359 | 92 | 15 | return 0; | |
360 | - | } | ||
361 | - | |||
362 | - | /* | ||
363 | - | * Read from an HTTP transport - for the first invocation of this function | ||
364 | - | * (ie, when stream->state == HTTP_STATE_NONE), we'll send a GET request | ||
365 | - | * to the remote host. We will stream that data back on all subsequent | ||
366 | - | * calls. | ||
367 | - | */ | ||
368 | 97 | 2 | static int http_stream_read( | |
369 | - | git_smart_subtransport_stream *s, | ||
370 | - | char *buffer, | ||
371 | - | size_t buffer_size, | ||
372 | - | size_t *out_len) | ||
373 | - | { | ||
374 | 97 | 2 | http_stream *stream = (http_stream *)s; | |
375 | 97 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
376 | 97 | 2 | git_net_url url = GIT_NET_URL_INIT; | |
377 | 97 | 2 | git_net_url proxy_url = GIT_NET_URL_INIT; | |
378 | 97 | 2 | git_http_request request = {0}; | |
379 | 97 | 2 | git_http_response response = {0}; | |
380 | - | bool complete; | ||
381 | - | int error; | ||
382 | - | |||
383 | 97 | 2 | *out_len = 0; | |
384 | - | |||
385 | 97 | 2 | if (stream->state == HTTP_STATE_NONE) { | |
386 | 41 | 3 | stream->state = HTTP_STATE_SENDING_REQUEST; | |
387 | 41 | 3 | stream->replay_count = 0; | |
388 | - | } | ||
389 | - | |||
390 | - | /* | ||
391 | - | * Formulate the URL, send the request and read the response | ||
392 | - | * headers. Some of the request body may also be read. | ||
393 | - | */ | ||
394 | 123 | 4,19,20 | while (stream->state == HTTP_STATE_SENDING_REQUEST && | |
395 | 67 | 20 | stream->replay_count < GIT_HTTP_REPLAY_MAX) { | |
396 | 67 | 5 | git_net_url_dispose(&url); | |
397 | 67 | 6 | git_net_url_dispose(&proxy_url); | |
398 | 67 | 7 | git_http_response_dispose(&response); | |
399 | - | |||
400 | 67 | 8-11 | if ((error = generate_request(&url, &request, stream, 0)) < 0 || | |
401 | 67 | 10 | (error = git_http_client_send_request( | |
402 | 60 | 12,13 | transport->http_client, &request)) < 0 || | |
403 | 60 | 12 | (error = git_http_client_read_response( | |
404 | 60 | 14,15 | &response, transport->http_client)) < 0 || | |
405 | - | (error = handle_response(&complete, stream, &response, true)) < 0) | ||
406 | - | goto done; | ||
407 | - | |||
408 | 56 | 16 | if (complete) | |
409 | 30 | 17 | break; | |
410 | - | |||
411 | 26 | 18 | stream->replay_count++; | |
412 | - | } | ||
413 | - | |||
414 | 86 | 21 | if (stream->state == HTTP_STATE_SENDING_REQUEST) { | |
415 | ##### | 22 | git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); | |
416 | ##### | 23 | error = -1; | |
417 | ##### | 23 | goto done; | |
418 | - | } | ||
419 | - | |||
420 | 86 | 24,25 | assert (stream->state == HTTP_STATE_RECEIVING_RESPONSE); | |
421 | - | |||
422 | 86 | 26 | error = git_http_client_read_body(transport->http_client, buffer, buffer_size); | |
423 | - | |||
424 | 86 | 27 | if (error > 0) { | |
425 | 86 | 28 | *out_len = error; | |
426 | 86 | 28 | error = 0; | |
427 | - | } | ||
428 | - | |||
429 | - | done: | ||
430 | 97 | 29 | git_net_url_dispose(&url); | |
431 | 97 | 30 | git_net_url_dispose(&proxy_url); | |
432 | 97 | 31 | git_http_response_dispose(&response); | |
433 | - | |||
434 | 97 | 32 | return error; | |
435 | - | } | ||
436 | - | |||
437 | 25 | 2 | static bool needs_probe(http_stream *stream) | |
438 | - | { | ||
439 | 25 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
440 | - | |||
441 | 25 | 2,3 | return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM || | |
442 | 25 | 3 | transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE); | |
443 | - | } | ||
444 | - | |||
445 | ##### | 2 | static int send_probe(http_stream *stream) | |
446 | - | { | ||
447 | ##### | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
448 | ##### | 2 | git_http_client *client = transport->http_client; | |
449 | ##### | 2 | const char *probe = "0000"; | |
450 | ##### | 2 | size_t len = 4; | |
451 | ##### | 2 | git_net_url url = GIT_NET_URL_INIT; | |
452 | ##### | 2 | git_http_request request = {0}; | |
453 | ##### | 2 | git_http_response response = {0}; | |
454 | ##### | 2 | bool complete = false; | |
455 | ##### | 2 | size_t step, steps = 1; | |
456 | - | int error; | ||
457 | - | |||
458 | - | /* NTLM requires a full challenge/response */ | ||
459 | ##### | 2 | if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM) | |
460 | ##### | 3 | steps = GIT_AUTH_STEPS_NTLM; | |
461 | - | |||
462 | - | /* | ||
463 | - | * Send at most two requests: one without any authentication to see | ||
464 | - | * if we get prompted to authenticate. If we do, send a second one | ||
465 | - | * with the first authentication message. The final authentication | ||
466 | - | * message with the response will occur with the *actual* POST data. | ||
467 | - | */ | ||
468 | ##### | 4,19-21 | for (step = 0; step < steps && !complete; step++) { | |
469 | ##### | 5 | git_net_url_dispose(&url); | |
470 | ##### | 6 | git_http_response_dispose(&response); | |
471 | - | |||
472 | ##### | 7-10 | if ((error = generate_request(&url, &request, stream, len)) < 0 || | |
473 | ##### | 11,12 | (error = git_http_client_send_request(client, &request)) < 0 || | |
474 | ##### | 13,14 | (error = git_http_client_send_body(client, probe, len)) < 0 || | |
475 | ##### | 15,16 | (error = git_http_client_read_response(&response, client)) < 0 || | |
476 | ##### | 17,18 | (error = git_http_client_skip_body(client)) < 0 || | |
477 | - | (error = handle_response(&complete, stream, &response, true)) < 0) | ||
478 | - | goto done; | ||
479 | - | } | ||
480 | - | |||
481 | - | done: | ||
482 | ##### | 22 | git_http_response_dispose(&response); | |
483 | ##### | 23 | git_net_url_dispose(&url); | |
484 | ##### | 24 | return error; | |
485 | - | } | ||
486 | - | |||
487 | - | /* | ||
488 | - | * Write to an HTTP transport - for the first invocation of this function | ||
489 | - | * (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request | ||
490 | - | * to the remote host. If we're sending chunked data, then subsequent calls | ||
491 | - | * will write the additional data given in the buffer. If we're not chunking, | ||
492 | - | * then the caller should have given us all the data in the original call. | ||
493 | - | * The caller should call http_stream_read_response to get the result. | ||
494 | - | */ | ||
495 | 25 | 2 | static int http_stream_write( | |
496 | - | git_smart_subtransport_stream *s, | ||
497 | - | const char *buffer, | ||
498 | - | size_t len) | ||
499 | - | { | ||
500 | 25 | 2 | http_stream *stream = GIT_CONTAINER_OF(s, http_stream, parent); | |
501 | 25 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
502 | 25 | 2 | git_net_url url = GIT_NET_URL_INIT; | |
503 | 25 | 2 | git_http_request request = {0}; | |
504 | 25 | 2 | git_http_response response = {0}; | |
505 | - | int error; | ||
506 | - | |||
507 | 50 | 2,25,26 | while (stream->state == HTTP_STATE_NONE && | |
508 | 25 | 26 | stream->replay_count < GIT_HTTP_REPLAY_MAX) { | |
509 | - | |||
510 | 25 | 3 | git_net_url_dispose(&url); | |
511 | 25 | 4 | git_http_response_dispose(&response); | |
512 | - | |||
513 | - | /* | ||
514 | - | * If we're authenticating with a connection-based mechanism | ||
515 | - | * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD | ||
516 | - | * authenticate an entire keep-alive connection, so ideally | ||
517 | - | * we should not need to authenticate but some servers do | ||
518 | - | * not support this. By sending a probe packet, we'll be | ||
519 | - | * able to follow up with a second POST using the actual | ||
520 | - | * data (and, in the degenerate case, the authentication | ||
521 | - | * header as well). | ||
522 | - | */ | ||
523 | 25 | 5-8 | if (needs_probe(stream) && (error = send_probe(stream)) < 0) | |
524 | ##### | 9 | goto done; | |
525 | - | |||
526 | - | /* Send the regular POST request. */ | ||
527 | 25 | 10-13 | if ((error = generate_request(&url, &request, stream, len)) < 0 || | |
528 | 25 | 12 | (error = git_http_client_send_request( | |
529 | - | transport->http_client, &request)) < 0) | ||
530 | - | goto done; | ||
531 | - | |||
532 | 25 | 14,16 | if (request.expect_continue && | |
533 | ##### | 15,21,22 | git_http_client_has_response(transport->http_client)) { | |
534 | - | bool complete; | ||
535 | - | |||
536 | - | /* | ||
537 | - | * If we got a response to an expect/continue, then | ||
538 | - | * it's something other than a 100 and we should | ||
539 | - | * deal with the response somehow. | ||
540 | - | */ | ||
541 | ##### | 17-20 | if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 || | |
542 | - | (error = handle_response(&complete, stream, &response, true)) < 0) | ||
543 | - | goto done; | ||
544 | - | } else { | ||
545 | 25 | 23 | stream->state = HTTP_STATE_SENDING_REQUEST; | |
546 | - | } | ||
547 | - | |||
548 | 25 | 24 | stream->replay_count++; | |
549 | - | } | ||
550 | - | |||
551 | 25 | 27 | if (stream->state == HTTP_STATE_NONE) { | |
552 | ##### | 28 | git_error_set(GIT_ERROR_HTTP, | |
553 | - | "too many redirects or authentication replays"); | ||
554 | ##### | 29 | error = -1; | |
555 | ##### | 29 | goto done; | |
556 | - | } | ||
557 | - | |||
558 | 25 | 30,31 | assert(stream->state == HTTP_STATE_SENDING_REQUEST); | |
559 | - | |||
560 | 25 | 32 | error = git_http_client_send_body(transport->http_client, buffer, len); | |
561 | - | |||
562 | - | done: | ||
563 | 25 | 33 | git_http_response_dispose(&response); | |
564 | 25 | 34 | git_net_url_dispose(&url); | |
565 | 25 | 35 | return error; | |
566 | - | } | ||
567 | - | |||
568 | - | /* | ||
569 | - | * Read from an HTTP transport after it has been written to. This is the | ||
570 | - | * response from a POST request made by http_stream_write. | ||
571 | - | */ | ||
572 | 445 | 2 | static int http_stream_read_response( | |
573 | - | git_smart_subtransport_stream *s, | ||
574 | - | char *buffer, | ||
575 | - | size_t buffer_size, | ||
576 | - | size_t *out_len) | ||
577 | - | { | ||
578 | 445 | 2 | http_stream *stream = (http_stream *)s; | |
579 | 445 | 2 | http_subtransport *transport = OWNING_SUBTRANSPORT(stream); | |
580 | 445 | 2 | git_http_client *client = transport->http_client; | |
581 | 445 | 2 | git_http_response response = {0}; | |
582 | - | bool complete; | ||
583 | - | int error; | ||
584 | - | |||
585 | 445 | 2 | *out_len = 0; | |
586 | - | |||
587 | 445 | 2 | if (stream->state == HTTP_STATE_SENDING_REQUEST) { | |
588 | 25 | 3-6 | if ((error = git_http_client_read_response(&response, client)) < 0 || | |
589 | - | (error = handle_response(&complete, stream, &response, false)) < 0) | ||
590 | - | goto done; | ||
591 | - | |||
592 | 25 | 7,8 | assert(complete); | |
593 | 25 | 9 | stream->state = HTTP_STATE_RECEIVING_RESPONSE; | |
594 | - | } | ||
595 | - | |||
596 | 445 | 10 | error = git_http_client_read_body(client, buffer, buffer_size); | |
597 | - | |||
598 | 445 | 11 | if (error > 0) { | |
599 | 445 | 12 | *out_len = error; | |
600 | 445 | 12 | error = 0; | |
601 | - | } | ||
602 | - | |||
603 | - | done: | ||
604 | 445 | 13 | git_http_response_dispose(&response); | |
605 | 445 | 14 | return error; | |
606 | - | } | ||
607 | - | |||
608 | 66 | 2 | static void http_stream_free(git_smart_subtransport_stream *stream) | |
609 | - | { | ||
610 | 66 | 2 | http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent); | |
611 | 66 | 2 | git__free(s); | |
612 | 66 | 3 | } | |
613 | - | |||
614 | 66 | 2 | static const http_service *select_service(git_smart_service_t action) | |
615 | - | { | ||
616 | 66 | 2 | switch (action) { | |
617 | - | case GIT_SERVICE_UPLOADPACK_LS: | ||
618 | 41 | 3 | return &upload_pack_ls_service; | |
619 | - | case GIT_SERVICE_UPLOADPACK: | ||
620 | 25 | 4 | return &upload_pack_service; | |
621 | - | case GIT_SERVICE_RECEIVEPACK_LS: | ||
622 | ##### | 5 | return &receive_pack_ls_service; | |
623 | - | case GIT_SERVICE_RECEIVEPACK: | ||
624 | ##### | 6 | return &receive_pack_service; | |
625 | - | } | ||
626 | - | |||
627 | ##### | 7 | return NULL; | |
628 | - | } | ||
629 | - | |||
630 | 67 | 2 | static int http_action( | |
631 | - | git_smart_subtransport_stream **out, | ||
632 | - | git_smart_subtransport *t, | ||
633 | - | const char *url, | ||
634 | - | git_smart_service_t action) | ||
635 | - | { | ||
636 | 67 | 2 | http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); | |
637 | - | http_stream *stream; | ||
638 | - | const http_service *service; | ||
639 | - | int error; | ||
640 | - | |||
641 | 67 | 2-4 | assert(out && t); | |
642 | - | |||
643 | 67 | 5 | *out = NULL; | |
644 | - | |||
645 | - | /* | ||
646 | - | * If we've seen a redirect then preserve the location that we've | ||
647 | - | * been given. This is important to continue authorization against | ||
648 | - | * the redirect target, not the user-given source; the endpoint may | ||
649 | - | * have redirected us from HTTP->HTTPS and is using an auth mechanism | ||
650 | - | * that would be insecure in plaintext (eg, HTTP Basic). | ||
651 | - | */ | ||
652 | 67 | 5-8 | if (!git_net_url_valid(&transport->server.url) && | |
653 | 42 | 7 | (error = git_net_url_parse(&transport->server.url, url)) < 0) | |
654 | 1 | 9 | return error; | |
655 | - | |||
656 | 66 | 10,11 | if ((service = select_service(action)) == NULL) { | |
657 | ##### | 12 | git_error_set(GIT_ERROR_HTTP, "invalid action"); | |
658 | ##### | 13 | return -1; | |
659 | - | } | ||
660 | - | |||
661 | 66 | 14 | stream = git__calloc(sizeof(http_stream), 1); | |
662 | 66 | 15,16 | GIT_ERROR_CHECK_ALLOC(stream); | |
663 | - | |||
664 | 66 | 17 | if (!transport->http_client) { | |
665 | 40 | 18 | git_http_client_options opts = {0}; | |
666 | - | |||
667 | 40 | 18 | opts.server_certificate_check_cb = transport->owner->certificate_check_cb; | |
668 | 40 | 18 | opts.server_certificate_check_payload = transport->owner->message_cb_payload; | |
669 | 40 | 18 | opts.proxy_certificate_check_cb = transport->owner->proxy.certificate_check; | |
670 | 40 | 18 | opts.proxy_certificate_check_payload = transport->owner->proxy.payload; | |
671 | - | |||
672 | 40 | 18,19 | if (git_http_client_new(&transport->http_client, &opts) < 0) | |
673 | 40 | 20,21 | return -1; | |
674 | - | } | ||
675 | - | |||
676 | 66 | 22 | stream->service = service; | |
677 | 66 | 22 | stream->parent.subtransport = &transport->parent; | |
678 | - | |||
679 | 66 | 22 | if (service->method == GIT_HTTP_METHOD_GET) { | |
680 | 41 | 23 | stream->parent.read = http_stream_read; | |
681 | - | } else { | ||
682 | 25 | 24 | stream->parent.write = http_stream_write; | |
683 | 25 | 24 | stream->parent.read = http_stream_read_response; | |
684 | - | } | ||
685 | - | |||
686 | 66 | 25 | stream->parent.free = http_stream_free; | |
687 | - | |||
688 | 66 | 25 | *out = (git_smart_subtransport_stream *)stream; | |
689 | 66 | 25 | return 0; | |
690 | - | } | ||
691 | - | |||
692 | 162 | 2 | static int http_close(git_smart_subtransport *t) | |
693 | - | { | ||
694 | 162 | 2 | http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); | |
695 | - | |||
696 | 162 | 2 | free_cred(&transport->server.cred); | |
697 | 162 | 3 | free_cred(&transport->proxy.cred); | |
698 | - | |||
699 | 162 | 4 | transport->server.url_cred_presented = false; | |
700 | 162 | 4 | transport->proxy.url_cred_presented = false; | |
701 | - | |||
702 | 162 | 4 | git_net_url_dispose(&transport->server.url); | |
703 | 162 | 5 | git_net_url_dispose(&transport->proxy.url); | |
704 | - | |||
705 | 162 | 6 | return 0; | |
706 | - | } | ||
707 | - | |||
708 | 45 | 2 | static void http_free(git_smart_subtransport *t) | |
709 | - | { | ||
710 | 45 | 2 | http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); | |
711 | - | |||
712 | 45 | 2 | git_http_client_free(transport->http_client); | |
713 | - | |||
714 | 45 | 3 | http_close(t); | |
715 | 45 | 4 | git__free(transport); | |
716 | 45 | 5 | } | |
717 | - | |||
718 | 45 | 2 | int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) | |
719 | - | { | ||
720 | - | http_subtransport *transport; | ||
721 | - | |||
722 | - | GIT_UNUSED(param); | ||
723 | - | |||
724 | 45 | 2,3 | assert(out); | |
725 | - | |||
726 | 45 | 4 | transport = git__calloc(sizeof(http_subtransport), 1); | |
727 | 45 | 5,6 | GIT_ERROR_CHECK_ALLOC(transport); | |
728 | - | |||
729 | 45 | 7 | transport->owner = (transport_smart *)owner; | |
730 | 45 | 7 | transport->parent.action = http_action; | |
731 | 45 | 7 | transport->parent.close = http_close; | |
732 | 45 | 7 | transport->parent.free = http_free; | |
733 | - | |||
734 | 45 | 7 | *out = (git_smart_subtransport *) transport; | |
735 | 45 | 7 | return 0; | |
736 | - | } | ||
737 | - | |||
738 | - | #endif /* !GIT_WINHTTP */ |