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 )