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