Git-Raw
view release on metacpan or search on metacpan
deps/libgit2/src/libgit2/transports/httpclient.c view on Meta::CPAN
git_vector_foreach(challenges, i, challenge) {
if ((scheme = scheme_for_challenge(challenge)) != NULL) {
*schemetypes |= scheme->type;
*credtypes |= scheme->credtypes;
}
}
}
static int resend_needed(git_http_client *client, git_http_response *response)
{
git_http_auth_context *auth_context;
if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED &&
(auth_context = client->server.auth_context) &&
auth_context->is_complete &&
!auth_context->is_complete(auth_context))
return 1;
if (response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED &&
(auth_context = client->proxy.auth_context) &&
auth_context->is_complete &&
!auth_context->is_complete(auth_context))
return 1;
return 0;
}
static int on_headers_complete(http_parser *parser)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
/* Finalize the last seen header */
switch (ctx->parse_header_state) {
case PARSE_HEADER_VALUE:
if (on_header_complete(parser) < 0)
return ctx->parse_status = PARSE_STATUS_ERROR;
/* Fall through */
case PARSE_HEADER_NONE:
ctx->parse_header_state = PARSE_HEADER_COMPLETE;
break;
default:
git_error_set(GIT_ERROR_HTTP,
"header completion at unexpected time");
return ctx->parse_status = PARSE_STATUS_ERROR;
}
ctx->response->status = parser->status_code;
ctx->client->keepalive = http_should_keep_alive(parser);
/* Prepare for authentication */
collect_authinfo(&ctx->response->server_auth_schemetypes,
&ctx->response->server_auth_credtypes,
&ctx->client->server.auth_challenges);
collect_authinfo(&ctx->response->proxy_auth_schemetypes,
&ctx->response->proxy_auth_credtypes,
&ctx->client->proxy.auth_challenges);
ctx->response->resend_credentials = resend_needed(ctx->client,
ctx->response);
/* Stop parsing. */
http_parser_pause(parser, 1);
if (ctx->response->content_type || ctx->response->chunked)
ctx->client->state = READING_BODY;
else
ctx->client->state = DONE;
return 0;
}
static int on_body(http_parser *parser, const char *buf, size_t len)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
size_t max_len;
/* Saw data when we expected not to (eg, in consume_response_body) */
if (ctx->output_buf == NULL || ctx->output_size == 0) {
ctx->parse_status = PARSE_STATUS_NO_OUTPUT;
return 0;
}
GIT_ASSERT(ctx->output_size >= ctx->output_written);
max_len = min(ctx->output_size - ctx->output_written, len);
max_len = min(max_len, INT_MAX);
memcpy(ctx->output_buf + ctx->output_written, buf, max_len);
ctx->output_written += max_len;
return 0;
}
static int on_message_complete(http_parser *parser)
{
http_parser_context *ctx = (http_parser_context *) parser->data;
ctx->client->state = DONE;
return 0;
}
GIT_INLINE(int) stream_write(
git_http_server *server,
const char *data,
size_t len)
{
git_trace(GIT_TRACE_TRACE,
"Sending request:\n%.*s", (int)len, data);
return git_stream__write_full(server->stream, data, len, 0);
}
GIT_INLINE(int) client_write_request(git_http_client *client)
{
git_stream *stream = client->current_server == PROXY ?
client->proxy.stream : client->server.stream;
git_trace(GIT_TRACE_TRACE,
"Sending request:\n%.*s",
(int)client->request_msg.size, client->request_msg.ptr);
return git_stream__write_full(stream,
client->request_msg.ptr,
client->request_msg.size,
0);
}
static const char *name_for_method(git_http_method method)
{
switch (method) {
case GIT_HTTP_METHOD_GET:
return "GET";
case GIT_HTTP_METHOD_POST:
return "POST";
case GIT_HTTP_METHOD_CONNECT:
return "CONNECT";
}
return NULL;
}
/*
* Find the scheme that is suitable for the given credentials, based on the
* server's auth challenges.
*/
static bool best_scheme_and_challenge(
git_http_auth_scheme **scheme_out,
const char **challenge_out,
git_vector *challenges,
git_credential *credentials)
{
const char *challenge;
size_t i, j;
for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
git_vector_foreach(challenges, j, challenge) {
git_http_auth_scheme *scheme = &auth_schemes[i];
if (challenge_matches_scheme(challenge, scheme) &&
(scheme->credtypes & credentials->credtype)) {
*scheme_out = scheme;
*challenge_out = challenge;
return true;
}
}
}
return false;
}
/*
* Find the challenge from the server for our current auth context.
*/
static const char *challenge_for_context(
git_vector *challenges,
git_http_auth_context *auth_ctx)
{
const char *challenge;
size_t i, j;
for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
if (auth_schemes[i].type == auth_ctx->type) {
git_http_auth_scheme *scheme = &auth_schemes[i];
git_vector_foreach(challenges, j, challenge) {
if (challenge_matches_scheme(challenge, scheme))
return challenge;
}
}
}
return NULL;
}
static const char *init_auth_context(
git_http_server *server,
git_vector *challenges,
git_credential *credentials)
{
git_http_auth_scheme *scheme;
const char *challenge;
int error;
if (!best_scheme_and_challenge(&scheme, &challenge, challenges, credentials)) {
git_error_set(GIT_ERROR_HTTP, "could not find appropriate mechanism for credentials");
return NULL;
}
error = scheme->init_context(&server->auth_context, &server->url);
if (error == GIT_PASSTHROUGH) {
git_error_set(GIT_ERROR_HTTP, "'%s' authentication is not supported", scheme->name);
return NULL;
}
return challenge;
}
static void free_auth_context(git_http_server *server)
{
if (!server->auth_context)
return;
if (server->auth_context->free)
server->auth_context->free(server->auth_context);
server->auth_context = NULL;
}
static int apply_credentials(
git_str *buf,
git_http_server *server,
const char *header_name,
git_credential *credentials)
{
git_http_auth_context *auth = server->auth_context;
git_vector *challenges = &server->auth_challenges;
const char *challenge;
git_str token = GIT_STR_INIT;
int error = 0;
/* We've started a new request without creds; free the context. */
if (auth && !credentials) {
free_auth_context(server);
return 0;
}
/* We haven't authenticated, nor were we asked to. Nothing to do. */
if (!auth && !git_vector_length(challenges))
return 0;
if (!auth) {
challenge = init_auth_context(server, challenges, credentials);
auth = server->auth_context;
if (!challenge || !auth) {
error = -1;
goto done;
}
} else if (auth->set_challenge) {
challenge = challenge_for_context(challenges, auth);
}
if (auth->set_challenge && challenge &&
(error = auth->set_challenge(auth, challenge)) < 0)
goto done;
if ((error = auth->next_token(&token, auth, credentials)) < 0)
goto done;
if (auth->is_complete && auth->is_complete(auth)) {
/*
* If we're done with an auth mechanism with connection affinity,
* we don't need to send any more headers and can dispose the context.
*/
if (auth->connection_affinity)
free_auth_context(server);
} else if (!token.size) {
git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge");
error = GIT_EAUTH;
goto done;
}
if (token.size > 0)
error = git_str_printf(buf, "%s: %s\r\n", header_name, token.ptr);
done:
git_str_dispose(&token);
return error;
}
GIT_INLINE(int) apply_server_credentials(
git_str *buf,
git_http_client *client,
git_http_request *request)
{
return apply_credentials(buf,
&client->server,
"Authorization",
request->credentials);
}
GIT_INLINE(int) apply_proxy_credentials(
git_str *buf,
git_http_client *client,
git_http_request *request)
{
return apply_credentials(buf,
&client->proxy,
"Proxy-Authorization",
request->proxy_credentials);
}
static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port)
{
bool ipv6 = git_net_url_is_ipv6(url);
if (ipv6)
git_str_putc(buf, '[');
git_str_puts(buf, url->host);
if (ipv6)
git_str_putc(buf, ']');
if (force_port || !git_net_url_is_default_port(url)) {
git_str_putc(buf, ':');
git_str_puts(buf, url->port);
}
return git_str_oom(buf) ? -1 : 0;
}
static int generate_connect_request(
git_http_client *client,
git_http_request *request)
{
git_str *buf;
int error;
git_str_clear(&client->request_msg);
buf = &client->request_msg;
git_str_puts(buf, "CONNECT ");
puts_host_and_port(buf, &client->server.url, true);
git_str_puts(buf, " HTTP/1.1\r\n");
git_str_puts(buf, "User-Agent: ");
git_http__user_agent(buf);
git_str_puts(buf, "\r\n");
git_str_puts(buf, "Host: ");
puts_host_and_port(buf, &client->server.url, true);
git_str_puts(buf, "\r\n");
if ((error = apply_proxy_credentials(buf, client, request) < 0))
return -1;
git_str_puts(buf, "\r\n");
return git_str_oom(buf) ? -1 : 0;
}
static bool use_connect_proxy(git_http_client *client)
{
return client->proxy.url.host && !strcmp(client->server.url.scheme, "https");
}
static int generate_request(
git_http_client *client,
git_http_request *request)
{
git_str *buf;
size_t i;
int error;
GIT_ASSERT_ARG(client);
GIT_ASSERT_ARG(request);
git_str_clear(&client->request_msg);
buf = &client->request_msg;
/* GET|POST path HTTP/1.1 */
git_str_puts(buf, name_for_method(request->method));
git_str_putc(buf, ' ');
if (request->proxy && strcmp(request->url->scheme, "https"))
git_net_url_fmt(buf, request->url);
else
git_net_url_fmt_path(buf, request->url);
git_str_puts(buf, " HTTP/1.1\r\n");
git_str_puts(buf, "User-Agent: ");
git_http__user_agent(buf);
git_str_puts(buf, "\r\n");
git_str_puts(buf, "Host: ");
puts_host_and_port(buf, request->url, false);
git_str_puts(buf, "\r\n");
if (request->accept)
git_str_printf(buf, "Accept: %s\r\n", request->accept);
else
git_str_puts(buf, "Accept: */*\r\n");
if (request->content_type)
git_str_printf(buf, "Content-Type: %s\r\n",
request->content_type);
if (request->chunked)
git_str_puts(buf, "Transfer-Encoding: chunked\r\n");
if (request->content_length > 0)
git_str_printf(buf, "Content-Length: %"PRIuZ "\r\n",
request->content_length);
if (request->expect_continue)
git_str_printf(buf, "Expect: 100-continue\r\n");
if ((error = apply_server_credentials(buf, client, request)) < 0 ||
(!use_connect_proxy(client) &&
(error = apply_proxy_credentials(buf, client, request)) < 0))
return error;
if (request->custom_headers) {
for (i = 0; i < request->custom_headers->count; i++) {
const char *hdr = request->custom_headers->strings[i];
if (hdr)
git_str_printf(buf, "%s\r\n", hdr);
}
}
git_str_puts(buf, "\r\n");
if (git_str_oom(buf))
return -1;
return 0;
}
static int check_certificate(
git_stream *stream,
git_net_url *url,
int is_valid,
git_transport_certificate_check_cb cert_cb,
void *cert_cb_payload)
{
git_cert *cert;
git_error_state last_error = {0};
int error;
if ((error = git_stream_certificate(&cert, stream)) < 0)
return error;
git_error_state_capture(&last_error, GIT_ECERTIFICATE);
error = cert_cb(cert, is_valid, url->host, cert_cb_payload);
if (error == GIT_PASSTHROUGH && !is_valid)
return git_error_state_restore(&last_error);
else if (error == GIT_PASSTHROUGH)
error = 0;
else if (error && !git_error_last())
git_error_set(GIT_ERROR_HTTP,
"user rejected certificate for %s", url->host);
git_error_state_free(&last_error);
return error;
}
static int server_connect_stream(
git_http_server *server,
git_transport_certificate_check_cb cert_cb,
void *cb_payload)
{
int error;
GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream");
error = git_stream_connect(server->stream);
( run in 0.653 second using v1.01-cache-2.11-cpan-75ffa21a3d4 )