mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-07 15:13:28 +00:00
Merge pull request #1048 from pwkelley/basic_auth
Basic authentication for http and winhttp
This commit is contained in:
commit
9d64128325
@ -286,6 +286,19 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha
|
|||||||
*/
|
*/
|
||||||
GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
|
GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a credentials acquisition callback for this remote. If the remote is
|
||||||
|
* not available for anonymous access, then you must set this callback in order
|
||||||
|
* to provide credentials to the transport at the time of authentication
|
||||||
|
* failure so that retry can be performed.
|
||||||
|
*
|
||||||
|
* @param remote the remote to configure
|
||||||
|
* @param The credentials acquisition callback to use (defaults to NULL)
|
||||||
|
*/
|
||||||
|
GIT_EXTERN(void) git_remote_set_cred_acquire_cb(
|
||||||
|
git_remote *remote,
|
||||||
|
git_cred_acquire_cb cred_acquire_cb);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a custom transport for the remote. The caller can use this function
|
* Sets a custom transport for the remote. The caller can use this function
|
||||||
* to bypass the automatic discovery of a transport by URL scheme (i.e.
|
* to bypass the automatic discovery of a transport by URL scheme (i.e.
|
||||||
@ -297,7 +310,9 @@ GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
|
|||||||
* @param remote the remote to configure
|
* @param remote the remote to configure
|
||||||
* @param transport the transport object for the remote to use
|
* @param transport the transport object for the remote to use
|
||||||
*/
|
*/
|
||||||
GIT_EXTERN(int) git_remote_set_transport(git_remote *remote, git_transport *transport);
|
GIT_EXTERN(int) git_remote_set_transport(
|
||||||
|
git_remote *remote,
|
||||||
|
git_transport *transport);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Argument to the completion callback which tells it which operation
|
* Argument to the completion callback which tells it which operation
|
||||||
|
@ -20,6 +20,54 @@
|
|||||||
GIT_BEGIN_DECL
|
GIT_BEGIN_DECL
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
*** Begin interface for credentials acquisition ***
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/* git_cred_userpass_plaintext */
|
||||||
|
GIT_CREDTYPE_USERPASS_PLAINTEXT = 1,
|
||||||
|
} git_credtype_t;
|
||||||
|
|
||||||
|
/* The base structure for all credential types */
|
||||||
|
typedef struct git_cred {
|
||||||
|
git_credtype_t credtype;
|
||||||
|
void (*free)(
|
||||||
|
struct git_cred *cred);
|
||||||
|
} git_cred;
|
||||||
|
|
||||||
|
/* A plaintext username and password */
|
||||||
|
typedef struct git_cred_userpass_plaintext {
|
||||||
|
git_cred parent;
|
||||||
|
char *username;
|
||||||
|
char *password;
|
||||||
|
} git_cred_userpass_plaintext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new plain-text username and password credential object.
|
||||||
|
*
|
||||||
|
* @param cred The newly created credential object.
|
||||||
|
* @param username The username of the credential.
|
||||||
|
* @param password The password of the credential.
|
||||||
|
*/
|
||||||
|
GIT_EXTERN(int) git_cred_userpass_plaintext_new(
|
||||||
|
git_cred **cred,
|
||||||
|
const char *username,
|
||||||
|
const char *password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signature of a function which acquires a credential object.
|
||||||
|
*
|
||||||
|
* @param cred The newly created credential object.
|
||||||
|
* @param url The resource for which we are demanding a credential.
|
||||||
|
* @param allowed_types A bitmask stating which cred types are OK to return.
|
||||||
|
*/
|
||||||
|
typedef int (*git_cred_acquire_cb)(
|
||||||
|
git_cred **cred,
|
||||||
|
const char *url,
|
||||||
|
int allowed_types);
|
||||||
|
|
||||||
|
/*
|
||||||
|
*** End interface for credentials acquisition ***
|
||||||
*** Begin base transport interface ***
|
*** Begin base transport interface ***
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -43,6 +91,7 @@ typedef struct git_transport {
|
|||||||
* direction. */
|
* direction. */
|
||||||
int (*connect)(struct git_transport *transport,
|
int (*connect)(struct git_transport *transport,
|
||||||
const char *url,
|
const char *url,
|
||||||
|
git_cred_acquire_cb cred_acquire_cb,
|
||||||
int direction,
|
int direction,
|
||||||
int flags);
|
int flags);
|
||||||
|
|
||||||
|
@ -193,16 +193,19 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
|
|||||||
static int gitno_ssl_teardown(gitno_ssl *ssl)
|
static int gitno_ssl_teardown(gitno_ssl *ssl)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
ret = SSL_shutdown(ssl->ssl);
|
ret = SSL_shutdown(ssl->ssl);
|
||||||
} while (ret == 0);
|
} while (ret == 0);
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ssl_set_error(ssl, ret);
|
ret = ssl_set_error(ssl, ret);
|
||||||
|
else
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
SSL_free(ssl->ssl);
|
SSL_free(ssl->ssl);
|
||||||
SSL_CTX_free(ssl->ctx);
|
SSL_CTX_free(ssl->ctx);
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Match host names according to RFC 2818 rules */
|
/* Match host names according to RFC 2818 rules */
|
||||||
|
11
src/remote.c
11
src/remote.c
@ -492,7 +492,7 @@ int git_remote_connect(git_remote *remote, int direction)
|
|||||||
if (!remote->check_cert)
|
if (!remote->check_cert)
|
||||||
flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
|
flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
|
||||||
|
|
||||||
if (t->connect(t, url, direction, flags) < 0)
|
if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0)
|
||||||
goto on_error;
|
goto on_error;
|
||||||
|
|
||||||
remote->transport = t;
|
remote->transport = t;
|
||||||
@ -809,6 +809,15 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
|
|||||||
remote->callbacks.data);
|
remote->callbacks.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void git_remote_set_cred_acquire_cb(
|
||||||
|
git_remote *remote,
|
||||||
|
git_cred_acquire_cb cred_acquire_cb)
|
||||||
|
{
|
||||||
|
assert(remote);
|
||||||
|
|
||||||
|
remote->cred_acquire_cb = cred_acquire_cb;
|
||||||
|
}
|
||||||
|
|
||||||
int git_remote_set_transport(git_remote *remote, git_transport *transport)
|
int git_remote_set_transport(git_remote *remote, git_transport *transport)
|
||||||
{
|
{
|
||||||
assert(remote && transport);
|
assert(remote && transport);
|
||||||
|
@ -22,6 +22,7 @@ struct git_remote {
|
|||||||
git_vector refs;
|
git_vector refs;
|
||||||
struct git_refspec fetch;
|
struct git_refspec fetch;
|
||||||
struct git_refspec push;
|
struct git_refspec push;
|
||||||
|
git_cred_acquire_cb cred_acquire_cb;
|
||||||
git_transport *transport;
|
git_transport *transport;
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
git_remote_callbacks callbacks;
|
git_remote_callbacks callbacks;
|
||||||
|
57
src/transports/cred.c
Normal file
57
src/transports/cred.c
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||||
|
*
|
||||||
|
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||||
|
* a Linking Exception. For full terms see the included COPYING file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "git2.h"
|
||||||
|
#include "smart.h"
|
||||||
|
|
||||||
|
static void plaintext_free(struct git_cred *cred)
|
||||||
|
{
|
||||||
|
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
|
||||||
|
int pass_len = strlen(c->password);
|
||||||
|
|
||||||
|
git__free(c->username);
|
||||||
|
|
||||||
|
/* Zero the memory which previously held the password */
|
||||||
|
memset(c->password, 0x0, pass_len);
|
||||||
|
git__free(c->password);
|
||||||
|
|
||||||
|
git__free(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int git_cred_userpass_plaintext_new(
|
||||||
|
git_cred **cred,
|
||||||
|
const char *username,
|
||||||
|
const char *password)
|
||||||
|
{
|
||||||
|
git_cred_userpass_plaintext *c;
|
||||||
|
|
||||||
|
if (!cred)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
c = (git_cred_userpass_plaintext *)git__malloc(sizeof(git_cred_userpass_plaintext));
|
||||||
|
GITERR_CHECK_ALLOC(c);
|
||||||
|
|
||||||
|
c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
|
||||||
|
c->parent.free = plaintext_free;
|
||||||
|
c->username = git__strdup(username);
|
||||||
|
|
||||||
|
if (!c->username) {
|
||||||
|
git__free(c);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->password = git__strdup(password);
|
||||||
|
|
||||||
|
if (!c->password) {
|
||||||
|
git__free(c->username);
|
||||||
|
git__free(c);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*cred = &c->parent;
|
||||||
|
return 0;
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
#include "http_parser.h"
|
#include "http_parser.h"
|
||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
#include "netops.h"
|
#include "netops.h"
|
||||||
|
#include "smart.h"
|
||||||
|
|
||||||
static const char *prefix_http = "http://";
|
static const char *prefix_http = "http://";
|
||||||
static const char *prefix_https = "https://";
|
static const char *prefix_https = "https://";
|
||||||
@ -18,15 +19,23 @@ static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-p
|
|||||||
static const char *upload_pack_service_url = "/git-upload-pack";
|
static const char *upload_pack_service_url = "/git-upload-pack";
|
||||||
static const char *get_verb = "GET";
|
static const char *get_verb = "GET";
|
||||||
static const char *post_verb = "POST";
|
static const char *post_verb = "POST";
|
||||||
|
static const char *basic_authtype = "Basic";
|
||||||
|
|
||||||
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
|
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
|
||||||
|
|
||||||
|
#define PARSE_ERROR_GENERIC -1
|
||||||
|
#define PARSE_ERROR_REPLAY -2
|
||||||
|
|
||||||
enum last_cb {
|
enum last_cb {
|
||||||
NONE,
|
NONE,
|
||||||
FIELD,
|
FIELD,
|
||||||
VALUE
|
VALUE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GIT_HTTP_AUTH_BASIC = 1,
|
||||||
|
} http_authmechanism_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
git_smart_subtransport_stream parent;
|
git_smart_subtransport_stream parent;
|
||||||
const char *service;
|
const char *service;
|
||||||
@ -37,27 +46,28 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
git_smart_subtransport parent;
|
git_smart_subtransport parent;
|
||||||
git_transport *owner;
|
transport_smart *owner;
|
||||||
gitno_socket socket;
|
gitno_socket socket;
|
||||||
const char *path;
|
const char *path;
|
||||||
char *host;
|
char *host;
|
||||||
char *port;
|
char *port;
|
||||||
|
git_cred *cred;
|
||||||
|
http_authmechanism_t auth_mechanism;
|
||||||
unsigned connected : 1,
|
unsigned connected : 1,
|
||||||
use_ssl : 1,
|
use_ssl : 1;
|
||||||
no_check_cert : 1;
|
|
||||||
|
|
||||||
/* Parser structures */
|
/* Parser structures */
|
||||||
http_parser parser;
|
http_parser parser;
|
||||||
http_parser_settings settings;
|
http_parser_settings settings;
|
||||||
gitno_buffer parse_buffer;
|
gitno_buffer parse_buffer;
|
||||||
git_buf parse_temp;
|
git_buf parse_header_name;
|
||||||
|
git_buf parse_header_value;
|
||||||
char parse_buffer_data[2048];
|
char parse_buffer_data[2048];
|
||||||
char *content_type;
|
char *content_type;
|
||||||
|
git_vector www_authenticate;
|
||||||
enum last_cb last_cb;
|
enum last_cb last_cb;
|
||||||
int parse_error;
|
int parse_error;
|
||||||
unsigned parse_finished : 1,
|
unsigned parse_finished : 1;
|
||||||
ct_found : 1,
|
|
||||||
ct_finished : 1;
|
|
||||||
} http_subtransport;
|
} http_subtransport;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -70,10 +80,42 @@ typedef struct {
|
|||||||
size_t *bytes_read;
|
size_t *bytes_read;
|
||||||
} parser_context;
|
} parser_context;
|
||||||
|
|
||||||
static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
|
static int apply_basic_credential(git_buf *buf, git_cred *cred)
|
||||||
const char *service, const char *service_url, ssize_t content_length)
|
|
||||||
{
|
{
|
||||||
if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
|
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
|
||||||
|
git_buf raw = GIT_BUF_INIT;
|
||||||
|
int error = -1;
|
||||||
|
|
||||||
|
git_buf_printf(&raw, "%s:%s", c->username, c->password);
|
||||||
|
|
||||||
|
if (git_buf_oom(&raw) ||
|
||||||
|
git_buf_puts(buf, "Authorization: Basic ") < 0 ||
|
||||||
|
git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 ||
|
||||||
|
git_buf_puts(buf, "\r\n") < 0)
|
||||||
|
goto on_error;
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
on_error:
|
||||||
|
if (raw.size)
|
||||||
|
memset(raw.ptr, 0x0, raw.size);
|
||||||
|
|
||||||
|
git_buf_free(&raw);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gen_request(
|
||||||
|
git_buf *buf,
|
||||||
|
const char *path,
|
||||||
|
const char *host,
|
||||||
|
git_cred *cred,
|
||||||
|
http_authmechanism_t auth_mechanism,
|
||||||
|
const char *op,
|
||||||
|
const char *service,
|
||||||
|
const char *service_url,
|
||||||
|
ssize_t content_length)
|
||||||
|
{
|
||||||
|
if (!path)
|
||||||
path = "/";
|
path = "/";
|
||||||
|
|
||||||
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url);
|
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url);
|
||||||
@ -86,6 +128,13 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
|
|||||||
} else {
|
} else {
|
||||||
git_buf_puts(buf, "Accept: */*\r\n");
|
git_buf_puts(buf, "Accept: */*\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply credentials to the request */
|
||||||
|
if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
|
||||||
|
auth_mechanism == GIT_HTTP_AUTH_BASIC &&
|
||||||
|
apply_basic_credential(buf, cred) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
git_buf_puts(buf, "\r\n");
|
git_buf_puts(buf, "\r\n");
|
||||||
|
|
||||||
if (git_buf_oom(buf))
|
if (git_buf_oom(buf))
|
||||||
@ -94,88 +143,157 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_unauthorized_response(
|
||||||
|
git_vector *www_authenticate,
|
||||||
|
int *allowed_types,
|
||||||
|
http_authmechanism_t *auth_mechanism)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
char *entry;
|
||||||
|
|
||||||
|
git_vector_foreach(www_authenticate, i, entry) {
|
||||||
|
if (!strncmp(entry, basic_authtype, 5) &&
|
||||||
|
(entry[5] == '\0' || entry[5] == ' ')) {
|
||||||
|
*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
|
||||||
|
*auth_mechanism = GIT_HTTP_AUTH_BASIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_header_ready(http_subtransport *t)
|
||||||
|
{
|
||||||
|
git_buf *name = &t->parse_header_name;
|
||||||
|
git_buf *value = &t->parse_header_value;
|
||||||
|
char *dup;
|
||||||
|
|
||||||
|
if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) {
|
||||||
|
t->content_type = git__strdup(git_buf_cstr(value));
|
||||||
|
GITERR_CHECK_ALLOC(t->content_type);
|
||||||
|
}
|
||||||
|
else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
|
||||||
|
dup = git__strdup(git_buf_cstr(value));
|
||||||
|
GITERR_CHECK_ALLOC(dup);
|
||||||
|
git_vector_insert(&t->www_authenticate, dup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int on_header_field(http_parser *parser, const char *str, size_t len)
|
static int on_header_field(http_parser *parser, const char *str, size_t len)
|
||||||
{
|
{
|
||||||
parser_context *ctx = (parser_context *) parser->data;
|
parser_context *ctx = (parser_context *) parser->data;
|
||||||
http_subtransport *t = ctx->t;
|
http_subtransport *t = ctx->t;
|
||||||
git_buf *buf = &t->parse_temp;
|
|
||||||
|
|
||||||
if (t->last_cb == VALUE && t->ct_found) {
|
/* Both parse_header_name and parse_header_value are populated
|
||||||
t->ct_finished = 1;
|
* and ready for consumption */
|
||||||
t->ct_found = 0;
|
if (VALUE == t->last_cb)
|
||||||
t->content_type = git__strdup(git_buf_cstr(buf));
|
if (on_header_ready(t) < 0)
|
||||||
GITERR_CHECK_ALLOC(t->content_type);
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
git_buf_clear(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t->ct_found) {
|
if (NONE == t->last_cb || VALUE == t->last_cb)
|
||||||
t->last_cb = FIELD;
|
git_buf_clear(&t->parse_header_name);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t->last_cb != FIELD)
|
if (git_buf_put(&t->parse_header_name, str, len) < 0)
|
||||||
git_buf_clear(buf);
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
|
|
||||||
git_buf_put(buf, str, len);
|
|
||||||
t->last_cb = FIELD;
|
t->last_cb = FIELD;
|
||||||
|
return 0;
|
||||||
return git_buf_oom(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int on_header_value(http_parser *parser, const char *str, size_t len)
|
static int on_header_value(http_parser *parser, const char *str, size_t len)
|
||||||
{
|
{
|
||||||
parser_context *ctx = (parser_context *) parser->data;
|
parser_context *ctx = (parser_context *) parser->data;
|
||||||
http_subtransport *t = ctx->t;
|
http_subtransport *t = ctx->t;
|
||||||
git_buf *buf = &t->parse_temp;
|
|
||||||
|
|
||||||
if (t->ct_finished) {
|
assert(NONE != t->last_cb);
|
||||||
t->last_cb = VALUE;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t->last_cb == VALUE)
|
if (FIELD == t->last_cb)
|
||||||
git_buf_put(buf, str, len);
|
git_buf_clear(&t->parse_header_value);
|
||||||
|
|
||||||
if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), "Content-Type")) {
|
if (git_buf_put(&t->parse_header_value, str, len) < 0)
|
||||||
t->ct_found = 1;
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
git_buf_clear(buf);
|
|
||||||
git_buf_put(buf, str, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
t->last_cb = VALUE;
|
t->last_cb = VALUE;
|
||||||
|
return 0;
|
||||||
return git_buf_oom(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int on_headers_complete(http_parser *parser)
|
static int on_headers_complete(http_parser *parser)
|
||||||
{
|
{
|
||||||
parser_context *ctx = (parser_context *) parser->data;
|
parser_context *ctx = (parser_context *) parser->data;
|
||||||
http_subtransport *t = ctx->t;
|
http_subtransport *t = ctx->t;
|
||||||
git_buf *buf = &t->parse_temp;
|
http_stream *s = ctx->s;
|
||||||
|
git_buf buf = GIT_BUF_INIT;
|
||||||
|
|
||||||
/* The content-type is text/plain for 404, so don't validate */
|
/* Both parse_header_name and parse_header_value are populated
|
||||||
if (parser->status_code == 404) {
|
* and ready for consumption. */
|
||||||
git_buf_clear(buf);
|
if (VALUE == t->last_cb)
|
||||||
return 0;
|
if (on_header_ready(t) < 0)
|
||||||
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
|
|
||||||
|
/* Check for an authentication failure. */
|
||||||
|
if (parser->status_code == 401 &&
|
||||||
|
get_verb == s->verb &&
|
||||||
|
t->owner->cred_acquire_cb) {
|
||||||
|
int allowed_types = 0;
|
||||||
|
|
||||||
|
if (parse_unauthorized_response(&t->www_authenticate,
|
||||||
|
&allowed_types, &t->auth_mechanism) < 0)
|
||||||
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
|
|
||||||
|
if (allowed_types &&
|
||||||
|
(!t->cred || 0 == (t->cred->credtype & allowed_types))) {
|
||||||
|
|
||||||
|
if (t->owner->cred_acquire_cb(&t->cred,
|
||||||
|
t->owner->url,
|
||||||
|
allowed_types) < 0)
|
||||||
|
return PARSE_ERROR_GENERIC;
|
||||||
|
|
||||||
|
assert(t->cred);
|
||||||
|
|
||||||
|
/* Successfully acquired a credential. */
|
||||||
|
return t->parse_error = PARSE_ERROR_REPLAY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t->content_type == NULL) {
|
/* Check for a 200 HTTP status code. */
|
||||||
t->content_type = git__strdup(git_buf_cstr(buf));
|
if (parser->status_code != 200) {
|
||||||
if (t->content_type == NULL)
|
giterr_set(GITERR_NET,
|
||||||
return t->parse_error = -1;
|
"Unexpected HTTP status code: %d",
|
||||||
|
parser->status_code);
|
||||||
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
git_buf_clear(buf);
|
/* The response must contain a Content-Type header. */
|
||||||
git_buf_printf(buf, "application/x-git-%s-advertisement", ctx->s->service);
|
if (!t->content_type) {
|
||||||
if (git_buf_oom(buf))
|
giterr_set(GITERR_NET, "No Content-Type header in response");
|
||||||
return t->parse_error = -1;
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
|
|
||||||
if (strcmp(t->content_type, git_buf_cstr(buf))) {
|
|
||||||
giterr_set(GITERR_NET, "Invalid content-type: %s", t->content_type);
|
|
||||||
return t->parse_error = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
git_buf_clear(buf);
|
/* The Content-Type header must match our expectation. */
|
||||||
|
if (get_verb == s->verb)
|
||||||
|
git_buf_printf(&buf,
|
||||||
|
"application/x-git-%s-advertisement",
|
||||||
|
ctx->s->service);
|
||||||
|
else
|
||||||
|
git_buf_printf(&buf,
|
||||||
|
"application/x-git-%s-result",
|
||||||
|
ctx->s->service);
|
||||||
|
|
||||||
|
if (git_buf_oom(&buf))
|
||||||
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
|
|
||||||
|
if (strcmp(t->content_type, git_buf_cstr(&buf))) {
|
||||||
|
git_buf_free(&buf);
|
||||||
|
giterr_set(GITERR_NET,
|
||||||
|
"Invalid Content-Type: %s",
|
||||||
|
t->content_type);
|
||||||
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
git_buf_free(&buf);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,11 +304,6 @@ static int on_message_complete(http_parser *parser)
|
|||||||
|
|
||||||
t->parse_finished = 1;
|
t->parse_finished = 1;
|
||||||
|
|
||||||
if (parser->status_code == 404) {
|
|
||||||
giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->parse_temp));
|
|
||||||
t->parse_error = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +314,7 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
|
|||||||
|
|
||||||
if (ctx->buf_size < len) {
|
if (ctx->buf_size < len) {
|
||||||
giterr_set(GITERR_NET, "Can't fit data in the buffer");
|
giterr_set(GITERR_NET, "Can't fit data in the buffer");
|
||||||
return t->parse_error = -1;
|
return t->parse_error = PARSE_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(ctx->buffer, str, len);
|
memcpy(ctx->buffer, str, len);
|
||||||
@ -212,6 +325,36 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clear_parser_state(http_subtransport *t)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
char *entry;
|
||||||
|
|
||||||
|
http_parser_init(&t->parser, HTTP_RESPONSE);
|
||||||
|
gitno_buffer_setup(&t->socket,
|
||||||
|
&t->parse_buffer,
|
||||||
|
t->parse_buffer_data,
|
||||||
|
sizeof(t->parse_buffer_data));
|
||||||
|
|
||||||
|
t->last_cb = NONE;
|
||||||
|
t->parse_error = 0;
|
||||||
|
t->parse_finished = 0;
|
||||||
|
|
||||||
|
git_buf_free(&t->parse_header_name);
|
||||||
|
git_buf_init(&t->parse_header_name, 0);
|
||||||
|
|
||||||
|
git_buf_free(&t->parse_header_value);
|
||||||
|
git_buf_init(&t->parse_header_value, 0);
|
||||||
|
|
||||||
|
git__free(t->content_type);
|
||||||
|
t->content_type = NULL;
|
||||||
|
|
||||||
|
git_vector_foreach(&t->www_authenticate, i, entry)
|
||||||
|
git__free(entry);
|
||||||
|
|
||||||
|
git_vector_free(&t->www_authenticate);
|
||||||
|
}
|
||||||
|
|
||||||
static int http_stream_read(
|
static int http_stream_read(
|
||||||
git_smart_subtransport_stream *stream,
|
git_smart_subtransport_stream *stream,
|
||||||
char *buffer,
|
char *buffer,
|
||||||
@ -219,17 +362,22 @@ static int http_stream_read(
|
|||||||
size_t *bytes_read)
|
size_t *bytes_read)
|
||||||
{
|
{
|
||||||
http_stream *s = (http_stream *)stream;
|
http_stream *s = (http_stream *)stream;
|
||||||
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||||
git_buf request = GIT_BUF_INIT;
|
git_buf request = GIT_BUF_INIT;
|
||||||
parser_context ctx;
|
parser_context ctx;
|
||||||
|
|
||||||
|
replay:
|
||||||
*bytes_read = 0;
|
*bytes_read = 0;
|
||||||
|
|
||||||
assert(t->connected);
|
assert(t->connected);
|
||||||
|
|
||||||
if (!s->sent_request) {
|
if (!s->sent_request) {
|
||||||
if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, 0) < 0) {
|
clear_parser_state(t);
|
||||||
giterr_set(GITERR_NET, "Failed to generate request");
|
|
||||||
|
if (gen_request(&request, t->path, t->host,
|
||||||
|
t->cred, t->auth_mechanism, s->verb,
|
||||||
|
s->service, s->service_url, 0) < 0) {
|
||||||
|
giterr_set(GITERR_NET, "Failed to generate request");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +387,7 @@ static int http_stream_read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
git_buf_free(&request);
|
git_buf_free(&request);
|
||||||
s->sent_request = 1;
|
s->sent_request = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
t->parse_buffer.offset = 0;
|
t->parse_buffer.offset = 0;
|
||||||
@ -250,10 +398,11 @@ static int http_stream_read(
|
|||||||
if (gitno_recv(&t->parse_buffer) < 0)
|
if (gitno_recv(&t->parse_buffer) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* This call to http_parser_execute will result in invocations of the on_* family of
|
/* This call to http_parser_execute will result in invocations of the on_*
|
||||||
* callbacks. The most interesting of these is on_body_fill_buffer, which is called
|
* family of callbacks. The most interesting of these is
|
||||||
* when data is ready to be copied into the target buffer. We need to marshal the
|
* on_body_fill_buffer, which is called when data is ready to be copied
|
||||||
* buffer, buf_size, and bytes_read parameters to this callback. */
|
* into the target buffer. We need to marshal the buffer, buf_size, and
|
||||||
|
* bytes_read parameters to this callback. */
|
||||||
ctx.t = t;
|
ctx.t = t;
|
||||||
ctx.s = s;
|
ctx.s = s;
|
||||||
ctx.buffer = buffer;
|
ctx.buffer = buffer;
|
||||||
@ -262,11 +411,23 @@ static int http_stream_read(
|
|||||||
|
|
||||||
/* Set the context, call the parser, then unset the context. */
|
/* Set the context, call the parser, then unset the context. */
|
||||||
t->parser.data = &ctx;
|
t->parser.data = &ctx;
|
||||||
http_parser_execute(&t->parser, &t->settings, t->parse_buffer.data, t->parse_buffer.offset);
|
|
||||||
|
http_parser_execute(&t->parser,
|
||||||
|
&t->settings,
|
||||||
|
t->parse_buffer.data,
|
||||||
|
t->parse_buffer.offset);
|
||||||
|
|
||||||
t->parser.data = NULL;
|
t->parser.data = NULL;
|
||||||
|
|
||||||
|
/* If there was a handled authentication failure, then parse_error
|
||||||
|
* will have signaled us that we should replay the request. */
|
||||||
|
if (PARSE_ERROR_REPLAY == t->parse_error) {
|
||||||
|
s->sent_request = 0;
|
||||||
|
goto replay;
|
||||||
|
}
|
||||||
|
|
||||||
if (t->parse_error < 0)
|
if (t->parse_error < 0)
|
||||||
return t->parse_error;
|
return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -277,7 +438,7 @@ static int http_stream_write(
|
|||||||
size_t len)
|
size_t len)
|
||||||
{
|
{
|
||||||
http_stream *s = (http_stream *)stream;
|
http_stream *s = (http_stream *)stream;
|
||||||
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||||
git_buf request = GIT_BUF_INIT;
|
git_buf request = GIT_BUF_INIT;
|
||||||
|
|
||||||
assert(t->connected);
|
assert(t->connected);
|
||||||
@ -287,7 +448,11 @@ static int http_stream_write(
|
|||||||
assert(!s->sent_request);
|
assert(!s->sent_request);
|
||||||
|
|
||||||
if (!s->sent_request) {
|
if (!s->sent_request) {
|
||||||
if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, len) < 0) {
|
clear_parser_state(t);
|
||||||
|
|
||||||
|
if (gen_request(&request, t->path, t->host,
|
||||||
|
t->cred, t->auth_mechanism, s->verb,
|
||||||
|
s->service, s->service_url, len) < 0) {
|
||||||
giterr_set(GITERR_NET, "Failed to generate request");
|
giterr_set(GITERR_NET, "Failed to generate request");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -316,9 +481,10 @@ static void http_stream_free(git_smart_subtransport_stream *stream)
|
|||||||
git__free(s);
|
git__free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int http_stream_alloc(http_subtransport *t, git_smart_subtransport_stream **stream)
|
static int http_stream_alloc(http_subtransport *t,
|
||||||
|
git_smart_subtransport_stream **stream)
|
||||||
{
|
{
|
||||||
http_stream *s;
|
http_stream *s;
|
||||||
|
|
||||||
if (!stream)
|
if (!stream)
|
||||||
return -1;
|
return -1;
|
||||||
@ -396,7 +562,8 @@ static int http_action(
|
|||||||
t->use_ssl = 1;
|
t->use_ssl = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
|
if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
|
||||||
|
url, default_port)) < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
t->path = strchr(url, '/');
|
t->path = strchr(url, '/');
|
||||||
@ -404,23 +571,23 @@ static int http_action(
|
|||||||
|
|
||||||
if (!t->connected || !http_should_keep_alive(&t->parser)) {
|
if (!t->connected || !http_should_keep_alive(&t->parser)) {
|
||||||
if (t->use_ssl) {
|
if (t->use_ssl) {
|
||||||
|
int transport_flags;
|
||||||
|
|
||||||
|
if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
flags |= GITNO_CONNECT_SSL;
|
flags |= GITNO_CONNECT_SSL;
|
||||||
|
|
||||||
if (t->no_check_cert)
|
if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags)
|
||||||
flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
|
flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
|
if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
t->parser.data = t;
|
|
||||||
|
|
||||||
http_parser_init(&t->parser, HTTP_RESPONSE);
|
|
||||||
gitno_buffer_setup(&t->socket, &t->parse_buffer, t->parse_buffer_data, sizeof(t->parse_buffer_data));
|
|
||||||
|
|
||||||
t->connected = 1;
|
t->connected = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
t->parse_finished = 0;
|
t->parse_finished = 0;
|
||||||
t->parse_error = 0;
|
t->parse_error = 0;
|
||||||
|
|
||||||
@ -432,7 +599,7 @@ static int http_action(
|
|||||||
case GIT_SERVICE_UPLOADPACK:
|
case GIT_SERVICE_UPLOADPACK:
|
||||||
return http_uploadpack(t, stream);
|
return http_uploadpack(t, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
*stream = NULL;
|
*stream = NULL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -441,14 +608,23 @@ static void http_free(git_smart_subtransport *smart_transport)
|
|||||||
{
|
{
|
||||||
http_subtransport *t = (http_subtransport *) smart_transport;
|
http_subtransport *t = (http_subtransport *) smart_transport;
|
||||||
|
|
||||||
git_buf_free(&t->parse_temp);
|
clear_parser_state(t);
|
||||||
git__free(t->content_type);
|
|
||||||
|
if (t->socket.socket)
|
||||||
|
gitno_close(&t->socket);
|
||||||
|
|
||||||
|
if (t->cred) {
|
||||||
|
t->cred->free(t->cred);
|
||||||
|
t->cred = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
git__free(t->host);
|
git__free(t->host);
|
||||||
git__free(t->port);
|
git__free(t->port);
|
||||||
git__free(t);
|
git__free(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
|
int git_smart_subtransport_http(git_smart_subtransport **out,
|
||||||
|
git_transport *owner)
|
||||||
{
|
{
|
||||||
http_subtransport *t;
|
http_subtransport *t;
|
||||||
int flags;
|
int flags;
|
||||||
@ -459,18 +635,10 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
|
|||||||
t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1);
|
t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1);
|
||||||
GITERR_CHECK_ALLOC(t);
|
GITERR_CHECK_ALLOC(t);
|
||||||
|
|
||||||
t->owner = owner;
|
t->owner = (transport_smart *)owner;
|
||||||
t->parent.action = http_action;
|
t->parent.action = http_action;
|
||||||
t->parent.free = http_free;
|
t->parent.free = http_free;
|
||||||
|
|
||||||
/* Read the flags from the owning transport */
|
|
||||||
if (owner->read_flags && owner->read_flags(owner, &flags) < 0) {
|
|
||||||
git__free(t);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
t->no_check_cert = flags & GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
|
|
||||||
|
|
||||||
t->settings.on_header_field = on_header_field;
|
t->settings.on_header_field = on_header_field;
|
||||||
t->settings.on_header_value = on_header_value;
|
t->settings.on_header_value = on_header_value;
|
||||||
t->settings.on_headers_complete = on_headers_complete;
|
t->settings.on_headers_complete = on_headers_complete;
|
||||||
|
@ -130,7 +130,11 @@ on_error:
|
|||||||
* Try to open the url as a git directory. The direction doesn't
|
* Try to open the url as a git directory. The direction doesn't
|
||||||
* matter in this case because we're calulating the heads ourselves.
|
* matter in this case because we're calulating the heads ourselves.
|
||||||
*/
|
*/
|
||||||
static int local_connect(git_transport *transport, const char *url, int direction, int flags)
|
static int local_connect(
|
||||||
|
git_transport *transport,
|
||||||
|
const char *url,
|
||||||
|
git_cred_acquire_cb cred_acquire_cb,
|
||||||
|
int direction, int flags)
|
||||||
{
|
{
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
int error;
|
int error;
|
||||||
@ -138,6 +142,8 @@ static int local_connect(git_transport *transport, const char *url, int directio
|
|||||||
const char *path;
|
const char *path;
|
||||||
git_buf buf = GIT_BUF_INIT;
|
git_buf buf = GIT_BUF_INIT;
|
||||||
|
|
||||||
|
GIT_UNUSED(cred_acquire_cb);
|
||||||
|
|
||||||
t->url = git__strdup(url);
|
t->url = git__strdup(url);
|
||||||
GITERR_CHECK_ALLOC(t->url);
|
GITERR_CHECK_ALLOC(t->url);
|
||||||
t->direction = direction;
|
t->direction = direction;
|
||||||
|
@ -52,7 +52,12 @@ static int git_smart__set_callbacks(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int git_smart__connect(git_transport *transport, const char *url, int direction, int flags)
|
static int git_smart__connect(
|
||||||
|
git_transport *transport,
|
||||||
|
const char *url,
|
||||||
|
git_cred_acquire_cb cred_acquire_cb,
|
||||||
|
int direction,
|
||||||
|
int flags)
|
||||||
{
|
{
|
||||||
transport_smart *t = (transport_smart *)transport;
|
transport_smart *t = (transport_smart *)transport;
|
||||||
git_smart_subtransport_stream *stream;
|
git_smart_subtransport_stream *stream;
|
||||||
@ -66,6 +71,7 @@ static int git_smart__connect(git_transport *transport, const char *url, int dir
|
|||||||
|
|
||||||
t->direction = direction;
|
t->direction = direction;
|
||||||
t->flags = flags;
|
t->flags = flags;
|
||||||
|
t->cred_acquire_cb = cred_acquire_cb;
|
||||||
|
|
||||||
if (GIT_DIR_FETCH == direction)
|
if (GIT_DIR_FETCH == direction)
|
||||||
{
|
{
|
||||||
|
@ -99,6 +99,7 @@ typedef void (*packetsize_cb)(int received, void *payload);
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
git_transport parent;
|
git_transport parent;
|
||||||
char *url;
|
char *url;
|
||||||
|
git_cred_acquire_cb cred_acquire_cb;
|
||||||
int direction;
|
int direction;
|
||||||
int flags;
|
int flags;
|
||||||
git_transport_message_cb progress_cb;
|
git_transport_message_cb progress_cb;
|
||||||
|
@ -30,6 +30,7 @@ static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-p
|
|||||||
static const char *upload_pack_service_url = "/git-upload-pack";
|
static const char *upload_pack_service_url = "/git-upload-pack";
|
||||||
static const wchar_t *get_verb = L"GET";
|
static const wchar_t *get_verb = L"GET";
|
||||||
static const wchar_t *post_verb = L"POST";
|
static const wchar_t *post_verb = L"POST";
|
||||||
|
static const wchar_t *basic_authtype = L"Basic";
|
||||||
static const wchar_t *pragma_nocache = L"Pragma: no-cache";
|
static const wchar_t *pragma_nocache = L"Pragma: no-cache";
|
||||||
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
||||||
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
|
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
|
||||||
@ -37,6 +38,10 @@ static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
|||||||
|
|
||||||
#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
|
#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GIT_WINHTTP_AUTH_BASIC = 1,
|
||||||
|
} winhttp_authmechanism_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
git_smart_subtransport_stream parent;
|
git_smart_subtransport_stream parent;
|
||||||
const char *service;
|
const char *service;
|
||||||
@ -49,16 +54,74 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
git_smart_subtransport parent;
|
git_smart_subtransport parent;
|
||||||
git_transport *owner;
|
transport_smart *owner;
|
||||||
const char *path;
|
const char *path;
|
||||||
char *host;
|
char *host;
|
||||||
char *port;
|
char *port;
|
||||||
|
git_cred *cred;
|
||||||
|
int auth_mechanism;
|
||||||
HINTERNET session;
|
HINTERNET session;
|
||||||
HINTERNET connection;
|
HINTERNET connection;
|
||||||
unsigned use_ssl : 1,
|
unsigned use_ssl : 1;
|
||||||
no_check_cert : 1;
|
|
||||||
} winhttp_subtransport;
|
} winhttp_subtransport;
|
||||||
|
|
||||||
|
static int apply_basic_credential(HINTERNET request, git_cred *cred)
|
||||||
|
{
|
||||||
|
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
|
||||||
|
git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT;
|
||||||
|
wchar_t *wide = NULL;
|
||||||
|
int error = -1, wide_len;
|
||||||
|
|
||||||
|
git_buf_printf(&raw, "%s:%s", c->username, c->password);
|
||||||
|
|
||||||
|
if (git_buf_oom(&raw) ||
|
||||||
|
git_buf_puts(&buf, "Authorization: Basic ") < 0 ||
|
||||||
|
git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
|
||||||
|
goto on_error;
|
||||||
|
|
||||||
|
wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||||
|
git_buf_cstr(&buf), -1, NULL, 0);
|
||||||
|
|
||||||
|
if (!wide_len) {
|
||||||
|
giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
|
||||||
|
goto on_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
wide = (wchar_t *)git__malloc(wide_len * sizeof(wchar_t));
|
||||||
|
|
||||||
|
if (!wide)
|
||||||
|
goto on_error;
|
||||||
|
|
||||||
|
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||||
|
git_buf_cstr(&buf), -1, wide, wide_len)) {
|
||||||
|
giterr_set(GITERR_OS, "Failed to convert string to wide form");
|
||||||
|
goto on_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
|
||||||
|
giterr_set(GITERR_OS, "Failed to add a header to the request");
|
||||||
|
goto on_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
on_error:
|
||||||
|
/* We were dealing with plaintext passwords, so clean up after ourselves a bit. */
|
||||||
|
if (wide)
|
||||||
|
memset(wide, 0x0, wide_len * sizeof(wchar_t));
|
||||||
|
|
||||||
|
if (buf.size)
|
||||||
|
memset(buf.ptr, 0x0, buf.size);
|
||||||
|
|
||||||
|
if (raw.size)
|
||||||
|
memset(raw.ptr, 0x0, raw.size);
|
||||||
|
|
||||||
|
git__free(wide);
|
||||||
|
git_buf_free(&buf);
|
||||||
|
git_buf_free(&raw);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
static int winhttp_stream_connect(winhttp_stream *s)
|
static int winhttp_stream_connect(winhttp_stream *s)
|
||||||
{
|
{
|
||||||
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||||
@ -119,14 +182,27 @@ static int winhttp_stream_connect(winhttp_stream *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* If requested, disable certificate validation */
|
/* If requested, disable certificate validation */
|
||||||
if (t->use_ssl && t->no_check_cert) {
|
if (t->use_ssl) {
|
||||||
if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
|
int flags;
|
||||||
|
|
||||||
|
if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
|
||||||
|
goto on_error;
|
||||||
|
|
||||||
|
if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) &&
|
||||||
|
!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
|
||||||
(LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
|
(LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
|
||||||
giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
|
giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
|
||||||
goto on_error;
|
goto on_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If we have a credential on the subtransport, apply it to the request */
|
||||||
|
if (t->cred &&
|
||||||
|
t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
|
||||||
|
t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC &&
|
||||||
|
apply_basic_credential(s->request, t->cred) < 0)
|
||||||
|
goto on_error;
|
||||||
|
|
||||||
/* We've done everything up to calling WinHttpSendRequest. */
|
/* We've done everything up to calling WinHttpSendRequest. */
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -136,6 +212,64 @@ on_error:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_unauthorized_response(
|
||||||
|
HINTERNET request,
|
||||||
|
int *allowed_types,
|
||||||
|
int *auth_mechanism)
|
||||||
|
{
|
||||||
|
DWORD index, buf_size, last_error;
|
||||||
|
int error = 0;
|
||||||
|
wchar_t *buf = NULL;
|
||||||
|
|
||||||
|
*allowed_types = 0;
|
||||||
|
|
||||||
|
for (index = 0; ; index++) {
|
||||||
|
/* Make a first call to ask for the size of the buffer to allocate
|
||||||
|
* to hold the WWW-Authenticate header */
|
||||||
|
if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
|
||||||
|
WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER,
|
||||||
|
&buf_size, &index))
|
||||||
|
{
|
||||||
|
last_error = GetLastError();
|
||||||
|
|
||||||
|
if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) {
|
||||||
|
/* End of enumeration */
|
||||||
|
break;
|
||||||
|
} else if (ERROR_INSUFFICIENT_BUFFER == last_error) {
|
||||||
|
git__free(buf);
|
||||||
|
buf = (wchar_t *)git__malloc(buf_size);
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
error = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
|
||||||
|
error = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actually receive the data into our now-allocated buffer */
|
||||||
|
if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
|
||||||
|
WINHTTP_HEADER_NAME_BY_INDEX, buf,
|
||||||
|
&buf_size, &index)) {
|
||||||
|
giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
|
||||||
|
error = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wcsncmp(buf, basic_authtype, 5) &&
|
||||||
|
(buf[5] == L'\0' || buf[5] == L' ')) {
|
||||||
|
*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
|
||||||
|
*auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
git__free(buf);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
static int winhttp_stream_read(
|
static int winhttp_stream_read(
|
||||||
git_smart_subtransport_stream *stream,
|
git_smart_subtransport_stream *stream,
|
||||||
char *buffer,
|
char *buffer,
|
||||||
@ -145,6 +279,7 @@ static int winhttp_stream_read(
|
|||||||
winhttp_stream *s = (winhttp_stream *)stream;
|
winhttp_stream *s = (winhttp_stream *)stream;
|
||||||
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||||
|
|
||||||
|
replay:
|
||||||
/* Connect if necessary */
|
/* Connect if necessary */
|
||||||
if (!s->request && winhttp_stream_connect(s) < 0)
|
if (!s->request && winhttp_stream_connect(s) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
@ -182,6 +317,31 @@ static int winhttp_stream_read(
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Handle authentication failures */
|
||||||
|
if (HTTP_STATUS_DENIED == status_code &&
|
||||||
|
get_verb == s->verb && t->owner->cred_acquire_cb) {
|
||||||
|
int allowed_types;
|
||||||
|
|
||||||
|
if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (allowed_types &&
|
||||||
|
(!t->cred || 0 == (t->cred->credtype & allowed_types))) {
|
||||||
|
|
||||||
|
if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, allowed_types) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
assert(t->cred);
|
||||||
|
|
||||||
|
WinHttpCloseHandle(s->request);
|
||||||
|
s->request = NULL;
|
||||||
|
s->sent_request = 0;
|
||||||
|
|
||||||
|
/* Successfully acquired a credential */
|
||||||
|
goto replay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (HTTP_STATUS_OK != status_code) {
|
if (HTTP_STATUS_OK != status_code) {
|
||||||
giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
|
giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
|
||||||
return -1;
|
return -1;
|
||||||
@ -432,6 +592,11 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
|
|||||||
git__free(t->host);
|
git__free(t->host);
|
||||||
git__free(t->port);
|
git__free(t->port);
|
||||||
|
|
||||||
|
if (t->cred) {
|
||||||
|
t->cred->free(t->cred);
|
||||||
|
t->cred = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (t->connection) {
|
if (t->connection) {
|
||||||
WinHttpCloseHandle(t->connection);
|
WinHttpCloseHandle(t->connection);
|
||||||
t->connection = NULL;
|
t->connection = NULL;
|
||||||
@ -448,7 +613,6 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
|
|||||||
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
|
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
|
||||||
{
|
{
|
||||||
winhttp_subtransport *t;
|
winhttp_subtransport *t;
|
||||||
int flags;
|
|
||||||
|
|
||||||
if (!out)
|
if (!out)
|
||||||
return -1;
|
return -1;
|
||||||
@ -456,18 +620,10 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
|
|||||||
t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1);
|
t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1);
|
||||||
GITERR_CHECK_ALLOC(t);
|
GITERR_CHECK_ALLOC(t);
|
||||||
|
|
||||||
t->owner = owner;
|
t->owner = (transport_smart *)owner;
|
||||||
t->parent.action = winhttp_action;
|
t->parent.action = winhttp_action;
|
||||||
t->parent.free = winhttp_free;
|
t->parent.free = winhttp_free;
|
||||||
|
|
||||||
/* Read the flags from the owning transport */
|
|
||||||
if (owner->read_flags && owner->read_flags(owner, &flags) < 0) {
|
|
||||||
git__free(t);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
t->no_check_cert = flags & GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
|
|
||||||
|
|
||||||
*out = (git_smart_subtransport *) t;
|
*out = (git_smart_subtransport *) t;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user