2 * Copyright (C) 2009-2012 the libgit2 contributors
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.
9 #include "http_parser.h"
11 #include "transport.h"
20 #include "repository.h"
24 # pragma comment(lib, "winhttp.lib")
27 #define WIDEN2(s) L ## s
28 #define WIDEN(s) WIDEN2(s)
38 http_parser_settings settings
;
41 int transfer_finished
:1,
63 static int gen_request(git_buf
*buf
, const char *path
, const char *host
, const char *op
,
64 const char *service
, ssize_t content_length
, int ls
)
66 if (path
== NULL
) /* Is 'git fetch http://host.com/' valid? */
70 git_buf_printf(buf
, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op
, path
, service
);
72 git_buf_printf(buf
, "%s %s/git-%s HTTP/1.1\r\n", op
, path
, service
);
74 git_buf_puts(buf
, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION
")\r\n");
75 git_buf_printf(buf
, "Host: %s\r\n", host
);
76 if (content_length
> 0) {
77 git_buf_printf(buf
, "Accept: application/x-git-%s-result\r\n", service
);
78 git_buf_printf(buf
, "Content-Type: application/x-git-%s-request\r\n", service
);
79 git_buf_printf(buf
, "Content-Length: %"PRIuZ
"\r\n", content_length
);
81 git_buf_puts(buf
, "Accept: */*\r\n");
83 git_buf_puts(buf
, "\r\n");
91 static int send_request(transport_http
*t
, const char *service
, void *data
, ssize_t content_length
, int ls
)
94 git_buf request
= GIT_BUF_INIT
;
98 verb
= ls
? "GET" : "POST";
99 /* Generate and send the HTTP request */
100 if (gen_request(&request
, t
->path
, t
->host
, verb
, service
, content_length
, ls
) < 0) {
101 giterr_set(GITERR_NET
, "Failed to generate request");
106 if (gitno_send((git_transport
*) t
, request
.ptr
, request
.size
, 0) < 0)
109 if (content_length
) {
110 if (gitno_send((git_transport
*) t
, data
, content_length
, 0) < 0)
117 git_buf_free(&request
);
122 wchar_t url
[GIT_WIN_PATH
], ct
[GIT_WIN_PATH
];
123 git_buf buf
= GIT_BUF_INIT
;
132 verb
= ls
? L
"GET" : L
"POST";
133 buffer
= data
? data
: WINHTTP_NO_REQUEST_DATA
;
134 flags
= t
->parent
.use_ssl
? WINHTTP_FLAG_SECURE
: 0;
137 git_buf_printf(&buf
, "%s/info/refs?service=git-%s", t
->path
, service
);
139 git_buf_printf(&buf
, "%s/git-%s", t
->path
, service
);
141 if (git_buf_oom(&buf
))
144 git__utf8_to_16(url
, GIT_WIN_PATH
, git_buf_cstr(&buf
));
146 t
->request
= WinHttpOpenRequest(t
->connection
, verb
, url
, NULL
, WINHTTP_NO_REFERER
, types
, flags
);
147 if (t
->request
== NULL
) {
149 giterr_set(GITERR_OS
, "Failed to open request");
154 if (git_buf_printf(&buf
, "Content-Type: application/x-git-%s-request", service
) < 0)
157 git__utf8_to_16(ct
, GIT_WIN_PATH
, git_buf_cstr(&buf
));
159 if (WinHttpAddRequestHeaders(t
->request
, ct
, (ULONG
) -1L, WINHTTP_ADDREQ_FLAG_ADD
) == FALSE
) {
160 giterr_set(GITERR_OS
, "Failed to add a header to the request");
164 if (!t
->parent
.check_cert
) {
165 int flags
= SECURITY_FLAG_IGNORE_CERT_CN_INVALID
| SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
| SECURITY_FLAG_IGNORE_UNKNOWN_CA
;
166 if (WinHttpSetOption(t
->request
, WINHTTP_OPTION_SECURITY_FLAGS
, &flags
, sizeof(flags
)) == FALSE
) {
167 giterr_set(GITERR_OS
, "Failed to set options to ignore cert errors");
172 if (WinHttpSendRequest(t
->request
, WINHTTP_NO_ADDITIONAL_HEADERS
, 0,
173 data
, (DWORD
)content_length
, (DWORD
)content_length
, 0) == FALSE
) {
174 giterr_set(GITERR_OS
, "Failed to send request");
178 ret
= WinHttpReceiveResponse(t
->request
, NULL
);
180 giterr_set(GITERR_OS
, "Failed to receive response");
189 WinHttpCloseHandle(t
->request
);
195 static int do_connect(transport_http
*t
)
198 if (t
->parent
.connected
&& http_should_keep_alive(&t
->parser
))
201 if (gitno_connect((git_transport
*) t
, t
->host
, t
->port
) < 0)
204 t
->parent
.connected
= 1;
208 wchar_t *ua
= L
"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION
) L
")";
209 wchar_t host
[GIT_WIN_PATH
];
212 t
->session
= WinHttpOpen(ua
, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY
,
213 WINHTTP_NO_PROXY_NAME
, WINHTTP_NO_PROXY_BYPASS
, 0);
215 if (t
->session
== NULL
) {
216 giterr_set(GITERR_OS
, "Failed to init WinHTTP");
220 git__utf8_to_16(host
, GIT_WIN_PATH
, t
->host
);
222 if (git__strtol32(&port
, t
->port
, NULL
, 10) < 0)
225 t
->connection
= WinHttpConnect(t
->session
, host
, port
, 0);
226 if (t
->connection
== NULL
) {
227 giterr_set(GITERR_OS
, "Failed to connect to host");
231 t
->parent
.connected
= 1;
236 WinHttpCloseHandle(t
->session
);
244 * The HTTP parser is streaming, so we need to wait until we're in the
245 * field handler before we can be sure that we can store the previous
246 * value. Right now, we only care about the
247 * Content-Type. on_header_{field,value} should be kept generic enough
248 * to work for any request.
251 static const char *typestr
= "Content-Type";
253 static int on_header_field(http_parser
*parser
, const char *str
, size_t len
)
255 transport_http
*t
= (transport_http
*) parser
->data
;
256 git_buf
*buf
= &t
->buf
;
258 if (t
->last_cb
== VALUE
&& t
->ct_found
) {
261 t
->content_type
= git__strdup(git_buf_cstr(buf
));
262 GITERR_CHECK_ALLOC(t
->content_type
);
271 if (t
->last_cb
!= FIELD
)
274 git_buf_put(buf
, str
, len
);
277 return git_buf_oom(buf
);
280 static int on_header_value(http_parser
*parser
, const char *str
, size_t len
)
282 transport_http
*t
= (transport_http
*) parser
->data
;
283 git_buf
*buf
= &t
->buf
;
285 if (t
->ct_finished
) {
290 if (t
->last_cb
== VALUE
)
291 git_buf_put(buf
, str
, len
);
293 if (t
->last_cb
== FIELD
&& !strcmp(git_buf_cstr(buf
), typestr
)) {
296 git_buf_put(buf
, str
, len
);
301 return git_buf_oom(buf
);
304 static int on_headers_complete(http_parser
*parser
)
306 transport_http
*t
= (transport_http
*) parser
->data
;
307 git_buf
*buf
= &t
->buf
;
309 /* The content-type is text/plain for 404, so don't validate */
310 if (parser
->status_code
== 404) {
315 if (t
->content_type
== NULL
) {
316 t
->content_type
= git__strdup(git_buf_cstr(buf
));
317 if (t
->content_type
== NULL
)
318 return t
->error
= -1;
322 git_buf_printf(buf
, "application/x-git-%s-advertisement", t
->service
);
323 if (git_buf_oom(buf
))
324 return t
->error
= -1;
326 if (strcmp(t
->content_type
, git_buf_cstr(buf
)))
327 return t
->error
= -1;
333 static int on_message_complete(http_parser
*parser
)
335 transport_http
*t
= (transport_http
*) parser
->data
;
337 t
->transfer_finished
= 1;
339 if (parser
->status_code
== 404) {
340 giterr_set(GITERR_NET
, "Remote error: %s", git_buf_cstr(&t
->buf
));
347 static int on_body_fill_buffer(http_parser
*parser
, const char *str
, size_t len
)
349 git_transport
*transport
= (git_transport
*) parser
->data
;
350 transport_http
*t
= (transport_http
*) parser
->data
;
351 gitno_buffer
*buf
= &transport
->buffer
;
353 if (buf
->len
- buf
->offset
< len
) {
354 giterr_set(GITERR_NET
, "Can't fit data in the buffer");
355 return t
->error
= -1;
358 memcpy(buf
->data
+ buf
->offset
, str
, len
);
364 static int http_recv_cb(gitno_buffer
*buf
)
366 git_transport
*transport
= (git_transport
*) buf
->cb_data
;
367 transport_http
*t
= (transport_http
*) transport
;
377 if (t
->transfer_finished
)
381 gitno_buffer_setup(transport
, &inner
, buffer
, sizeof(buffer
));
383 if ((error
= gitno_recv(&inner
)) < 0)
386 old_len
= buf
->offset
;
387 http_parser_execute(&t
->parser
, &t
->settings
, inner
.data
, inner
.offset
);
391 old_len
= buf
->offset
;
392 if (WinHttpReadData(t
->request
, buffer
, sizeof(buffer
), &recvd
) == FALSE
) {
393 giterr_set(GITERR_OS
, "Failed to read data from the network");
394 return t
->error
= -1;
397 if (buf
->len
- buf
->offset
< recvd
) {
398 giterr_set(GITERR_NET
, "Can't fit data in the buffer");
399 return t
->error
= -1;
402 memcpy(buf
->data
+ buf
->offset
, buffer
, recvd
);
403 buf
->offset
+= recvd
;
406 return (int)(buf
->offset
- old_len
);
409 /* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */
410 static void setup_gitno_buffer(git_transport
*transport
)
412 transport_http
*t
= (transport_http
*) transport
;
414 /* WinHTTP takes care of this for us */
416 http_parser_init(&t
->parser
, HTTP_RESPONSE
);
418 t
->transfer_finished
= 0;
419 memset(&t
->settings
, 0x0, sizeof(http_parser_settings
));
420 t
->settings
.on_header_field
= on_header_field
;
421 t
->settings
.on_header_value
= on_header_value
;
422 t
->settings
.on_headers_complete
= on_headers_complete
;
423 t
->settings
.on_body
= on_body_fill_buffer
;
424 t
->settings
.on_message_complete
= on_message_complete
;
427 gitno_buffer_setup_callback(transport
, &transport
->buffer
, t
->buffer
, sizeof(t
->buffer
), http_recv_cb
, t
);
430 static int http_connect(git_transport
*transport
, int direction
)
432 transport_http
*t
= (transport_http
*) transport
;
434 git_buf request
= GIT_BUF_INIT
;
435 const char *service
= "upload-pack";
436 const char *url
= t
->parent
.url
, *prefix_http
= "http://", *prefix_https
= "https://";
437 const char *default_port
;
440 if (direction
== GIT_DIR_PUSH
) {
441 giterr_set(GITERR_NET
, "Pushing over HTTP is not implemented");
445 t
->parent
.direction
= direction
;
447 if (!git__prefixcmp(url
, prefix_http
)) {
448 url
= t
->parent
.url
+ strlen(prefix_http
);
452 if (!git__prefixcmp(url
, prefix_https
)) {
453 url
+= strlen(prefix_https
);
454 default_port
= "443";
457 t
->path
= strchr(url
, '/');
459 if ((ret
= gitno_extract_host_and_port(&t
->host
, &t
->port
, url
, default_port
)) < 0)
462 t
->service
= git__strdup(service
);
463 GITERR_CHECK_ALLOC(t
->service
);
465 if ((ret
= do_connect(t
)) < 0)
468 if ((ret
= send_request(t
, "upload-pack", NULL
, 0, 1)) < 0)
471 setup_gitno_buffer(transport
);
472 if ((ret
= git_protocol_store_refs(transport
, 2)) < 0)
475 pkt
= git_vector_get(&transport
->refs
, 0);
476 if (pkt
== NULL
|| pkt
->type
!= GIT_PKT_COMMENT
) {
477 giterr_set(GITERR_NET
, "Invalid HTTP response");
478 return t
->error
= -1;
480 /* Remove the comment pkt from the list */
481 git_vector_remove(&transport
->refs
, 0);
485 if (git_protocol_detect_caps(git_vector_get(&transport
->refs
, 0), &transport
->caps
) < 0)
486 return t
->error
= -1;
489 git_buf_free(&request
);
490 git_buf_clear(&t
->buf
);
495 static int http_negotiation_step(struct git_transport
*transport
, void *data
, size_t len
)
497 transport_http
*t
= (transport_http
*) transport
;
500 /* First, send the data as a HTTP POST request */
501 if ((ret
= do_connect(t
)) < 0)
504 if (send_request(t
, "upload-pack", data
, len
, 0) < 0)
507 /* Then we need to set up the buffer to grab data from the HTTP response */
508 setup_gitno_buffer(transport
);
513 static int http_close(git_transport
*transport
)
516 if (gitno_ssl_teardown(transport
) < 0)
519 if (gitno_close(transport
->socket
) < 0) {
520 giterr_set(GITERR_OS
, "Failed to close the socket: %s", strerror(errno
));
524 transport_http
*t
= (transport_http
*) transport
;
527 WinHttpCloseHandle(t
->request
);
529 WinHttpCloseHandle(t
->connection
);
531 WinHttpCloseHandle(t
->session
);
534 transport
->connected
= 0;
540 static void http_free(git_transport
*transport
)
542 transport_http
*t
= (transport_http
*) transport
;
543 git_vector
*refs
= &transport
->refs
;
544 git_vector
*common
= &transport
->common
;
549 /* cleanup the WSA context. note that this context
550 * can be initialized more than once with WSAStartup(),
551 * and needs to be cleaned one time for each init call
556 git_vector_foreach(refs
, i
, p
) {
559 git_vector_free(refs
);
560 git_vector_foreach(common
, i
, p
) {
563 git_vector_free(common
);
564 git_buf_free(&t
->buf
);
565 git__free(t
->content_type
);
568 git__free(t
->service
);
569 git__free(t
->parent
.url
);
573 int git_transport_http(git_transport
**out
)
577 t
= git__malloc(sizeof(transport_http
));
578 GITERR_CHECK_ALLOC(t
);
580 memset(t
, 0x0, sizeof(transport_http
));
582 t
->parent
.connect
= http_connect
;
583 t
->parent
.negotiation_step
= http_negotiation_step
;
584 t
->parent
.close
= http_close
;
585 t
->parent
.free
= http_free
;
588 if (git_vector_init(&t
->parent
.refs
, 16, NULL
) < 0) {
594 /* on win32, the WSA context needs to be initialized
595 * before any socket calls can be performed */
596 if (WSAStartup(MAKEWORD(2,2), &t
->wsd
) != 0) {
597 http_free((git_transport
*) t
);
598 giterr_set(GITERR_OS
, "Winsock init failed");
603 *out
= (git_transport
*) t
;
607 int git_transport_https(git_transport
**out
)
609 #if defined(GIT_SSL) || defined(GIT_WINHTTP)
611 if (git_transport_http((git_transport
**)&t
) < 0)
614 t
->parent
.use_ssl
= 1;
615 t
->parent
.check_cert
= 1;
616 *out
= (git_transport
*) t
;
622 giterr_set(GITERR_NET
, "HTTPS support not available");