mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-02 21:34:15 +00:00
Reorganize transport architecture (squashed 3)
This commit is contained in:
parent
a0ce87c51c
commit
41fb1ca0ec
@ -36,6 +36,7 @@
|
||||
|
||||
#include "git2/index.h"
|
||||
#include "git2/config.h"
|
||||
#include "git2/transport.h"
|
||||
#include "git2/remote.h"
|
||||
#include "git2/clone.h"
|
||||
#include "git2/checkout.h"
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "net.h"
|
||||
#include "indexer.h"
|
||||
#include "strarray.h"
|
||||
#include "transport.h"
|
||||
|
||||
/**
|
||||
* @file git2/remote.h
|
||||
@ -283,9 +284,21 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha
|
||||
* @param remote the remote to configure
|
||||
* @param check whether to check the server's certificate (defaults to yes)
|
||||
*/
|
||||
|
||||
GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* http://, https://, git://) and supply their own transport to be used
|
||||
* instead. After providing the transport to a remote using this function,
|
||||
* the transport object belongs exclusively to that remote, and the remote will
|
||||
* free it when it is freed with git_remote_free.
|
||||
*
|
||||
* @param remote the remote to configure
|
||||
* @param transport the transport object for the remote to use
|
||||
*/
|
||||
GIT_EXTERN(int) git_remote_set_transport(git_remote *remote, git_transport *transport);
|
||||
|
||||
/**
|
||||
* Argument to the completion callback which tells it which operation
|
||||
* finished.
|
||||
|
257
include/git2/transport.h
Normal file
257
include/git2/transport.h
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_git_transport_h__
|
||||
#define INCLUDE_git_transport_h__
|
||||
|
||||
#include "indexer.h"
|
||||
#include "net.h"
|
||||
|
||||
/**
|
||||
* @file git2/transport.h
|
||||
* @brief Git transport interfaces and functions
|
||||
* @defgroup git_transport interfaces and functions
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/*
|
||||
*** Begin base transport interface ***
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
GIT_TRANSPORTFLAGS_NONE = 0,
|
||||
/* If the connection is secured with SSL/TLS, the authenticity
|
||||
* of the server certificate should not be verified. */
|
||||
GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1
|
||||
} git_transport_flags_t;
|
||||
|
||||
typedef void (*git_transport_message_cb)(const char *str, int len, void *data);
|
||||
|
||||
typedef struct git_transport {
|
||||
/* Set progress and error callbacks */
|
||||
int (*set_callbacks)(struct git_transport *transport,
|
||||
git_transport_message_cb progress_cb,
|
||||
git_transport_message_cb error_cb,
|
||||
void *payload);
|
||||
|
||||
/* Connect the transport to the remote repository, using the given
|
||||
* direction. */
|
||||
int (*connect)(struct git_transport *transport,
|
||||
const char *url,
|
||||
int direction,
|
||||
int flags);
|
||||
|
||||
/* This function may be called after a successful call to connect(). The
|
||||
* provided callback is invoked for each ref discovered on the remote
|
||||
* end. */
|
||||
int (*ls)(struct git_transport *transport,
|
||||
git_headlist_cb list_cb,
|
||||
void *payload);
|
||||
|
||||
/* Reserved until push is implemented. */
|
||||
int (*push)(struct git_transport *transport);
|
||||
|
||||
/* This function may be called after a successful call to connect(), when
|
||||
* the direction is FETCH. The function performs a negotiation to calculate
|
||||
* the wants list for the fetch. */
|
||||
int (*negotiate_fetch)(struct git_transport *transport,
|
||||
git_repository *repo,
|
||||
const git_remote_head * const *refs,
|
||||
size_t count);
|
||||
|
||||
/* This function may be called after a successful call to negotiate_fetch(),
|
||||
* when the direction is FETCH. This function retrieves the pack file for
|
||||
* the fetch from the remote end. */
|
||||
int (*download_pack)(struct git_transport *transport,
|
||||
git_repository *repo,
|
||||
git_transfer_progress *stats,
|
||||
git_transfer_progress_callback progress_cb,
|
||||
void *progress_payload);
|
||||
|
||||
/* Checks to see if the transport is connected */
|
||||
int (*is_connected)(struct git_transport *transport, int *connected);
|
||||
|
||||
/* Reads the flags value previously passed into connect() */
|
||||
int (*read_flags)(struct git_transport *transport, int *flags);
|
||||
|
||||
/* Cancels any outstanding transport operation */
|
||||
void (*cancel)(struct git_transport *transport);
|
||||
|
||||
/* This function is the reverse of connect() -- it terminates the
|
||||
* connection to the remote end. */
|
||||
int (*close)(struct git_transport *transport);
|
||||
|
||||
/* Frees/destructs the git_transport object. */
|
||||
void (*free)(struct git_transport *transport);
|
||||
} git_transport;
|
||||
|
||||
/**
|
||||
* Function to use to create a transport from a URL. The transport database
|
||||
* is scanned to find a transport that implements the scheme of the URI (i.e.
|
||||
* git:// or http://) and a transport object is returned to the caller.
|
||||
*
|
||||
* @param transport The newly created transport (out)
|
||||
* @param url The URL to connect to
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transport_new(git_transport **transport, const char *url);
|
||||
|
||||
/**
|
||||
* Function which checks to see if a transport could be created for the
|
||||
* given URL (i.e. checks to see if libgit2 has a transport that supports
|
||||
* the given URL's scheme)
|
||||
*
|
||||
* @param url The URL to check
|
||||
* @return Zero if the URL is not valid; nonzero otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_transport_valid_url(const char *url);
|
||||
|
||||
/* Signature of a function which creates a transport */
|
||||
typedef int (*git_transport_cb)(git_transport **transport, void *param);
|
||||
|
||||
/* Transports which come with libgit2 (match git_transport_cb). The expected
|
||||
* value for "param" is listed in-line below. */
|
||||
|
||||
/**
|
||||
* Create an instance of the dummy transport.
|
||||
*
|
||||
* @param transport The newly created transport (out)
|
||||
* @param param You must pass NULL for this parameter.
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transport_dummy(
|
||||
git_transport **transport,
|
||||
/* NULL */ void *param);
|
||||
|
||||
/**
|
||||
* Create an instance of the local transport.
|
||||
*
|
||||
* @param transport The newly created transport (out)
|
||||
* @param param You must pass NULL for this parameter.
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transport_local(
|
||||
git_transport **transport,
|
||||
/* NULL */ void *param);
|
||||
|
||||
/**
|
||||
* Create an instance of the smart transport.
|
||||
*
|
||||
* @param transport The newly created transport (out)
|
||||
* @param param A pointer to a git_smart_subtransport_definition
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_transport_smart(
|
||||
git_transport **transport,
|
||||
/* (git_smart_subtransport_definition *) */ void *param);
|
||||
|
||||
/*
|
||||
*** End of base transport interface ***
|
||||
*** Begin interface for subtransports for the smart transport ***
|
||||
*/
|
||||
|
||||
/* The smart transport knows how to speak the git protocol, but it has no
|
||||
* knowledge of how to establish a connection between it and another endpoint,
|
||||
* or how to move data back and forth. For this, a subtransport interface is
|
||||
* declared, and the smart transport delegates this work to the subtransports.
|
||||
* Three subtransports are implemented: git, http, and winhttp. (The http and
|
||||
* winhttp transports each implement both http and https.) */
|
||||
|
||||
/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1
|
||||
* (request/response). The smart transport handles the differences in its own
|
||||
* logic. The git subtransport is RPC = 0, while http and winhttp are both
|
||||
* RPC = 1. */
|
||||
|
||||
/* Actions that the smart transport can ask
|
||||
* a subtransport to perform */
|
||||
typedef enum {
|
||||
GIT_SERVICE_UPLOADPACK_LS = 1,
|
||||
GIT_SERVICE_UPLOADPACK = 2,
|
||||
} git_smart_service_t;
|
||||
|
||||
struct git_smart_subtransport;
|
||||
|
||||
/* A stream used by the smart transport to read and write data
|
||||
* from a subtransport */
|
||||
typedef struct git_smart_subtransport_stream {
|
||||
/* The owning subtransport */
|
||||
struct git_smart_subtransport *subtransport;
|
||||
|
||||
int (*read)(
|
||||
struct git_smart_subtransport_stream *stream,
|
||||
char *buffer,
|
||||
size_t buf_size,
|
||||
size_t *bytes_read);
|
||||
|
||||
int (*write)(
|
||||
struct git_smart_subtransport_stream *stream,
|
||||
const char *buffer,
|
||||
size_t len);
|
||||
|
||||
void (*free)(
|
||||
struct git_smart_subtransport_stream *stream);
|
||||
} git_smart_subtransport_stream;
|
||||
|
||||
/* An implementation of a subtransport which carries data for the
|
||||
* smart transport */
|
||||
typedef struct git_smart_subtransport {
|
||||
int (* action)(
|
||||
git_smart_subtransport_stream **out,
|
||||
struct git_smart_subtransport *transport,
|
||||
const char *url,
|
||||
git_smart_service_t action);
|
||||
|
||||
void (* free)(struct git_smart_subtransport *transport);
|
||||
} git_smart_subtransport;
|
||||
|
||||
/* A function which creates a new subtransport for the smart transport */
|
||||
typedef int (*git_smart_subtransport_cb)(
|
||||
git_smart_subtransport **out,
|
||||
git_transport* owner);
|
||||
|
||||
typedef struct git_smart_subtransport_definition {
|
||||
/* The function to use to create the git_smart_subtransport */
|
||||
git_smart_subtransport_cb callback;
|
||||
/* True if the protocol is stateless; false otherwise. For example,
|
||||
* http:// is stateless, but git:// is not. */
|
||||
unsigned rpc : 1;
|
||||
} git_smart_subtransport_definition;
|
||||
|
||||
/* Smart transport subtransports that come with libgit2 */
|
||||
|
||||
/**
|
||||
* Create an instance of the http subtransport. This subtransport
|
||||
* also supports https. On Win32, this subtransport may be implemented
|
||||
* using the WinHTTP library.
|
||||
*
|
||||
* @param out The newly created subtransport
|
||||
* @param owner The smart transport to own this subtransport
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_smart_subtransport_http(
|
||||
git_smart_subtransport **out,
|
||||
git_transport* owner);
|
||||
|
||||
/**
|
||||
* Create an instance of the git subtransport.
|
||||
*
|
||||
* @param out The newly created subtransport
|
||||
* @param owner The smart transport to own this subtransport
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_smart_subtransport_git(
|
||||
git_smart_subtransport **out,
|
||||
git_transport* owner);
|
||||
|
||||
/*
|
||||
*** End interface for subtransports for the smart transport ***
|
||||
*/
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
#endif
|
20
src/clone.c
20
src/clone.c
@ -18,7 +18,6 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "remote.h"
|
||||
#include "pkt.h"
|
||||
#include "fileops.h"
|
||||
#include "refs.h"
|
||||
#include "path.h"
|
||||
@ -171,11 +170,19 @@ static int update_head_to_new_branch(
|
||||
return error;
|
||||
}
|
||||
|
||||
static int get_head_callback(git_remote_head *head, void *payload)
|
||||
{
|
||||
git_remote_head **destination = (git_remote_head **)payload;
|
||||
|
||||
/* Save the first entry, and terminate the enumeration */
|
||||
*destination = head;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int update_head_to_remote(git_repository *repo, git_remote *remote)
|
||||
{
|
||||
int retcode = -1;
|
||||
git_remote_head *remote_head;
|
||||
git_pkt_ref *pkt;
|
||||
struct head_info head_info;
|
||||
git_buf remote_master_name = GIT_BUF_INIT;
|
||||
|
||||
@ -189,8 +196,13 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
|
||||
}
|
||||
|
||||
/* Get the remote's HEAD. This is always the first ref in remote->refs. */
|
||||
pkt = remote->transport->refs.contents[0];
|
||||
remote_head = &pkt->head;
|
||||
remote_head = NULL;
|
||||
|
||||
if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head))
|
||||
return -1;
|
||||
|
||||
assert(remote_head);
|
||||
|
||||
git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
|
||||
git_buf_init(&head_info.branchname, 16);
|
||||
head_info.repo = repo;
|
||||
|
@ -69,7 +69,4 @@ void giterr_set_regex(const regex_t *regex, int error_code);
|
||||
|
||||
#include "util.h"
|
||||
|
||||
typedef struct git_transport git_transport;
|
||||
typedef struct gitno_buffer gitno_buffer;
|
||||
|
||||
#endif /* INCLUDE_common_h__ */
|
||||
|
393
src/fetch.c
393
src/fetch.c
@ -9,17 +9,14 @@
|
||||
#include "git2/refs.h"
|
||||
#include "git2/revwalk.h"
|
||||
#include "git2/indexer.h"
|
||||
#include "git2/transport.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "transport.h"
|
||||
#include "remote.h"
|
||||
#include "refspec.h"
|
||||
#include "pack.h"
|
||||
#include "fetch.h"
|
||||
#include "netops.h"
|
||||
#include "pkt.h"
|
||||
|
||||
#define NETWORK_XFER_THRESHOLD (100*1024)
|
||||
|
||||
struct filter_payload {
|
||||
git_remote *remote;
|
||||
@ -88,61 +85,6 @@ cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Wait until we get an ack from the */
|
||||
static int recv_pkt(git_pkt **out, gitno_buffer *buf)
|
||||
{
|
||||
const char *ptr = buf->data, *line_end = ptr;
|
||||
git_pkt *pkt;
|
||||
int pkt_type, error = 0, ret;
|
||||
|
||||
do {
|
||||
if (buf->offset > 0)
|
||||
error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
|
||||
else
|
||||
error = GIT_EBUFS;
|
||||
|
||||
if (error == 0)
|
||||
break; /* return the pkt */
|
||||
|
||||
if (error < 0 && error != GIT_EBUFS)
|
||||
return -1;
|
||||
|
||||
if ((ret = gitno_recv(buf)) < 0)
|
||||
return -1;
|
||||
} while (error);
|
||||
|
||||
gitno_consume(buf, line_end);
|
||||
pkt_type = pkt->type;
|
||||
if (out != NULL)
|
||||
*out = pkt;
|
||||
else
|
||||
git__free(pkt);
|
||||
|
||||
return pkt_type;
|
||||
}
|
||||
|
||||
static int store_common(git_transport *t)
|
||||
{
|
||||
git_pkt *pkt = NULL;
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
|
||||
do {
|
||||
if (recv_pkt(&pkt, buf) < 0)
|
||||
return -1;
|
||||
|
||||
if (pkt->type == GIT_PKT_ACK) {
|
||||
if (git_vector_insert(&t->common, pkt) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
git__free(pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} while (1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* In this first version, we push all our refs in and start sending
|
||||
* them out. When we get an ACK we hide that commit and continue
|
||||
@ -151,13 +93,7 @@ static int store_common(git_transport *t)
|
||||
int git_fetch_negotiate(git_remote *remote)
|
||||
{
|
||||
git_transport *t = remote->transport;
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
git_buf data = GIT_BUF_INIT;
|
||||
git_revwalk *walk = NULL;
|
||||
int error = -1, pkt_type;
|
||||
unsigned int i;
|
||||
git_oid oid;
|
||||
|
||||
|
||||
if (filter_wants(remote) < 0) {
|
||||
giterr_set(GITERR_NET, "Failed to filter the reference list for wants");
|
||||
return -1;
|
||||
@ -169,330 +105,23 @@ int git_fetch_negotiate(git_remote *remote)
|
||||
|
||||
/*
|
||||
* Now we have everything set up so we can start tell the
|
||||
* server what we want and what we have. Call the function if
|
||||
* the transport has its own logic. This is transitional and
|
||||
* will be removed once this function can support git and http.
|
||||
* server what we want and what we have.
|
||||
*/
|
||||
if (t->own_logic)
|
||||
return t->negotiate_fetch(t, remote->repo, &remote->refs);
|
||||
|
||||
/* No own logic, do our thing */
|
||||
if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_fetch_setup_walk(&walk, remote->repo) < 0)
|
||||
goto on_error;
|
||||
/*
|
||||
* We don't support any kind of ACK extensions, so the negotiation
|
||||
* boils down to sending what we have and listening for an ACK
|
||||
* every once in a while.
|
||||
*/
|
||||
i = 0;
|
||||
while ((error = git_revwalk_next(&oid, walk)) == 0) {
|
||||
git_pkt_buffer_have(&oid, &data);
|
||||
i++;
|
||||
if (i % 20 == 0) {
|
||||
if (t->cancel.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
error = GIT_EUSER;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git_pkt_buffer_flush(&data);
|
||||
if (git_buf_oom(&data))
|
||||
goto on_error;
|
||||
|
||||
if (t->negotiation_step(t, data.ptr, data.size) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_clear(&data);
|
||||
if (t->caps.multi_ack) {
|
||||
if (store_common(t) < 0)
|
||||
goto on_error;
|
||||
} else {
|
||||
pkt_type = recv_pkt(NULL, buf);
|
||||
|
||||
if (pkt_type == GIT_PKT_ACK) {
|
||||
break;
|
||||
} else if (pkt_type == GIT_PKT_NAK) {
|
||||
continue;
|
||||
} else {
|
||||
giterr_set(GITERR_NET, "Unexpected pkt type");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (t->common.length > 0)
|
||||
break;
|
||||
|
||||
if (i % 20 == 0 && t->rpc) {
|
||||
git_pkt_ack *pkt;
|
||||
unsigned int i;
|
||||
|
||||
if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_vector_foreach(&t->common, i, pkt) {
|
||||
git_pkt_buffer_have(&pkt->oid, &data);
|
||||
}
|
||||
|
||||
if (git_buf_oom(&data))
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (error < 0 && error != GIT_ITEROVER)
|
||||
goto on_error;
|
||||
|
||||
/* Tell the other end that we're done negotiating */
|
||||
if (t->rpc && t->common.length > 0) {
|
||||
git_pkt_ack *pkt;
|
||||
unsigned int i;
|
||||
|
||||
if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_vector_foreach(&t->common, i, pkt) {
|
||||
git_pkt_buffer_have(&pkt->oid, &data);
|
||||
}
|
||||
|
||||
if (git_buf_oom(&data))
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git_pkt_buffer_done(&data);
|
||||
if (t->cancel.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
error = GIT_EUSER;
|
||||
goto on_error;
|
||||
}
|
||||
if (t->negotiation_step(t, data.ptr, data.size) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_free(&data);
|
||||
git_revwalk_free(walk);
|
||||
|
||||
/* Now let's eat up whatever the server gives us */
|
||||
if (!t->caps.multi_ack) {
|
||||
pkt_type = recv_pkt(NULL, buf);
|
||||
if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
|
||||
giterr_set(GITERR_NET, "Unexpected pkt type");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
git_pkt_ack *pkt;
|
||||
do {
|
||||
if (recv_pkt((git_pkt **)&pkt, buf) < 0)
|
||||
return -1;
|
||||
|
||||
if (pkt->type == GIT_PKT_NAK ||
|
||||
(pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
|
||||
git__free(pkt);
|
||||
break;
|
||||
}
|
||||
|
||||
git__free(pkt);
|
||||
} while (1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_revwalk_free(walk);
|
||||
git_buf_free(&data);
|
||||
return error;
|
||||
return t->negotiate_fetch(t,
|
||||
remote->repo,
|
||||
(const git_remote_head * const *)remote->refs.contents,
|
||||
remote->refs.length);
|
||||
}
|
||||
|
||||
int git_fetch_download_pack(
|
||||
git_remote *remote,
|
||||
git_transfer_progress_callback progress_cb,
|
||||
void *progress_payload)
|
||||
git_remote *remote,
|
||||
git_transfer_progress_callback progress_cb,
|
||||
void *progress_payload)
|
||||
{
|
||||
git_transport *t = remote->transport;
|
||||
|
||||
if(!remote->need_pack)
|
||||
return 0;
|
||||
|
||||
if (t->own_logic)
|
||||
return t->download_pack(t, remote->repo, &remote->stats);
|
||||
|
||||
return git_fetch__download_pack(t, remote->repo, &remote->stats,
|
||||
progress_cb, progress_payload);
|
||||
|
||||
}
|
||||
|
||||
static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_transfer_progress *stats)
|
||||
{
|
||||
int recvd;
|
||||
|
||||
do {
|
||||
if (t->cancel.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
return GIT_EUSER;
|
||||
}
|
||||
|
||||
if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0)
|
||||
return -1;
|
||||
|
||||
gitno_consume_n(buf, buf->offset);
|
||||
|
||||
if ((recvd = gitno_recv(buf)) < 0)
|
||||
return -1;
|
||||
} while(recvd > 0);
|
||||
|
||||
if (git_indexer_stream_finalize(idx, stats))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct network_packetsize_payload
|
||||
{
|
||||
git_transfer_progress_callback callback;
|
||||
void *payload;
|
||||
git_transfer_progress *stats;
|
||||
git_off_t last_fired_bytes;
|
||||
};
|
||||
|
||||
static void network_packetsize(int received, void *payload)
|
||||
{
|
||||
struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
|
||||
|
||||
/* Accumulate bytes */
|
||||
npp->stats->received_bytes += received;
|
||||
|
||||
/* Fire notification if the threshold is reached */
|
||||
if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
|
||||
npp->last_fired_bytes = npp->stats->received_bytes;
|
||||
npp->callback(npp->stats, npp->payload);
|
||||
}
|
||||
}
|
||||
|
||||
/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */
|
||||
int git_fetch__download_pack(
|
||||
git_transport *t,
|
||||
git_repository *repo,
|
||||
git_transfer_progress *stats,
|
||||
git_transfer_progress_callback progress_cb,
|
||||
void *progress_payload)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
git_indexer_stream *idx = NULL;
|
||||
int error = -1;
|
||||
struct network_packetsize_payload npp = {0};
|
||||
|
||||
if (progress_cb) {
|
||||
npp.callback = progress_cb;
|
||||
npp.payload = progress_payload;
|
||||
npp.stats = stats;
|
||||
buf->packetsize_cb = &network_packetsize;
|
||||
buf->packetsize_payload = &npp;
|
||||
}
|
||||
|
||||
if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0)
|
||||
return -1;
|
||||
|
||||
if (git_indexer_stream_new(&idx, git_buf_cstr(&path), progress_cb, progress_payload) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_free(&path);
|
||||
memset(stats, 0, sizeof(git_transfer_progress));
|
||||
|
||||
/*
|
||||
* If the remote doesn't support the side-band, we can feed
|
||||
* the data directly to the indexer. Otherwise, we need to
|
||||
* check which one belongs there.
|
||||
*/
|
||||
if (!t->caps.side_band && !t->caps.side_band_64k) {
|
||||
if (no_sideband(t, idx, buf, stats) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_indexer_stream_free(idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
do {
|
||||
git_pkt *pkt;
|
||||
|
||||
if (t->cancel.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
error = GIT_EUSER;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (recv_pkt(&pkt, buf) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (pkt->type == GIT_PKT_PROGRESS) {
|
||||
if (t->progress_cb) {
|
||||
git_pkt_progress *p = (git_pkt_progress *) pkt;
|
||||
t->progress_cb(p->data, p->len, t->cb_data);
|
||||
}
|
||||
git__free(pkt);
|
||||
} else if (pkt->type == GIT_PKT_DATA) {
|
||||
git_pkt_data *p = (git_pkt_data *) pkt;
|
||||
if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0)
|
||||
goto on_error;
|
||||
|
||||
git__free(pkt);
|
||||
} else if (pkt->type == GIT_PKT_FLUSH) {
|
||||
/* A flush indicates the end of the packfile */
|
||||
git__free(pkt);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
if (git_indexer_stream_finalize(idx, stats) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_indexer_stream_free(idx);
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_buf_free(&path);
|
||||
git_indexer_stream_free(idx);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_fetch_setup_walk(git_revwalk **out, git_repository *repo)
|
||||
{
|
||||
git_revwalk *walk;
|
||||
git_strarray refs;
|
||||
unsigned int i;
|
||||
git_reference *ref;
|
||||
|
||||
if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_revwalk_new(&walk, repo) < 0)
|
||||
return -1;
|
||||
|
||||
git_revwalk_sorting(walk, GIT_SORT_TIME);
|
||||
|
||||
for (i = 0; i < refs.count; ++i) {
|
||||
/* No tags */
|
||||
if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
|
||||
continue;
|
||||
|
||||
if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
|
||||
continue;
|
||||
if (git_revwalk_push(walk, git_reference_oid(ref)) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
git_strarray_free(&refs);
|
||||
*out = walk;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_reference_free(ref);
|
||||
git_strarray_free(&refs);
|
||||
return -1;
|
||||
return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload);
|
||||
}
|
||||
|
195
src/netops.c
195
src/netops.c
@ -14,12 +14,13 @@
|
||||
#else
|
||||
# include <ws2tcpip.h>
|
||||
# ifdef _MSC_VER
|
||||
# pragma comment(lib, "ws2_32.lib")
|
||||
# pragma comment(lib, "ws2_32")
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef GIT_SSL
|
||||
# include <openssl/ssl.h>
|
||||
# include <openssl/err.h>
|
||||
# include <openssl/x509v3.h>
|
||||
#endif
|
||||
|
||||
@ -30,7 +31,6 @@
|
||||
#include "netops.h"
|
||||
#include "posix.h"
|
||||
#include "buffer.h"
|
||||
#include "transport.h"
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
static void net_set_error(const char *str)
|
||||
@ -41,6 +41,8 @@ static void net_set_error(const char *str)
|
||||
size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
0, error, 0, (LPSTR)&err_str, 0, 0);
|
||||
|
||||
GIT_UNUSED(size);
|
||||
|
||||
giterr_set(GITERR_NET, "%s: %s", str, err_str);
|
||||
LocalFree(err_str);
|
||||
}
|
||||
@ -108,8 +110,8 @@ static int gitno__recv_ssl(gitno_buffer *buf)
|
||||
int ret;
|
||||
|
||||
do {
|
||||
ret = SSL_read(buf->ssl->ssl, buf->data + buf->offset, buf->len - buf->offset);
|
||||
} while (SSL_get_error(buf->ssl->ssl, ret) == SSL_ERROR_WANT_READ);
|
||||
ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);
|
||||
} while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);
|
||||
|
||||
if (ret < 0) {
|
||||
net_set_error("Error receiving socket data");
|
||||
@ -117,28 +119,26 @@ static int gitno__recv_ssl(gitno_buffer *buf)
|
||||
}
|
||||
|
||||
buf->offset += ret;
|
||||
if (buf->packetsize_cb) buf->packetsize_cb(ret, buf->packetsize_payload);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
int gitno__recv(gitno_buffer *buf)
|
||||
static int gitno__recv(gitno_buffer *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
|
||||
ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);
|
||||
if (ret < 0) {
|
||||
net_set_error("Error receiving socket data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf->offset += ret;
|
||||
if (buf->packetsize_cb) buf->packetsize_cb(ret, buf->packetsize_payload);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void gitno_buffer_setup_callback(
|
||||
git_transport *t,
|
||||
gitno_socket *socket,
|
||||
gitno_buffer *buf,
|
||||
char *data,
|
||||
size_t len,
|
||||
@ -148,20 +148,21 @@ void gitno_buffer_setup_callback(
|
||||
buf->data = data;
|
||||
buf->len = len;
|
||||
buf->offset = 0;
|
||||
buf->fd = t->socket;
|
||||
buf->socket = socket;
|
||||
buf->recv = recv;
|
||||
buf->cb_data = cb_data;
|
||||
}
|
||||
|
||||
void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len)
|
||||
void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
|
||||
{
|
||||
#ifdef GIT_SSL
|
||||
if (t->use_ssl) {
|
||||
gitno_buffer_setup_callback(t, buf, data, len, gitno__recv_ssl, NULL);
|
||||
buf->ssl = &t->ssl;
|
||||
} else
|
||||
if (socket->ssl.ctx) {
|
||||
gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL);
|
||||
|
||||
gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
|
||||
}
|
||||
|
||||
/* Consume up to ptr and move the rest of the buffer to the beginning */
|
||||
@ -187,31 +188,23 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
|
||||
buf->offset -= cons;
|
||||
}
|
||||
|
||||
int gitno_ssl_teardown(git_transport *t)
|
||||
#ifdef GIT_SSL
|
||||
|
||||
static int gitno_ssl_teardown(gitno_ssl *ssl)
|
||||
{
|
||||
#ifdef GIT_SSL
|
||||
int ret;
|
||||
#endif
|
||||
|
||||
if (!t->use_ssl)
|
||||
return 0;
|
||||
|
||||
#ifdef GIT_SSL
|
||||
|
||||
|
||||
do {
|
||||
ret = SSL_shutdown(t->ssl.ssl);
|
||||
ret = SSL_shutdown(ssl->ssl);
|
||||
} while (ret == 0);
|
||||
if (ret < 0)
|
||||
return ssl_set_error(&t->ssl, ret);
|
||||
return ssl_set_error(ssl, ret);
|
||||
|
||||
SSL_free(t->ssl.ssl);
|
||||
SSL_CTX_free(t->ssl.ctx);
|
||||
#endif
|
||||
SSL_free(ssl->ssl);
|
||||
SSL_CTX_free(ssl->ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef GIT_SSL
|
||||
/* Match host names according to RFC 2818 rules */
|
||||
static int match_host(const char *pattern, const char *host)
|
||||
{
|
||||
@ -262,7 +255,7 @@ static int check_host_name(const char *name, const char *host)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verify_server_cert(git_transport *t, const char *host)
|
||||
static int verify_server_cert(gitno_ssl *ssl, const char *host)
|
||||
{
|
||||
X509 *cert;
|
||||
X509_NAME *peer_name;
|
||||
@ -275,7 +268,7 @@ static int verify_server_cert(git_transport *t, const char *host)
|
||||
void *addr;
|
||||
int i = -1,j;
|
||||
|
||||
if (SSL_get_verify_result(t->ssl.ssl) != X509_V_OK) {
|
||||
if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
|
||||
giterr_set(GITERR_SSL, "The SSL certificate is invalid");
|
||||
return -1;
|
||||
}
|
||||
@ -292,7 +285,7 @@ static int verify_server_cert(git_transport *t, const char *host)
|
||||
}
|
||||
|
||||
|
||||
cert = SSL_get_peer_certificate(t->ssl.ssl);
|
||||
cert = SSL_get_peer_certificate(ssl->ssl);
|
||||
|
||||
/* Check the alternative names */
|
||||
alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
||||
@ -376,7 +369,7 @@ static int verify_server_cert(git_transport *t, const char *host)
|
||||
|
||||
on_error:
|
||||
OPENSSL_free(peer_cn);
|
||||
return ssl_set_error(&t->ssl, 0);
|
||||
return ssl_set_error(ssl, 0);
|
||||
|
||||
cert_fail:
|
||||
OPENSSL_free(peer_cn);
|
||||
@ -384,51 +377,81 @@ cert_fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ssl_setup(git_transport *t, const char *host)
|
||||
static int ssl_setup(gitno_socket *socket, const char *host, int flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
t->ssl.ctx = SSL_CTX_new(SSLv23_method());
|
||||
if (t->ssl.ctx == NULL)
|
||||
return ssl_set_error(&t->ssl, 0);
|
||||
socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
|
||||
if (socket->ssl.ctx == NULL)
|
||||
return ssl_set_error(&socket->ssl, 0);
|
||||
|
||||
SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY);
|
||||
SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_NONE, NULL);
|
||||
if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx))
|
||||
return ssl_set_error(&t->ssl, 0);
|
||||
SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
|
||||
SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
|
||||
if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
|
||||
return ssl_set_error(&socket->ssl, 0);
|
||||
|
||||
t->ssl.ssl = SSL_new(t->ssl.ctx);
|
||||
if (t->ssl.ssl == NULL)
|
||||
return ssl_set_error(&t->ssl, 0);
|
||||
socket->ssl.ssl = SSL_new(socket->ssl.ctx);
|
||||
if (socket->ssl.ssl == NULL)
|
||||
return ssl_set_error(&socket->ssl, 0);
|
||||
|
||||
if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0)
|
||||
return ssl_set_error(&t->ssl, ret);
|
||||
if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
|
||||
return ssl_set_error(&socket->ssl, ret);
|
||||
|
||||
if ((ret = SSL_connect(t->ssl.ssl)) <= 0)
|
||||
return ssl_set_error(&t->ssl, ret);
|
||||
if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
|
||||
return ssl_set_error(&socket->ssl, ret);
|
||||
|
||||
if (t->check_cert && verify_server_cert(t, host) < 0)
|
||||
if ((GITNO_CONNECT_SSL_NO_CHECK_CERT & flags) || verify_server_cert(&socket->ssl, host) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int ssl_setup(git_transport *t, const char *host)
|
||||
{
|
||||
GIT_UNUSED(t);
|
||||
GIT_UNUSED(host);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int gitno_connect(git_transport *t, const char *host, const char *port)
|
||||
static int gitno__close(GIT_SOCKET s)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
if (SOCKET_ERROR == closesocket(s))
|
||||
return -1;
|
||||
|
||||
if (0 != WSACleanup()) {
|
||||
giterr_set(GITERR_OS, "Winsock cleanup failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return close(s);
|
||||
#endif
|
||||
}
|
||||
|
||||
int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
|
||||
{
|
||||
struct addrinfo *info = NULL, *p;
|
||||
struct addrinfo hints;
|
||||
int ret;
|
||||
GIT_SOCKET s = INVALID_SOCKET;
|
||||
int ret;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
/* on win32, the WSA context needs to be initialized
|
||||
* before any socket calls can be performed */
|
||||
WSADATA wsd;
|
||||
|
||||
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
|
||||
giterr_set(GITERR_OS, "Winsock init failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
|
||||
WSACleanup();
|
||||
giterr_set(GITERR_OS, "Winsock init failed");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Zero the socket structure provided */
|
||||
memset(s_out, 0x0, sizeof(gitno_socket));
|
||||
|
||||
memset(&hints, 0x0, sizeof(struct addrinfo));
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
@ -452,7 +475,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port)
|
||||
break;
|
||||
|
||||
/* If we can't connect, try the next one */
|
||||
gitno_close(s);
|
||||
gitno__close(s);
|
||||
s = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
@ -462,46 +485,56 @@ int gitno_connect(git_transport *t, const char *host, const char *port)
|
||||
return -1;
|
||||
}
|
||||
|
||||
t->socket = s;
|
||||
s_out->socket = s;
|
||||
p_freeaddrinfo(info);
|
||||
|
||||
if (t->use_ssl && ssl_setup(t, host) < 0)
|
||||
#ifdef GIT_SSL
|
||||
if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
|
||||
return -1;
|
||||
#else
|
||||
/* SSL is not supported */
|
||||
if (flags & GITNO_CONNECT_SSL) {
|
||||
giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef GIT_SSL
|
||||
static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len)
|
||||
static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
|
||||
{
|
||||
int ret;
|
||||
size_t off = 0;
|
||||
|
||||
GIT_UNUSED(flags);
|
||||
|
||||
while (off < len) {
|
||||
ret = SSL_write(ssl->ssl, msg + off, len - off);
|
||||
if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
|
||||
return ssl_set_error(ssl, ret);
|
||||
|
||||
off += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return off;
|
||||
}
|
||||
#endif
|
||||
|
||||
int gitno_send(git_transport *t, const char *msg, size_t len, int flags)
|
||||
int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
|
||||
{
|
||||
int ret;
|
||||
size_t off = 0;
|
||||
|
||||
#ifdef GIT_SSL
|
||||
if (t->use_ssl)
|
||||
return send_ssl(&t->ssl, msg, len);
|
||||
if (socket->ssl.ctx)
|
||||
return gitno_send_ssl(&socket->ssl, msg, len, flags);
|
||||
#endif
|
||||
|
||||
while (off < len) {
|
||||
errno = 0;
|
||||
ret = p_send(t->socket, msg + off, len - off, flags);
|
||||
ret = p_send(socket->socket, msg + off, len - off, flags);
|
||||
if (ret < 0) {
|
||||
net_set_error("Error sending data");
|
||||
return -1;
|
||||
@ -513,19 +546,17 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags)
|
||||
return (int)off;
|
||||
}
|
||||
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
int gitno_close(GIT_SOCKET s)
|
||||
int gitno_close(gitno_socket *s)
|
||||
{
|
||||
return closesocket(s) == SOCKET_ERROR ? -1 : 0;
|
||||
}
|
||||
#else
|
||||
int gitno_close(GIT_SOCKET s)
|
||||
{
|
||||
return close(s);
|
||||
}
|
||||
#ifdef GIT_SSL
|
||||
if (s->ssl.ctx &&
|
||||
gitno_ssl_teardown(&s->ssl) < 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return gitno__close(s->socket);
|
||||
}
|
||||
|
||||
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
|
||||
{
|
||||
fd_set fds;
|
||||
@ -535,10 +566,10 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
|
||||
tv.tv_usec = usec;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(buf->fd, &fds);
|
||||
FD_SET(buf->socket->socket, &fds);
|
||||
|
||||
/* The select(2) interface is silly */
|
||||
return select((int)buf->fd + 1, &fds, NULL, NULL, &tv);
|
||||
return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
|
||||
|
57
src/netops.h
57
src/netops.h
@ -10,33 +10,60 @@
|
||||
#include "posix.h"
|
||||
#include "common.h"
|
||||
|
||||
#ifdef GIT_SSL
|
||||
# include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
struct gitno_ssl {
|
||||
#ifdef GIT_SSL
|
||||
SSL_CTX *ctx;
|
||||
SSL *ssl;
|
||||
#else
|
||||
size_t dummy;
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef struct gitno_ssl gitno_ssl;
|
||||
|
||||
/* Represents a socket that may or may not be using SSL */
|
||||
struct gitno_socket {
|
||||
GIT_SOCKET socket;
|
||||
gitno_ssl ssl;
|
||||
};
|
||||
|
||||
typedef struct gitno_socket gitno_socket;
|
||||
|
||||
struct gitno_buffer {
|
||||
char *data;
|
||||
size_t len;
|
||||
size_t offset;
|
||||
GIT_SOCKET fd;
|
||||
#ifdef GIT_SSL
|
||||
struct gitno_ssl *ssl;
|
||||
#endif
|
||||
int (*recv)(gitno_buffer *buffer);
|
||||
gitno_socket *socket;
|
||||
int (*recv)(struct gitno_buffer *buffer);
|
||||
void *cb_data;
|
||||
void (*packetsize_cb)(int received, void *payload);
|
||||
void *packetsize_payload;
|
||||
};
|
||||
|
||||
void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len);
|
||||
void gitno_buffer_setup_callback(git_transport *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
|
||||
typedef struct gitno_buffer gitno_buffer;
|
||||
|
||||
/* Flags to gitno_connect */
|
||||
enum {
|
||||
/* Attempt to create an SSL connection. */
|
||||
GITNO_CONNECT_SSL = 1,
|
||||
|
||||
/* Valid only when GITNO_CONNECT_SSL is also specified.
|
||||
* Indicates that the server certificate should not be validated. */
|
||||
GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,
|
||||
};
|
||||
|
||||
void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len);
|
||||
void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
|
||||
int gitno_recv(gitno_buffer *buf);
|
||||
int gitno__recv(gitno_buffer *buf);
|
||||
|
||||
void gitno_consume(gitno_buffer *buf, const char *ptr);
|
||||
void gitno_consume_n(gitno_buffer *buf, size_t cons);
|
||||
|
||||
int gitno_connect(git_transport *t, const char *host, const char *port);
|
||||
int gitno_send(git_transport *t, const char *msg, size_t len, int flags);
|
||||
int gitno_close(GIT_SOCKET s);
|
||||
int gitno_ssl_teardown(git_transport *t);
|
||||
int gitno_send_chunk_size(int s, size_t len);
|
||||
int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags);
|
||||
int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags);
|
||||
int gitno_close(gitno_socket *s);
|
||||
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec);
|
||||
|
||||
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port);
|
||||
|
@ -604,8 +604,8 @@ on_error:
|
||||
|
||||
static int send_pack_file(void *buf, size_t size, void *data)
|
||||
{
|
||||
git_transport *t = (git_transport *)data;
|
||||
return gitno_send(t, buf, size, 0);
|
||||
gitno_socket *s = (gitno_socket *)data;
|
||||
return gitno_send(s, buf, size, 0);
|
||||
}
|
||||
|
||||
static int write_pack_buf(void *buf, size_t size, void *data)
|
||||
@ -1231,10 +1231,10 @@ static int prepare_pack(git_packbuilder *pb)
|
||||
|
||||
#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
|
||||
|
||||
int git_packbuilder_send(git_packbuilder *pb, git_transport *t)
|
||||
int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s)
|
||||
{
|
||||
PREPARE_PACK;
|
||||
return write_pack(pb, &send_pack_file, t);
|
||||
return write_pack(pb, &send_pack_file, s);
|
||||
}
|
||||
|
||||
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "buffer.h"
|
||||
#include "hash.h"
|
||||
#include "oidmap.h"
|
||||
#include "netops.h"
|
||||
|
||||
#include "git2/oid.h"
|
||||
|
||||
@ -81,7 +82,7 @@ struct git_packbuilder {
|
||||
bool done;
|
||||
};
|
||||
|
||||
int git_packbuilder_send(git_packbuilder *pb, git_transport *t);
|
||||
int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s);
|
||||
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
|
||||
|
||||
#endif /* INCLUDE_pack_objects_h__ */
|
||||
|
91
src/pkt.h
91
src/pkt.h
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_pkt_h__
|
||||
#define INCLUDE_pkt_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "transport.h"
|
||||
#include "buffer.h"
|
||||
#include "posix.h"
|
||||
#include "git2/net.h"
|
||||
|
||||
enum git_pkt_type {
|
||||
GIT_PKT_CMD,
|
||||
GIT_PKT_FLUSH,
|
||||
GIT_PKT_REF,
|
||||
GIT_PKT_HAVE,
|
||||
GIT_PKT_ACK,
|
||||
GIT_PKT_NAK,
|
||||
GIT_PKT_PACK,
|
||||
GIT_PKT_COMMENT,
|
||||
GIT_PKT_ERR,
|
||||
GIT_PKT_DATA,
|
||||
GIT_PKT_PROGRESS,
|
||||
};
|
||||
|
||||
/* Used for multi-ack */
|
||||
enum git_ack_status {
|
||||
GIT_ACK_NONE,
|
||||
GIT_ACK_CONTINUE,
|
||||
GIT_ACK_COMMON,
|
||||
GIT_ACK_READY
|
||||
};
|
||||
|
||||
/* This would be a flush pkt */
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
} git_pkt;
|
||||
|
||||
struct git_pkt_cmd {
|
||||
enum git_pkt_type type;
|
||||
char *cmd;
|
||||
char *path;
|
||||
char *host;
|
||||
};
|
||||
|
||||
/* This is a pkt-line with some info in it */
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
git_remote_head head;
|
||||
char *capabilities;
|
||||
} git_pkt_ref;
|
||||
|
||||
/* Useful later */
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
git_oid oid;
|
||||
enum git_ack_status status;
|
||||
} git_pkt_ack;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
char comment[GIT_FLEX_ARRAY];
|
||||
} git_pkt_comment;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
int len;
|
||||
char data[GIT_FLEX_ARRAY];
|
||||
} git_pkt_data;
|
||||
|
||||
typedef git_pkt_data git_pkt_progress;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
char error[GIT_FLEX_ARRAY];
|
||||
} git_pkt_err;
|
||||
|
||||
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
|
||||
int git_pkt_buffer_flush(git_buf *buf);
|
||||
int git_pkt_send_flush(GIT_SOCKET s);
|
||||
int git_pkt_buffer_done(git_buf *buf);
|
||||
int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf);
|
||||
int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
|
||||
void git_pkt_free(git_pkt *pkt);
|
||||
|
||||
#endif
|
110
src/protocol.c
110
src/protocol.c
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* 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 "common.h"
|
||||
#include "protocol.h"
|
||||
#include "pkt.h"
|
||||
#include "buffer.h"
|
||||
|
||||
int git_protocol_store_refs(git_transport *t, int flushes)
|
||||
{
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
git_vector *refs = &t->refs;
|
||||
int error, flush = 0, recvd;
|
||||
const char *line_end;
|
||||
git_pkt *pkt;
|
||||
|
||||
do {
|
||||
if (buf->offset > 0)
|
||||
error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
|
||||
else
|
||||
error = GIT_EBUFS;
|
||||
|
||||
if (error < 0 && error != GIT_EBUFS)
|
||||
return -1;
|
||||
|
||||
if (error == GIT_EBUFS) {
|
||||
if ((recvd = gitno_recv(buf)) < 0)
|
||||
return -1;
|
||||
|
||||
if (recvd == 0 && !flush) {
|
||||
giterr_set(GITERR_NET, "Early EOF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
gitno_consume(buf, line_end);
|
||||
if (pkt->type == GIT_PKT_ERR) {
|
||||
giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
|
||||
git__free(pkt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)
|
||||
return -1;
|
||||
|
||||
if (pkt->type == GIT_PKT_FLUSH) {
|
||||
flush++;
|
||||
git_pkt_free(pkt);
|
||||
}
|
||||
} while (flush < flushes);
|
||||
|
||||
return flush;
|
||||
}
|
||||
|
||||
int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps)
|
||||
{
|
||||
const char *ptr;
|
||||
|
||||
/* No refs or capabilites, odd but not a problem */
|
||||
if (pkt == NULL || pkt->capabilities == NULL)
|
||||
return 0;
|
||||
|
||||
ptr = pkt->capabilities;
|
||||
while (ptr != NULL && *ptr != '\0') {
|
||||
if (*ptr == ' ')
|
||||
ptr++;
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
|
||||
caps->common = caps->ofs_delta = 1;
|
||||
ptr += strlen(GIT_CAP_OFS_DELTA);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
|
||||
caps->common = caps->multi_ack = 1;
|
||||
ptr += strlen(GIT_CAP_MULTI_ACK);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
|
||||
caps->common = caps->include_tag = 1;
|
||||
ptr += strlen(GIT_CAP_INCLUDE_TAG);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Keep side-band check after side-band-64k */
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
|
||||
caps->common = caps->side_band_64k = 1;
|
||||
ptr += strlen(GIT_CAP_SIDE_BAND_64K);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
|
||||
caps->common = caps->side_band = 1;
|
||||
ptr += strlen(GIT_CAP_SIDE_BAND);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
/* We don't know this capability, so skip it */
|
||||
ptr = strchr(ptr, ' ');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_protocol_h__
|
||||
#define INCLUDE_protocol_h__
|
||||
|
||||
#include "transport.h"
|
||||
#include "buffer.h"
|
||||
#include "pkt.h"
|
||||
|
||||
int git_protocol_store_refs(git_transport *t, int flushes);
|
||||
int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps);
|
||||
|
||||
#define GIT_SIDE_BAND_DATA 1
|
||||
#define GIT_SIDE_BAND_PROGRESS 2
|
||||
#define GIT_SIDE_BAND_ERROR 3
|
||||
|
||||
#endif
|
131
src/remote.c
131
src/remote.c
@ -14,7 +14,6 @@
|
||||
#include "remote.h"
|
||||
#include "fetch.h"
|
||||
#include "refs.h"
|
||||
#include "pkt.h"
|
||||
|
||||
#include <regex.h>
|
||||
|
||||
@ -464,23 +463,30 @@ int git_remote_connect(git_remote *remote, int direction)
|
||||
{
|
||||
git_transport *t;
|
||||
const char *url;
|
||||
int flags = GIT_TRANSPORTFLAGS_NONE;
|
||||
|
||||
assert(remote);
|
||||
|
||||
t = remote->transport;
|
||||
|
||||
url = git_remote__urlfordirection(remote, direction);
|
||||
if (url == NULL )
|
||||
return -1;
|
||||
|
||||
if (git_transport_new(&t, url) < 0)
|
||||
/* A transport could have been supplied in advance with
|
||||
* git_remote_set_transport */
|
||||
if (!t && git_transport_new(&t, url) < 0)
|
||||
return -1;
|
||||
|
||||
t->progress_cb = remote->callbacks.progress;
|
||||
t->cb_data = remote->callbacks.data;
|
||||
|
||||
t->check_cert = remote->check_cert;
|
||||
if (t->connect(t, direction) < 0) {
|
||||
if (t->set_callbacks &&
|
||||
t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.data) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (!remote->check_cert)
|
||||
flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
|
||||
|
||||
if (t->connect(t, url, direction, flags) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
remote->transport = t;
|
||||
|
||||
@ -493,30 +499,14 @@ on_error:
|
||||
|
||||
int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
|
||||
{
|
||||
git_vector *refs = &remote->transport->refs;
|
||||
unsigned int i;
|
||||
git_pkt *p = NULL;
|
||||
|
||||
assert(remote);
|
||||
|
||||
if (!remote->transport || !remote->transport->connected) {
|
||||
if (!remote->transport) {
|
||||
giterr_set(GITERR_NET, "The remote is not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_vector_foreach(refs, i, p) {
|
||||
git_pkt_ref *pkt = NULL;
|
||||
|
||||
if (p->type != GIT_PKT_REF)
|
||||
continue;
|
||||
|
||||
pkt = (git_pkt_ref *)p;
|
||||
|
||||
if (list_cb(&pkt->head, payload))
|
||||
return GIT_EUSER;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return remote->transport->ls(remote->transport, list_cb, payload);
|
||||
}
|
||||
|
||||
int git_remote_download(
|
||||
@ -534,54 +524,61 @@ int git_remote_download(
|
||||
return git_fetch_download_pack(remote, progress_cb, progress_payload);
|
||||
}
|
||||
|
||||
static int update_tips_callback(git_remote_head *head, void *payload)
|
||||
{
|
||||
git_vector *refs = (git_vector *)payload;
|
||||
git_vector_insert(refs, head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_remote_update_tips(git_remote *remote)
|
||||
{
|
||||
int error = 0, autotag;
|
||||
unsigned int i = 0;
|
||||
git_buf refname = GIT_BUF_INIT;
|
||||
git_oid old;
|
||||
git_pkt *pkt;
|
||||
git_odb *odb;
|
||||
git_vector *refs;
|
||||
git_remote_head *head;
|
||||
git_reference *ref;
|
||||
struct git_refspec *spec;
|
||||
git_refspec tagspec;
|
||||
git_vector refs;
|
||||
|
||||
assert(remote);
|
||||
|
||||
refs = &remote->transport->refs;
|
||||
spec = &remote->fetch;
|
||||
|
||||
if (refs->length == 0)
|
||||
return 0;
|
||||
|
||||
|
||||
if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
|
||||
return -1;
|
||||
|
||||
/* HEAD is only allowed to be the first in the list */
|
||||
pkt = refs->contents[0];
|
||||
head = &((git_pkt_ref *)pkt)->head;
|
||||
if (!strcmp(head->name, GIT_HEAD_FILE)) {
|
||||
if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
|
||||
return -1;
|
||||
/* Make a copy of the transport's refs */
|
||||
if (git_vector_init(&refs, 16, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
i = 1;
|
||||
git_reference_free(ref);
|
||||
if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0)
|
||||
goto on_error;
|
||||
|
||||
/* Let's go find HEAD, if it exists. Check only the first ref in the vector. */
|
||||
if (refs.length > 0) {
|
||||
head = (git_remote_head *)refs.contents[0];
|
||||
|
||||
if (!strcmp(head->name, GIT_HEAD_FILE)) {
|
||||
if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
|
||||
goto on_error;
|
||||
|
||||
i = 1;
|
||||
git_reference_free(ref);
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < refs->length; ++i) {
|
||||
git_pkt *pkt = refs->contents[i];
|
||||
for (; i < refs.length; ++i) {
|
||||
head = (git_remote_head *)refs.contents[i];
|
||||
autotag = 0;
|
||||
|
||||
if (pkt->type == GIT_PKT_REF)
|
||||
head = &((git_pkt_ref *)pkt)->head;
|
||||
else
|
||||
continue;
|
||||
|
||||
/* Ignore malformed ref names (which also saves us from tag^{} */
|
||||
if (!git_reference_is_valid_name(head->name))
|
||||
continue;
|
||||
@ -630,11 +627,13 @@ int git_remote_update_tips(git_remote *remote)
|
||||
}
|
||||
}
|
||||
|
||||
git_vector_free(&refs);
|
||||
git_refspec__free(&tagspec);
|
||||
git_buf_free(&refname);
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_vector_free(&refs);
|
||||
git_refspec__free(&tagspec);
|
||||
git_buf_free(&refname);
|
||||
return -1;
|
||||
@ -643,21 +642,31 @@ on_error:
|
||||
|
||||
int git_remote_connected(git_remote *remote)
|
||||
{
|
||||
int connected;
|
||||
|
||||
assert(remote);
|
||||
return remote->transport == NULL ? 0 : remote->transport->connected;
|
||||
|
||||
if (!remote->transport || !remote->transport->is_connected)
|
||||
return 0;
|
||||
|
||||
/* Ask the transport if it's connected. */
|
||||
remote->transport->is_connected(remote->transport, &connected);
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
void git_remote_stop(git_remote *remote)
|
||||
{
|
||||
git_atomic_set(&remote->transport->cancel, 1);
|
||||
if (remote->transport->cancel)
|
||||
remote->transport->cancel(remote->transport);
|
||||
}
|
||||
|
||||
void git_remote_disconnect(git_remote *remote)
|
||||
{
|
||||
assert(remote);
|
||||
|
||||
if (remote->transport != NULL && remote->transport->connected)
|
||||
remote->transport->close(remote->transport);
|
||||
if (git_remote_connected(remote))
|
||||
remote->transport->close(remote->transport);
|
||||
}
|
||||
|
||||
void git_remote_free(git_remote *remote)
|
||||
@ -786,10 +795,24 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
|
||||
|
||||
memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks));
|
||||
|
||||
if (remote->transport && remote->transport->set_callbacks)
|
||||
remote->transport->set_callbacks(remote->transport,
|
||||
remote->callbacks.progress,
|
||||
NULL,
|
||||
remote->callbacks.data);
|
||||
}
|
||||
|
||||
int git_remote_set_transport(git_remote *remote, git_transport *transport)
|
||||
{
|
||||
assert(remote && transport);
|
||||
|
||||
if (remote->transport) {
|
||||
remote->transport->progress_cb = remote->callbacks.progress;
|
||||
remote->transport->cb_data = remote->callbacks.data;
|
||||
giterr_set(GITERR_NET, "A transport is already bound to this remote");
|
||||
return -1;
|
||||
}
|
||||
|
||||
remote->transport = transport;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const git_transfer_progress* git_remote_stats(git_remote *remote)
|
||||
|
@ -8,9 +8,9 @@
|
||||
#define INCLUDE_remote_h__
|
||||
|
||||
#include "git2/remote.h"
|
||||
#include "git2/transport.h"
|
||||
|
||||
#include "refspec.h"
|
||||
#include "transport.h"
|
||||
#include "repository.h"
|
||||
|
||||
#define GIT_REMOTE_ORIGIN "origin"
|
||||
|
@ -8,52 +8,78 @@
|
||||
#include "git2/types.h"
|
||||
#include "git2/remote.h"
|
||||
#include "git2/net.h"
|
||||
#include "transport.h"
|
||||
#include "git2/transport.h"
|
||||
#include "path.h"
|
||||
|
||||
static struct {
|
||||
typedef struct transport_definition {
|
||||
char *prefix;
|
||||
unsigned priority;
|
||||
git_transport_cb fn;
|
||||
} transports[] = {
|
||||
{"git://", git_transport_git},
|
||||
{"http://", git_transport_http},
|
||||
{"https://", git_transport_https},
|
||||
{"file://", git_transport_local},
|
||||
{"git+ssh://", git_transport_dummy},
|
||||
{"ssh+git://", git_transport_dummy},
|
||||
{NULL, 0}
|
||||
void *param;
|
||||
} transport_definition;
|
||||
|
||||
static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
|
||||
static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
|
||||
|
||||
static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };
|
||||
static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };
|
||||
|
||||
static transport_definition transports[] = {
|
||||
{"git://", 1, git_transport_smart, &git_subtransport_definition},
|
||||
{"http://", 1, git_transport_smart, &http_subtransport_definition},
|
||||
{"https://", 1, git_transport_smart, &http_subtransport_definition},
|
||||
{"file://", 1, git_transport_local, NULL},
|
||||
{"git+ssh://", 1, git_transport_dummy, NULL},
|
||||
{"ssh+git://", 1, git_transport_dummy, NULL},
|
||||
{NULL, 0, 0}
|
||||
};
|
||||
|
||||
#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1
|
||||
|
||||
static git_transport_cb transport_find_fn(const char *url)
|
||||
static int transport_find_fn(const char *url, git_transport_cb *callback, void **param)
|
||||
{
|
||||
size_t i = 0;
|
||||
unsigned priority = 0;
|
||||
transport_definition *definition = NULL, *definition_iter;
|
||||
|
||||
// First, check to see if it's an obvious URL, which a URL scheme
|
||||
for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
|
||||
if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix)))
|
||||
return transports[i].fn;
|
||||
definition_iter = &transports[i];
|
||||
|
||||
if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
|
||||
continue;
|
||||
|
||||
if (definition_iter->priority > priority)
|
||||
definition = definition_iter;
|
||||
}
|
||||
|
||||
/* still here? Check to see if the path points to a file on the local file system */
|
||||
if ((git_path_exists(url) == 0) && git_path_isdir(url))
|
||||
return &git_transport_local;
|
||||
if (!definition) {
|
||||
/* still here? Check to see if the path points to a file on the local file system */
|
||||
if ((git_path_exists(url) == 0) && git_path_isdir(url))
|
||||
definition = &local_transport_definition;
|
||||
|
||||
/* It could be a SSH remote path. Check to see if there's a : */
|
||||
if (strrchr(url, ':'))
|
||||
return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */
|
||||
/* It could be a SSH remote path. Check to see if there's a : */
|
||||
if (strrchr(url, ':'))
|
||||
definition = &dummy_transport_definition; /* SSH is an unsupported transport mechanism in this version of libgit2 */
|
||||
}
|
||||
|
||||
return NULL;
|
||||
if (!definition)
|
||||
return -1;
|
||||
|
||||
*callback = definition->fn;
|
||||
*param = definition->param;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**************
|
||||
* Public API *
|
||||
**************/
|
||||
|
||||
int git_transport_dummy(git_transport **transport)
|
||||
int git_transport_dummy(git_transport **transport, void *param)
|
||||
{
|
||||
GIT_UNUSED(transport);
|
||||
GIT_UNUSED(param);
|
||||
giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
|
||||
return -1;
|
||||
}
|
||||
@ -62,22 +88,18 @@ int git_transport_new(git_transport **out, const char *url)
|
||||
{
|
||||
git_transport_cb fn;
|
||||
git_transport *transport;
|
||||
void *param;
|
||||
int error;
|
||||
|
||||
fn = transport_find_fn(url);
|
||||
|
||||
if (fn == NULL) {
|
||||
if (transport_find_fn(url, &fn, ¶m) < 0) {
|
||||
giterr_set(GITERR_NET, "Unsupported URL protocol");
|
||||
return -1;
|
||||
}
|
||||
|
||||
error = fn(&transport);
|
||||
error = fn(&transport, param);
|
||||
if (error < 0)
|
||||
return error;
|
||||
|
||||
transport->url = git__strdup(url);
|
||||
GITERR_CHECK_ALLOC(transport->url);
|
||||
|
||||
*out = transport;
|
||||
|
||||
return 0;
|
||||
@ -86,12 +108,19 @@ int git_transport_new(git_transport **out, const char *url)
|
||||
/* from remote.h */
|
||||
int git_remote_valid_url(const char *url)
|
||||
{
|
||||
return transport_find_fn(url) != NULL;
|
||||
git_transport_cb fn;
|
||||
void *param;
|
||||
|
||||
return !transport_find_fn(url, &fn, ¶m);
|
||||
}
|
||||
|
||||
int git_remote_supported_url(const char* url)
|
||||
{
|
||||
git_transport_cb transport_fn = transport_find_fn(url);
|
||||
git_transport_cb fn;
|
||||
void *param;
|
||||
|
||||
return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy));
|
||||
if (transport_find_fn(url, &fn, ¶m) < 0)
|
||||
return 0;
|
||||
|
||||
return fn != &git_transport_dummy;
|
||||
}
|
||||
|
148
src/transport.h
148
src/transport.h
@ -1,148 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef INCLUDE_transport_h__
|
||||
#define INCLUDE_transport_h__
|
||||
|
||||
#include "git2/net.h"
|
||||
#include "git2/indexer.h"
|
||||
#include "vector.h"
|
||||
#include "posix.h"
|
||||
#include "common.h"
|
||||
#include "netops.h"
|
||||
#ifdef GIT_SSL
|
||||
# include <openssl/ssl.h>
|
||||
# include <openssl/err.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define GIT_CAP_OFS_DELTA "ofs-delta"
|
||||
#define GIT_CAP_MULTI_ACK "multi_ack"
|
||||
#define GIT_CAP_SIDE_BAND "side-band"
|
||||
#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
|
||||
#define GIT_CAP_INCLUDE_TAG "include-tag"
|
||||
|
||||
typedef struct git_transport_caps {
|
||||
int common:1,
|
||||
ofs_delta:1,
|
||||
multi_ack: 1,
|
||||
side_band:1,
|
||||
side_band_64k:1,
|
||||
include_tag:1;
|
||||
} git_transport_caps;
|
||||
|
||||
#ifdef GIT_SSL
|
||||
typedef struct gitno_ssl {
|
||||
SSL_CTX *ctx;
|
||||
SSL *ssl;
|
||||
} gitno_ssl;
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* A day in the life of a network operation
|
||||
* ========================================
|
||||
*
|
||||
* The library gets told to ls-remote/push/fetch on/to/from some
|
||||
* remote. We look at the URL of the remote and fill the function
|
||||
* table with whatever is appropriate (the remote may be git over git,
|
||||
* ssh or http(s). It may even be an hg or svn repository, the library
|
||||
* at this level doesn't care, it just calls the helpers.
|
||||
*
|
||||
* The first call is to ->connect() which connects to the remote,
|
||||
* making use of the direction if necessary. This function must also
|
||||
* store the remote heads and any other information it needs.
|
||||
*
|
||||
* The next useful step is to call ->ls() to get the list of
|
||||
* references available to the remote. These references may have been
|
||||
* collected on connect, or we may build them now. For ls-remote,
|
||||
* nothing else is needed other than closing the connection.
|
||||
* Otherwise, the higher leves decide which objects we want to
|
||||
* have. ->send_have() is used to tell the other end what we have. If
|
||||
* we do need to download a pack, ->download_pack() is called.
|
||||
*
|
||||
* When we're done, we call ->close() to close the
|
||||
* connection. ->free() takes care of freeing all the resources.
|
||||
*/
|
||||
|
||||
struct git_transport {
|
||||
/**
|
||||
* Where the repo lives
|
||||
*/
|
||||
char *url;
|
||||
/**
|
||||
* Whether we want to push or fetch
|
||||
*/
|
||||
int direction : 1, /* 0 fetch, 1 push */
|
||||
connected : 1,
|
||||
check_cert: 1,
|
||||
use_ssl : 1,
|
||||
own_logic: 1, /* transitional */
|
||||
rpc: 1; /* git-speak for the HTTP transport */
|
||||
#ifdef GIT_SSL
|
||||
struct gitno_ssl ssl;
|
||||
#endif
|
||||
git_vector refs;
|
||||
git_vector common;
|
||||
gitno_buffer buffer;
|
||||
GIT_SOCKET socket;
|
||||
git_transport_caps caps;
|
||||
void *cb_data;
|
||||
git_atomic cancel;
|
||||
|
||||
/**
|
||||
* Connect and store the remote heads
|
||||
*/
|
||||
int (*connect)(struct git_transport *transport, int dir);
|
||||
/**
|
||||
* Send our side of a negotiation
|
||||
*/
|
||||
int (*negotiation_step)(struct git_transport *transport, void *data, size_t len);
|
||||
/**
|
||||
* Push the changes over
|
||||
*/
|
||||
int (*push)(struct git_transport *transport);
|
||||
/**
|
||||
* Negotiate the minimal amount of objects that need to be
|
||||
* retrieved
|
||||
*/
|
||||
int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants);
|
||||
/**
|
||||
* Download the packfile
|
||||
*/
|
||||
int (*download_pack)(struct git_transport *transport, git_repository *repo, git_transfer_progress *stats);
|
||||
/**
|
||||
* Close the connection
|
||||
*/
|
||||
int (*close)(struct git_transport *transport);
|
||||
/**
|
||||
* Free the associated resources
|
||||
*/
|
||||
void (*free)(struct git_transport *transport);
|
||||
/**
|
||||
* Callbacks for the progress and error output
|
||||
*/
|
||||
void (*progress_cb)(const char *str, int len, void *data);
|
||||
void (*error_cb)(const char *str, int len, void *data);
|
||||
};
|
||||
|
||||
|
||||
int git_transport_new(struct git_transport **transport, const char *url);
|
||||
int git_transport_local(struct git_transport **transport);
|
||||
int git_transport_git(struct git_transport **transport);
|
||||
int git_transport_http(struct git_transport **transport);
|
||||
int git_transport_https(struct git_transport **transport);
|
||||
int git_transport_dummy(struct git_transport **transport);
|
||||
|
||||
/**
|
||||
Returns true if the passed URL is valid (a URL with a Git supported scheme,
|
||||
or pointing to an existing path)
|
||||
*/
|
||||
int git_transport_valid_url(const char *url);
|
||||
|
||||
typedef int (*git_transport_cb)(git_transport **transport);
|
||||
|
||||
#endif
|
@ -5,40 +5,37 @@
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#include "git2/net.h"
|
||||
#include "git2/common.h"
|
||||
#include "git2/types.h"
|
||||
#include "git2/errors.h"
|
||||
#include "git2/net.h"
|
||||
#include "git2/revwalk.h"
|
||||
|
||||
#include "vector.h"
|
||||
#include "transport.h"
|
||||
#include "pkt.h"
|
||||
#include "common.h"
|
||||
#include "git2.h"
|
||||
#include "buffer.h"
|
||||
#include "netops.h"
|
||||
#include "filebuf.h"
|
||||
#include "repository.h"
|
||||
#include "fetch.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
|
||||
|
||||
static const char prefix_git[] = "git://";
|
||||
static const char cmd_uploadpack[] = "git-upload-pack";
|
||||
|
||||
typedef struct {
|
||||
git_transport parent;
|
||||
char buff[65536];
|
||||
#ifdef GIT_WIN32
|
||||
WSADATA wsd;
|
||||
#endif
|
||||
} transport_git;
|
||||
git_smart_subtransport_stream parent;
|
||||
gitno_socket socket;
|
||||
const char *cmd;
|
||||
char *url;
|
||||
unsigned sent_command : 1;
|
||||
} git_stream;
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
git_transport *owner;
|
||||
git_stream *current_stream;
|
||||
} git_subtransport;
|
||||
|
||||
/*
|
||||
* Create a git procol request.
|
||||
* Create a git protocol request.
|
||||
*
|
||||
* For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
|
||||
*/
|
||||
static int gen_proto(git_buf *request, const char *cmd, const char *url)
|
||||
{
|
||||
char *delim, *repo;
|
||||
char default_command[] = "git-upload-pack";
|
||||
char host[] = "host=";
|
||||
size_t len;
|
||||
|
||||
@ -54,9 +51,6 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
|
||||
if (delim == NULL)
|
||||
delim = strchr(url, '/');
|
||||
|
||||
if (cmd == NULL)
|
||||
cmd = default_command;
|
||||
|
||||
len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
|
||||
|
||||
git_buf_grow(request, len);
|
||||
@ -71,175 +65,211 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_request(git_transport *t, const char *cmd, const char *url)
|
||||
static int send_command(git_stream *s)
|
||||
{
|
||||
int error;
|
||||
git_buf request = GIT_BUF_INIT;
|
||||
|
||||
error = gen_proto(&request, cmd, url);
|
||||
error = gen_proto(&request, s->cmd, s->url);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = gitno_send(t, request.ptr, request.size, 0);
|
||||
/* It looks like negative values are errors here, and positive values
|
||||
* are the number of bytes sent. */
|
||||
error = gitno_send(&s->socket, request.ptr, request.size, 0);
|
||||
|
||||
if (error >= 0)
|
||||
s->sent_command = 1;
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&request);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the URL and connect to a server, storing the socket in
|
||||
* out. For convenience this also takes care of asking for the remote
|
||||
* refs
|
||||
*/
|
||||
static int do_connect(transport_git *t, const char *url)
|
||||
static int git_stream_read(
|
||||
git_smart_subtransport_stream *stream,
|
||||
char *buffer,
|
||||
size_t buf_size,
|
||||
size_t *bytes_read)
|
||||
{
|
||||
git_stream *s = (git_stream *)stream;
|
||||
gitno_buffer buf;
|
||||
|
||||
*bytes_read = 0;
|
||||
|
||||
if (!s->sent_command && send_command(s) < 0)
|
||||
return -1;
|
||||
|
||||
gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
|
||||
|
||||
if (gitno_recv(&buf) < 0)
|
||||
return -1;
|
||||
|
||||
*bytes_read = buf.offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_stream_write(
|
||||
git_smart_subtransport_stream *stream,
|
||||
const char *buffer,
|
||||
size_t len)
|
||||
{
|
||||
git_stream *s = (git_stream *)stream;
|
||||
|
||||
if (!s->sent_command && send_command(s) < 0)
|
||||
return -1;
|
||||
|
||||
return gitno_send(&s->socket, buffer, len, 0);
|
||||
}
|
||||
|
||||
static void git_stream_free(git_smart_subtransport_stream *stream)
|
||||
{
|
||||
git_stream *s = (git_stream *)stream;
|
||||
git_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
int ret;
|
||||
|
||||
GIT_UNUSED(ret);
|
||||
|
||||
t->current_stream = NULL;
|
||||
|
||||
if (s->socket.socket) {
|
||||
ret = gitno_close(&s->socket);
|
||||
assert(!ret);
|
||||
}
|
||||
|
||||
git__free(s->url);
|
||||
git__free(s);
|
||||
}
|
||||
|
||||
static int git_stream_alloc(
|
||||
git_subtransport *t,
|
||||
const char *url,
|
||||
const char *cmd,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
git_stream *s;
|
||||
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
s = (git_stream *)git__calloc(sizeof(git_stream), 1);
|
||||
GITERR_CHECK_ALLOC(s);
|
||||
|
||||
s->parent.subtransport = &t->parent;
|
||||
s->parent.read = git_stream_read;
|
||||
s->parent.write = git_stream_write;
|
||||
s->parent.free = git_stream_free;
|
||||
|
||||
s->cmd = cmd;
|
||||
s->url = git__strdup(url);
|
||||
|
||||
if (!s->url) {
|
||||
git__free(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*stream = &s->parent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_git_uploadpack_ls(
|
||||
git_subtransport *t,
|
||||
const char *url,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
char *host, *port;
|
||||
const char prefix[] = "git://";
|
||||
git_stream *s;
|
||||
|
||||
if (!git__prefixcmp(url, prefix))
|
||||
url += strlen(prefix);
|
||||
*stream = NULL;
|
||||
|
||||
if (!git__prefixcmp(url, prefix_git))
|
||||
url += strlen(prefix_git);
|
||||
|
||||
if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
|
||||
return -1;
|
||||
|
||||
s = (git_stream *)*stream;
|
||||
|
||||
if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
|
||||
return -1;
|
||||
|
||||
if (gitno_connect((git_transport *)t, host, port) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (send_request((git_transport *)t, NULL, url) < 0)
|
||||
if (gitno_connect(&s->socket, host, port, 0) < 0)
|
||||
goto on_error;
|
||||
|
||||
t->current_stream = s;
|
||||
git__free(host);
|
||||
git__free(port);
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
if (*stream)
|
||||
git_stream_free(*stream);
|
||||
|
||||
git__free(host);
|
||||
git__free(port);
|
||||
gitno_close(t->parent.socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since this is a network connection, we need to parse and store the
|
||||
* pkt-lines at this stage and keep them there.
|
||||
*/
|
||||
static int git_connect(git_transport *transport, int direction)
|
||||
static int git_git_uploadpack(
|
||||
git_subtransport *t,
|
||||
const char *url,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
transport_git *t = (transport_git *) transport;
|
||||
GIT_UNUSED(url);
|
||||
|
||||
if (direction == GIT_DIR_PUSH) {
|
||||
giterr_set(GITERR_NET, "Pushing over git:// is not supported");
|
||||
return -1;
|
||||
if (t->current_stream) {
|
||||
*stream = &t->current_stream->parent;
|
||||
return 0;
|
||||
}
|
||||
|
||||
t->parent.direction = direction;
|
||||
|
||||
/* Connect and ask for the refs */
|
||||
if (do_connect(t, transport->url) < 0)
|
||||
return -1;
|
||||
|
||||
gitno_buffer_setup(transport, &transport->buffer, t->buff, sizeof(t->buff));
|
||||
|
||||
t->parent.connected = 1;
|
||||
if (git_protocol_store_refs(transport, 1) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int git_negotiation_step(struct git_transport *transport, void *data, size_t len)
|
||||
static int _git_action(
|
||||
git_smart_subtransport_stream **stream,
|
||||
git_smart_subtransport *smart_transport,
|
||||
const char *url,
|
||||
git_smart_service_t action)
|
||||
{
|
||||
return gitno_send(transport, data, len, 0);
|
||||
git_subtransport *t = (git_subtransport *) smart_transport;
|
||||
|
||||
switch (action) {
|
||||
case GIT_SERVICE_UPLOADPACK_LS:
|
||||
return git_git_uploadpack_ls(t, url, stream);
|
||||
|
||||
case GIT_SERVICE_UPLOADPACK:
|
||||
return git_git_uploadpack(t, url, stream);
|
||||
}
|
||||
|
||||
*stream = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int git_close(git_transport *t)
|
||||
static void _git_free(git_smart_subtransport *smart_transport)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
git_subtransport *t = (git_subtransport *) smart_transport;
|
||||
|
||||
if (git_pkt_buffer_flush(&buf) < 0)
|
||||
return -1;
|
||||
/* Can't do anything if there's an error, so don't bother checking */
|
||||
gitno_send(t, buf.ptr, buf.size, 0);
|
||||
git_buf_free(&buf);
|
||||
assert(!t->current_stream);
|
||||
|
||||
if (gitno_close(t->socket) < 0) {
|
||||
giterr_set(GITERR_NET, "Failed to close socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
t->connected = 0;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void git_free(git_transport *transport)
|
||||
{
|
||||
transport_git *t = (transport_git *) transport;
|
||||
git_vector *refs = &transport->refs;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < refs->length; ++i) {
|
||||
git_pkt *p = git_vector_get(refs, i);
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(refs);
|
||||
|
||||
refs = &transport->common;
|
||||
for (i = 0; i < refs->length; ++i) {
|
||||
git_pkt *p = git_vector_get(refs, i);
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(refs);
|
||||
|
||||
git__free(t->parent.url);
|
||||
git__free(t);
|
||||
}
|
||||
|
||||
int git_transport_git(git_transport **out)
|
||||
int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
|
||||
{
|
||||
transport_git *t;
|
||||
#ifdef GIT_WIN32
|
||||
int ret;
|
||||
#endif
|
||||
git_subtransport *t;
|
||||
|
||||
t = git__malloc(sizeof(transport_git));
|
||||
if (!out)
|
||||
return -1;
|
||||
|
||||
t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1);
|
||||
GITERR_CHECK_ALLOC(t);
|
||||
|
||||
memset(t, 0x0, sizeof(transport_git));
|
||||
if (git_vector_init(&t->parent.common, 8, NULL))
|
||||
goto on_error;
|
||||
|
||||
if (git_vector_init(&t->parent.refs, 16, NULL) < 0)
|
||||
goto on_error;
|
||||
|
||||
t->parent.connect = git_connect;
|
||||
t->parent.negotiation_step = git_negotiation_step;
|
||||
t->parent.close = git_close;
|
||||
t->parent.free = git_free;
|
||||
|
||||
*out = (git_transport *) t;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
|
||||
if (ret != 0) {
|
||||
git_free(*out);
|
||||
giterr_set(GITERR_NET, "Winsock init failed");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
t->owner = owner;
|
||||
t->parent.action = _git_action;
|
||||
t->parent.free = _git_free;
|
||||
|
||||
*out = (git_smart_subtransport *) t;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git__free(t);
|
||||
return -1;
|
||||
}
|
||||
|
@ -4,28 +4,22 @@
|
||||
* 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 <stdlib.h>
|
||||
#ifndef GIT_WINHTTP
|
||||
|
||||
#include "git2.h"
|
||||
#include "http_parser.h"
|
||||
|
||||
#include "transport.h"
|
||||
#include "common.h"
|
||||
#include "netops.h"
|
||||
#include "buffer.h"
|
||||
#include "pkt.h"
|
||||
#include "refs.h"
|
||||
#include "pack.h"
|
||||
#include "fetch.h"
|
||||
#include "filebuf.h"
|
||||
#include "repository.h"
|
||||
#include "protocol.h"
|
||||
#if GIT_WINHTTP
|
||||
# include <winhttp.h>
|
||||
# pragma comment(lib, "winhttp.lib")
|
||||
#endif
|
||||
#include "netops.h"
|
||||
|
||||
#define WIDEN2(s) L ## s
|
||||
#define WIDEN(s) WIDEN2(s)
|
||||
static const char *prefix_http = "http://";
|
||||
static const char *prefix_https = "https://";
|
||||
static const char *upload_pack_service = "upload-pack";
|
||||
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
|
||||
static const char *upload_pack_service_url = "/git-upload-pack";
|
||||
static const char *get_verb = "GET";
|
||||
static const char *post_verb = "POST";
|
||||
|
||||
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
|
||||
|
||||
enum last_cb {
|
||||
NONE,
|
||||
@ -34,43 +28,55 @@ enum last_cb {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_transport parent;
|
||||
http_parser_settings settings;
|
||||
git_buf buf;
|
||||
int error;
|
||||
int transfer_finished :1,
|
||||
ct_found :1,
|
||||
ct_finished :1,
|
||||
pack_ready :1;
|
||||
enum last_cb last_cb;
|
||||
http_parser parser;
|
||||
char *content_type;
|
||||
char *path;
|
||||
git_smart_subtransport_stream parent;
|
||||
const char *service;
|
||||
const char *service_url;
|
||||
const char *verb;
|
||||
unsigned sent_request : 1;
|
||||
} http_stream;
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
git_transport *owner;
|
||||
gitno_socket socket;
|
||||
const char *path;
|
||||
char *host;
|
||||
char *port;
|
||||
char *service;
|
||||
char buffer[65536];
|
||||
#ifdef GIT_WIN32
|
||||
WSADATA wsd;
|
||||
#endif
|
||||
#ifdef GIT_WINHTTP
|
||||
HINTERNET session;
|
||||
HINTERNET connection;
|
||||
HINTERNET request;
|
||||
#endif
|
||||
} transport_http;
|
||||
char *port;
|
||||
unsigned connected : 1,
|
||||
use_ssl : 1,
|
||||
no_check_cert : 1;
|
||||
|
||||
/* Parser structures */
|
||||
http_parser parser;
|
||||
http_parser_settings settings;
|
||||
gitno_buffer parse_buffer;
|
||||
git_buf parse_temp;
|
||||
char parse_buffer_data[2048];
|
||||
char *content_type;
|
||||
enum last_cb last_cb;
|
||||
int parse_error;
|
||||
unsigned parse_finished : 1,
|
||||
ct_found : 1,
|
||||
ct_finished : 1;
|
||||
} http_subtransport;
|
||||
|
||||
typedef struct {
|
||||
http_stream *s;
|
||||
http_subtransport *t;
|
||||
|
||||
/* Target buffer details from read() */
|
||||
char *buffer;
|
||||
size_t buf_size;
|
||||
size_t *bytes_read;
|
||||
} parser_context;
|
||||
|
||||
static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
|
||||
const char *service, ssize_t content_length, int ls)
|
||||
const char *service, const char *service_url, ssize_t content_length)
|
||||
{
|
||||
if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
|
||||
path = "/";
|
||||
|
||||
if (ls) {
|
||||
git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
|
||||
} else {
|
||||
git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
|
||||
}
|
||||
git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url);
|
||||
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
|
||||
git_buf_printf(buf, "Host: %s\r\n", host);
|
||||
if (content_length > 0) {
|
||||
@ -88,172 +94,11 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls)
|
||||
{
|
||||
#ifndef GIT_WINHTTP
|
||||
git_buf request = GIT_BUF_INIT;
|
||||
const char *verb;
|
||||
int error = -1;
|
||||
|
||||
verb = ls ? "GET" : "POST";
|
||||
/* Generate and send the HTTP request */
|
||||
if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) {
|
||||
giterr_set(GITERR_NET, "Failed to generate request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (content_length) {
|
||||
if (gitno_send((git_transport *) t, data, content_length, 0) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
error = 0;
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&request);
|
||||
return error;
|
||||
|
||||
#else
|
||||
wchar_t *verb;
|
||||
wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH];
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
BOOL ret;
|
||||
DWORD flags;
|
||||
void *buffer;
|
||||
wchar_t *types[] = {
|
||||
L"*/*",
|
||||
NULL,
|
||||
};
|
||||
|
||||
verb = ls ? L"GET" : L"POST";
|
||||
buffer = data ? data : WINHTTP_NO_REQUEST_DATA;
|
||||
flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0;
|
||||
|
||||
if (ls)
|
||||
git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service);
|
||||
else
|
||||
git_buf_printf(&buf, "%s/git-%s", t->path, service);
|
||||
|
||||
if (git_buf_oom(&buf))
|
||||
return -1;
|
||||
|
||||
git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
|
||||
|
||||
t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags);
|
||||
if (t->request == NULL) {
|
||||
git_buf_free(&buf);
|
||||
giterr_set(GITERR_OS, "Failed to open request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_buf_clear(&buf);
|
||||
if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0)
|
||||
goto on_error;
|
||||
|
||||
git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf));
|
||||
|
||||
if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) {
|
||||
giterr_set(GITERR_OS, "Failed to add a header to the request");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (!t->parent.check_cert) {
|
||||
int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
|
||||
if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) {
|
||||
giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) {
|
||||
giterr_set(GITERR_OS, "Failed to send request");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
ret = WinHttpReceiveResponse(t->request, NULL);
|
||||
if (ret == FALSE) {
|
||||
giterr_set(GITERR_OS, "Failed to receive response");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_buf_free(&buf);
|
||||
if (t->request)
|
||||
WinHttpCloseHandle(t->request);
|
||||
t->request = NULL;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int do_connect(transport_http *t)
|
||||
{
|
||||
#ifndef GIT_WINHTTP
|
||||
if (t->parent.connected && http_should_keep_alive(&t->parser))
|
||||
return 0;
|
||||
|
||||
if (gitno_connect((git_transport *) t, t->host, t->port) < 0)
|
||||
return -1;
|
||||
|
||||
t->parent.connected = 1;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
|
||||
wchar_t host[GIT_WIN_PATH];
|
||||
int32_t port;
|
||||
|
||||
t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
|
||||
if (t->session == NULL) {
|
||||
giterr_set(GITERR_OS, "Failed to init WinHTTP");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git__utf8_to_16(host, GIT_WIN_PATH, t->host);
|
||||
|
||||
if (git__strtol32(&port, t->port, NULL, 10) < 0)
|
||||
goto on_error;
|
||||
|
||||
t->connection = WinHttpConnect(t->session, host, port, 0);
|
||||
if (t->connection == NULL) {
|
||||
giterr_set(GITERR_OS, "Failed to connect to host");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
t->parent.connected = 1;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
if (t->session) {
|
||||
WinHttpCloseHandle(t->session);
|
||||
t->session = NULL;
|
||||
}
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* The HTTP parser is streaming, so we need to wait until we're in the
|
||||
* field handler before we can be sure that we can store the previous
|
||||
* value. Right now, we only care about the
|
||||
* Content-Type. on_header_{field,value} should be kept generic enough
|
||||
* to work for any request.
|
||||
*/
|
||||
|
||||
static const char *typestr = "Content-Type";
|
||||
|
||||
static int on_header_field(http_parser *parser, const char *str, size_t len)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
git_buf *buf = &t->buf;
|
||||
parser_context *ctx = (parser_context *) parser->data;
|
||||
http_subtransport *t = ctx->t;
|
||||
git_buf *buf = &t->parse_temp;
|
||||
|
||||
if (t->last_cb == VALUE && t->ct_found) {
|
||||
t->ct_finished = 1;
|
||||
@ -279,8 +124,9 @@ static int on_header_field(http_parser *parser, const char *str, size_t len)
|
||||
|
||||
static int on_header_value(http_parser *parser, const char *str, size_t len)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
git_buf *buf = &t->buf;
|
||||
parser_context *ctx = (parser_context *) parser->data;
|
||||
http_subtransport *t = ctx->t;
|
||||
git_buf *buf = &t->parse_temp;
|
||||
|
||||
if (t->ct_finished) {
|
||||
t->last_cb = VALUE;
|
||||
@ -290,7 +136,7 @@ static int on_header_value(http_parser *parser, const char *str, size_t len)
|
||||
if (t->last_cb == VALUE)
|
||||
git_buf_put(buf, str, len);
|
||||
|
||||
if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
|
||||
if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), "Content-Type")) {
|
||||
t->ct_found = 1;
|
||||
git_buf_clear(buf);
|
||||
git_buf_put(buf, str, len);
|
||||
@ -303,8 +149,9 @@ static int on_header_value(http_parser *parser, const char *str, size_t len)
|
||||
|
||||
static int on_headers_complete(http_parser *parser)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
git_buf *buf = &t->buf;
|
||||
parser_context *ctx = (parser_context *) parser->data;
|
||||
http_subtransport *t = ctx->t;
|
||||
git_buf *buf = &t->parse_temp;
|
||||
|
||||
/* The content-type is text/plain for 404, so don't validate */
|
||||
if (parser->status_code == 404) {
|
||||
@ -315,16 +162,16 @@ static int on_headers_complete(http_parser *parser)
|
||||
if (t->content_type == NULL) {
|
||||
t->content_type = git__strdup(git_buf_cstr(buf));
|
||||
if (t->content_type == NULL)
|
||||
return t->error = -1;
|
||||
return t->parse_error = -1;
|
||||
}
|
||||
|
||||
git_buf_clear(buf);
|
||||
git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
|
||||
git_buf_printf(buf, "application/x-git-%s-advertisement", ctx->s->service);
|
||||
if (git_buf_oom(buf))
|
||||
return t->error = -1;
|
||||
return t->parse_error = -1;
|
||||
|
||||
if (strcmp(t->content_type, git_buf_cstr(buf)))
|
||||
return t->error = -1;
|
||||
return t->parse_error = -1;
|
||||
|
||||
git_buf_clear(buf);
|
||||
return 0;
|
||||
@ -332,13 +179,14 @@ static int on_headers_complete(http_parser *parser)
|
||||
|
||||
static int on_message_complete(http_parser *parser)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
parser_context *ctx = (parser_context *) parser->data;
|
||||
http_subtransport *t = ctx->t;
|
||||
|
||||
t->transfer_finished = 1;
|
||||
t->parse_finished = 1;
|
||||
|
||||
if (parser->status_code == 404) {
|
||||
giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));
|
||||
t->error = -1;
|
||||
giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->parse_temp));
|
||||
t->parse_error = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -346,283 +194,289 @@ static int on_message_complete(http_parser *parser)
|
||||
|
||||
static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
|
||||
{
|
||||
git_transport *transport = (git_transport *) parser->data;
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
gitno_buffer *buf = &transport->buffer;
|
||||
parser_context *ctx = (parser_context *) parser->data;
|
||||
http_subtransport *t = ctx->t;
|
||||
|
||||
if (buf->len - buf->offset < len) {
|
||||
if (ctx->buf_size < len) {
|
||||
giterr_set(GITERR_NET, "Can't fit data in the buffer");
|
||||
return t->error = -1;
|
||||
return t->parse_error = -1;
|
||||
}
|
||||
|
||||
memcpy(buf->data + buf->offset, str, len);
|
||||
buf->offset += len;
|
||||
memcpy(ctx->buffer, str, len);
|
||||
*(ctx->bytes_read) += len;
|
||||
ctx->buffer += len;
|
||||
ctx->buf_size -= len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int http_recv_cb(gitno_buffer *buf)
|
||||
static int http_stream_read(
|
||||
git_smart_subtransport_stream *stream,
|
||||
char *buffer,
|
||||
size_t buf_size,
|
||||
size_t *bytes_read)
|
||||
{
|
||||
git_transport *transport = (git_transport *) buf->cb_data;
|
||||
transport_http *t = (transport_http *) transport;
|
||||
size_t old_len;
|
||||
char buffer[2048];
|
||||
#ifdef GIT_WINHTTP
|
||||
DWORD recvd;
|
||||
#else
|
||||
gitno_buffer inner;
|
||||
int error;
|
||||
#endif
|
||||
http_stream *s = (http_stream *)stream;
|
||||
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
git_buf request = GIT_BUF_INIT;
|
||||
parser_context ctx;
|
||||
|
||||
if (t->transfer_finished)
|
||||
*bytes_read = 0;
|
||||
|
||||
assert(t->connected);
|
||||
|
||||
if (!s->sent_request) {
|
||||
if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, 0) < 0) {
|
||||
giterr_set(GITERR_NET, "Failed to generate request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
|
||||
git_buf_free(&request);
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_buf_free(&request);
|
||||
s->sent_request = 1;
|
||||
}
|
||||
|
||||
t->parse_buffer.offset = 0;
|
||||
|
||||
if (t->parse_finished)
|
||||
return 0;
|
||||
|
||||
#ifndef GIT_WINHTTP
|
||||
gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer));
|
||||
inner.packetsize_cb = buf->packetsize_cb;
|
||||
inner.packetsize_payload = buf->packetsize_payload;
|
||||
|
||||
if ((error = gitno_recv(&inner)) < 0)
|
||||
if (gitno_recv(&t->parse_buffer) < 0)
|
||||
return -1;
|
||||
|
||||
old_len = buf->offset;
|
||||
http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset);
|
||||
if (t->error < 0)
|
||||
return t->error;
|
||||
#else
|
||||
old_len = buf->offset;
|
||||
if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) {
|
||||
giterr_set(GITERR_OS, "Failed to read data from the network");
|
||||
return t->error = -1;
|
||||
}
|
||||
/* This call to http_parser_execute will result in invocations of the on_* family of
|
||||
* callbacks. The most interesting of these is on_body_fill_buffer, which is called
|
||||
* when data is ready to be copied into the target buffer. We need to marshal the
|
||||
* buffer, buf_size, and bytes_read parameters to this callback. */
|
||||
ctx.t = t;
|
||||
ctx.s = s;
|
||||
ctx.buffer = buffer;
|
||||
ctx.buf_size = buf_size;
|
||||
ctx.bytes_read = bytes_read;
|
||||
|
||||
if (buf->len - buf->offset < recvd) {
|
||||
giterr_set(GITERR_NET, "Can't fit data in the buffer");
|
||||
return t->error = -1;
|
||||
}
|
||||
/* Set the context, call the parser, then unset the context. */
|
||||
t->parser.data = &ctx;
|
||||
http_parser_execute(&t->parser, &t->settings, t->parse_buffer.data, t->parse_buffer.offset);
|
||||
t->parser.data = NULL;
|
||||
|
||||
memcpy(buf->data + buf->offset, buffer, recvd);
|
||||
buf->offset += recvd;
|
||||
if (buf->packetsize_cb) buf->packetsize_cb(recvd, buf->packetsize_payload);
|
||||
#endif
|
||||
if (t->parse_error < 0)
|
||||
return t->parse_error;
|
||||
|
||||
return (int)(buf->offset - old_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */
|
||||
static void setup_gitno_buffer(git_transport *transport)
|
||||
static int http_stream_write(
|
||||
git_smart_subtransport_stream *stream,
|
||||
const char *buffer,
|
||||
size_t len)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
http_stream *s = (http_stream *)stream;
|
||||
http_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
git_buf request = GIT_BUF_INIT;
|
||||
|
||||
assert(t->connected);
|
||||
|
||||
/* Since we have to write the Content-Length header up front, we're
|
||||
* basically limited to a single call to write() per request. */
|
||||
assert(!s->sent_request);
|
||||
|
||||
if (!s->sent_request) {
|
||||
if (gen_request(&request, t->path, t->host, s->verb, s->service, s->service_url, len) < 0) {
|
||||
giterr_set(GITERR_NET, "Failed to generate request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_free(&request);
|
||||
s->sent_request = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_buf_free(&request);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void http_stream_free(git_smart_subtransport_stream *stream)
|
||||
{
|
||||
http_stream *s = (http_stream *)stream;
|
||||
|
||||
git__free(s);
|
||||
}
|
||||
|
||||
static int http_stream_alloc(http_subtransport *t, git_smart_subtransport_stream **stream)
|
||||
{
|
||||
http_stream *s;
|
||||
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
s = (http_stream *)git__calloc(sizeof(http_stream), 1);
|
||||
GITERR_CHECK_ALLOC(s);
|
||||
|
||||
s->parent.subtransport = &t->parent;
|
||||
s->parent.read = http_stream_read;
|
||||
s->parent.write = http_stream_write;
|
||||
s->parent.free = http_stream_free;
|
||||
|
||||
*stream = (git_smart_subtransport_stream *)s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int http_uploadpack_ls(
|
||||
http_subtransport *t,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
http_stream *s;
|
||||
|
||||
if (http_stream_alloc(t, stream) < 0)
|
||||
return -1;
|
||||
|
||||
s = (http_stream *)*stream;
|
||||
|
||||
s->service = upload_pack_service;
|
||||
s->service_url = upload_pack_ls_service_url;
|
||||
s->verb = get_verb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int http_uploadpack(
|
||||
http_subtransport *t,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
http_stream *s;
|
||||
|
||||
if (http_stream_alloc(t, stream) < 0)
|
||||
return -1;
|
||||
|
||||
s = (http_stream *)*stream;
|
||||
|
||||
s->service = upload_pack_service;
|
||||
s->service_url = upload_pack_service_url;
|
||||
s->verb = post_verb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int http_action(
|
||||
git_smart_subtransport_stream **stream,
|
||||
git_smart_subtransport *smart_transport,
|
||||
const char *url,
|
||||
git_smart_service_t action)
|
||||
{
|
||||
http_subtransport *t = (http_subtransport *)smart_transport;
|
||||
const char *default_port;
|
||||
int flags = 0, ret;
|
||||
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
if (!t->host || !t->port || !t->path) {
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url = url + strlen(prefix_http);
|
||||
default_port = "80";
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
t->use_ssl = 1;
|
||||
}
|
||||
|
||||
if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
|
||||
return ret;
|
||||
|
||||
t->path = strchr(url, '/');
|
||||
}
|
||||
|
||||
if (!t->connected || !http_should_keep_alive(&t->parser)) {
|
||||
if (t->use_ssl) {
|
||||
flags |= GITNO_CONNECT_SSL;
|
||||
|
||||
if (t->no_check_cert)
|
||||
flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
|
||||
}
|
||||
|
||||
if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
|
||||
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->parse_finished = 0;
|
||||
t->parse_error = 0;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GIT_SERVICE_UPLOADPACK_LS:
|
||||
return http_uploadpack_ls(t, stream);
|
||||
|
||||
case GIT_SERVICE_UPLOADPACK:
|
||||
return http_uploadpack(t, stream);
|
||||
}
|
||||
|
||||
*stream = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void http_free(git_smart_subtransport *smart_transport)
|
||||
{
|
||||
http_subtransport *t = (http_subtransport *) smart_transport;
|
||||
|
||||
git_buf_free(&t->parse_temp);
|
||||
git__free(t->content_type);
|
||||
git__free(t->host);
|
||||
git__free(t->port);
|
||||
git__free(t);
|
||||
}
|
||||
|
||||
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
|
||||
{
|
||||
http_subtransport *t;
|
||||
int flags;
|
||||
|
||||
if (!out)
|
||||
return -1;
|
||||
|
||||
t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1);
|
||||
GITERR_CHECK_ALLOC(t);
|
||||
|
||||
t->owner = owner;
|
||||
t->parent.action = http_action;
|
||||
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;
|
||||
|
||||
/* WinHTTP takes care of this for us */
|
||||
#ifndef GIT_WINHTTP
|
||||
http_parser_init(&t->parser, HTTP_RESPONSE);
|
||||
t->parser.data = t;
|
||||
t->transfer_finished = 0;
|
||||
memset(&t->settings, 0x0, sizeof(http_parser_settings));
|
||||
t->settings.on_header_field = on_header_field;
|
||||
t->settings.on_header_value = on_header_value;
|
||||
t->settings.on_headers_complete = on_headers_complete;
|
||||
t->settings.on_body = on_body_fill_buffer;
|
||||
t->settings.on_message_complete = on_message_complete;
|
||||
#endif
|
||||
|
||||
gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t);
|
||||
}
|
||||
|
||||
static int http_connect(git_transport *transport, int direction)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
int ret;
|
||||
git_buf request = GIT_BUF_INIT;
|
||||
const char *service = "upload-pack";
|
||||
const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://";
|
||||
const char *default_port;
|
||||
git_pkt *pkt;
|
||||
|
||||
if (direction == GIT_DIR_PUSH) {
|
||||
giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
|
||||
return -1;
|
||||
}
|
||||
|
||||
t->parent.direction = direction;
|
||||
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url = t->parent.url + strlen(prefix_http);
|
||||
default_port = "80";
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
}
|
||||
|
||||
t->path = strchr(url, '/');
|
||||
|
||||
if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
t->service = git__strdup(service);
|
||||
GITERR_CHECK_ALLOC(t->service);
|
||||
|
||||
if ((ret = do_connect(t)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
setup_gitno_buffer(transport);
|
||||
if ((ret = git_protocol_store_refs(transport, 2)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
pkt = git_vector_get(&transport->refs, 0);
|
||||
if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
|
||||
giterr_set(GITERR_NET, "Invalid HTTP response");
|
||||
return t->error = -1;
|
||||
} else {
|
||||
/* Remove the comment pkt from the list */
|
||||
git_vector_remove(&transport->refs, 0);
|
||||
git__free(pkt);
|
||||
}
|
||||
|
||||
if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
|
||||
return t->error = -1;
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&request);
|
||||
git_buf_clear(&t->buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int http_negotiation_step(struct git_transport *transport, void *data, size_t len)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
int ret;
|
||||
|
||||
/* First, send the data as a HTTP POST request */
|
||||
if ((ret = do_connect(t)) < 0)
|
||||
return -1;
|
||||
|
||||
if (send_request(t, "upload-pack", data, len, 0) < 0)
|
||||
return -1;
|
||||
|
||||
/* Then we need to set up the buffer to grab data from the HTTP response */
|
||||
setup_gitno_buffer(transport);
|
||||
|
||||
*out = (git_smart_subtransport *) t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int http_close(git_transport *transport)
|
||||
{
|
||||
#ifndef GIT_WINHTTP
|
||||
if (gitno_ssl_teardown(transport) < 0)
|
||||
return -1;
|
||||
|
||||
if (gitno_close(transport->socket) < 0) {
|
||||
giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
transport_http *t = (transport_http *) transport;
|
||||
|
||||
if (t->request)
|
||||
WinHttpCloseHandle(t->request);
|
||||
if (t->connection)
|
||||
WinHttpCloseHandle(t->connection);
|
||||
if (t->session)
|
||||
WinHttpCloseHandle(t->session);
|
||||
#endif
|
||||
|
||||
transport->connected = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void http_free(git_transport *transport)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
git_vector *refs = &transport->refs;
|
||||
git_vector *common = &transport->common;
|
||||
unsigned int i;
|
||||
git_pkt *p;
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
/* cleanup the WSA context. note that this context
|
||||
* can be initialized more than once with WSAStartup(),
|
||||
* and needs to be cleaned one time for each init call
|
||||
*/
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
git_vector_foreach(refs, i, p) {
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(refs);
|
||||
git_vector_foreach(common, i, p) {
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(common);
|
||||
git_buf_free(&t->buf);
|
||||
git__free(t->content_type);
|
||||
git__free(t->host);
|
||||
git__free(t->port);
|
||||
git__free(t->service);
|
||||
git__free(t->parent.url);
|
||||
git__free(t);
|
||||
}
|
||||
|
||||
int git_transport_http(git_transport **out)
|
||||
{
|
||||
transport_http *t;
|
||||
|
||||
t = git__malloc(sizeof(transport_http));
|
||||
GITERR_CHECK_ALLOC(t);
|
||||
|
||||
memset(t, 0x0, sizeof(transport_http));
|
||||
|
||||
t->parent.connect = http_connect;
|
||||
t->parent.negotiation_step = http_negotiation_step;
|
||||
t->parent.close = http_close;
|
||||
t->parent.free = http_free;
|
||||
t->parent.rpc = 1;
|
||||
|
||||
if (git_vector_init(&t->parent.refs, 16, NULL) < 0) {
|
||||
git__free(t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
/* on win32, the WSA context needs to be initialized
|
||||
* before any socket calls can be performed */
|
||||
if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
|
||||
http_free((git_transport *) t);
|
||||
giterr_set(GITERR_OS, "Winsock init failed");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
*out = (git_transport *) t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_transport_https(git_transport **out)
|
||||
{
|
||||
#if defined(GIT_SSL) || defined(GIT_WINHTTP)
|
||||
transport_http *t;
|
||||
if (git_transport_http((git_transport **)&t) < 0)
|
||||
return -1;
|
||||
|
||||
t->parent.use_ssl = 1;
|
||||
t->parent.check_cert = 1;
|
||||
*out = (git_transport *) t;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
GIT_UNUSED(out);
|
||||
|
||||
giterr_set(GITERR_NET, "HTTPS support not available");
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
#endif /* !GIT_WINHTTP */
|
||||
|
@ -11,15 +11,20 @@
|
||||
#include "git2/object.h"
|
||||
#include "git2/tag.h"
|
||||
#include "refs.h"
|
||||
#include "transport.h"
|
||||
#include "git2/transport.h"
|
||||
#include "posix.h"
|
||||
#include "path.h"
|
||||
#include "buffer.h"
|
||||
#include "pkt.h"
|
||||
|
||||
typedef struct {
|
||||
git_transport parent;
|
||||
char *url;
|
||||
int direction;
|
||||
int flags;
|
||||
git_atomic cancelled;
|
||||
git_repository *repo;
|
||||
git_vector refs;
|
||||
unsigned connected : 1;
|
||||
} transport_local;
|
||||
|
||||
static int add_ref(transport_local *t, const char *name)
|
||||
@ -27,32 +32,24 @@ static int add_ref(transport_local *t, const char *name)
|
||||
const char peeled[] = "^{}";
|
||||
git_remote_head *head;
|
||||
git_object *obj = NULL, *target = NULL;
|
||||
git_transport *transport = (git_transport *) t;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
git_pkt_ref *pkt;
|
||||
|
||||
head = git__malloc(sizeof(git_remote_head));
|
||||
head = (git_remote_head *)git__malloc(sizeof(git_remote_head));
|
||||
GITERR_CHECK_ALLOC(head);
|
||||
pkt = git__malloc(sizeof(git_pkt_ref));
|
||||
GITERR_CHECK_ALLOC(pkt);
|
||||
|
||||
head->name = git__strdup(name);
|
||||
GITERR_CHECK_ALLOC(head->name);
|
||||
|
||||
if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) {
|
||||
git__free(head->name);
|
||||
git__free(head);
|
||||
git__free(pkt->head.name);
|
||||
git__free(pkt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pkt->type = GIT_PKT_REF;
|
||||
memcpy(&pkt->head, head, sizeof(git_remote_head));
|
||||
git__free(head);
|
||||
|
||||
if (git_vector_insert(&transport->refs, pkt) < 0)
|
||||
if (git_vector_insert(&t->refs, head) < 0)
|
||||
{
|
||||
git__free(pkt->head.name);
|
||||
git__free(pkt);
|
||||
git__free(head->name);
|
||||
git__free(head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -60,7 +57,7 @@ static int add_ref(transport_local *t, const char *name)
|
||||
if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
|
||||
return 0;
|
||||
|
||||
if (git_object_lookup(&obj, t->repo, &pkt->head.oid, GIT_OBJ_ANY) < 0)
|
||||
if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0)
|
||||
return -1;
|
||||
|
||||
head = NULL;
|
||||
@ -72,27 +69,21 @@ static int add_ref(transport_local *t, const char *name)
|
||||
}
|
||||
|
||||
/* And if it's a tag, peel it, and add it to the list */
|
||||
head = git__malloc(sizeof(git_remote_head));
|
||||
head = (git_remote_head *)git__malloc(sizeof(git_remote_head));
|
||||
GITERR_CHECK_ALLOC(head);
|
||||
if (git_buf_join(&buf, 0, name, peeled) < 0)
|
||||
return -1;
|
||||
|
||||
head->name = git_buf_detach(&buf);
|
||||
|
||||
pkt = git__malloc(sizeof(git_pkt_ref));
|
||||
GITERR_CHECK_ALLOC(pkt);
|
||||
pkt->type = GIT_PKT_REF;
|
||||
|
||||
if (git_tag_peel(&target, (git_tag *) obj) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_oid_cpy(&head->oid, git_object_id(target));
|
||||
git_object_free(obj);
|
||||
git_object_free(target);
|
||||
memcpy(&pkt->head, head, sizeof(git_remote_head));
|
||||
git__free(head);
|
||||
|
||||
if (git_vector_insert(&transport->refs, pkt) < 0)
|
||||
if (git_vector_insert(&t->refs, head) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
@ -107,12 +98,11 @@ static int store_refs(transport_local *t)
|
||||
{
|
||||
unsigned int i;
|
||||
git_strarray ref_names = {0};
|
||||
git_transport *transport = (git_transport *) t;
|
||||
|
||||
assert(t);
|
||||
|
||||
if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
|
||||
git_vector_init(&transport->refs, (unsigned int)ref_names.count, NULL) < 0)
|
||||
git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
|
||||
goto on_error;
|
||||
|
||||
/* Sort the references first */
|
||||
@ -131,7 +121,7 @@ static int store_refs(transport_local *t)
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_vector_free(&transport->refs);
|
||||
git_vector_free(&t->refs);
|
||||
git_strarray_free(&ref_names);
|
||||
return -1;
|
||||
}
|
||||
@ -140,7 +130,7 @@ on_error:
|
||||
* Try to open the url as a git directory. The direction doesn't
|
||||
* matter in this case because we're calulating the heads ourselves.
|
||||
*/
|
||||
static int local_connect(git_transport *transport, int direction)
|
||||
static int local_connect(git_transport *transport, const char *url, int direction, int flags)
|
||||
{
|
||||
git_repository *repo;
|
||||
int error;
|
||||
@ -148,18 +138,21 @@ static int local_connect(git_transport *transport, int direction)
|
||||
const char *path;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
GIT_UNUSED(direction);
|
||||
t->url = git__strdup(url);
|
||||
GITERR_CHECK_ALLOC(t->url);
|
||||
t->direction = direction;
|
||||
t->flags = flags;
|
||||
|
||||
/* The repo layer doesn't want the prefix */
|
||||
if (!git__prefixcmp(transport->url, "file://")) {
|
||||
if (git_path_fromurl(&buf, transport->url) < 0) {
|
||||
if (!git__prefixcmp(t->url, "file://")) {
|
||||
if (git_path_fromurl(&buf, t->url) < 0) {
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
path = git_buf_cstr(&buf);
|
||||
|
||||
} else { /* We assume transport->url is already a path */
|
||||
path = transport->url;
|
||||
path = t->url;
|
||||
}
|
||||
|
||||
error = git_repository_open(&repo, path);
|
||||
@ -174,26 +167,74 @@ static int local_connect(git_transport *transport, int direction)
|
||||
if (store_refs(t) < 0)
|
||||
return -1;
|
||||
|
||||
t->parent.connected = 1;
|
||||
t->connected = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
|
||||
static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
|
||||
{
|
||||
transport_local *t = (transport_local *)transport;
|
||||
unsigned int i;
|
||||
git_remote_head *head = NULL;
|
||||
|
||||
if (!t->connected) {
|
||||
giterr_set(GITERR_NET, "The transport is not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_vector_foreach(&t->refs, i, head) {
|
||||
if (list_cb(head, payload))
|
||||
return GIT_EUSER;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int local_negotiate_fetch(
|
||||
git_transport *transport,
|
||||
git_repository *repo,
|
||||
const git_remote_head * const *refs, size_t count)
|
||||
{
|
||||
GIT_UNUSED(transport);
|
||||
GIT_UNUSED(repo);
|
||||
GIT_UNUSED(wants);
|
||||
GIT_UNUSED(refs);
|
||||
GIT_UNUSED(count);
|
||||
|
||||
giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int local_is_connected(git_transport *transport, int *connected)
|
||||
{
|
||||
transport_local *t = (transport_local *)transport;
|
||||
|
||||
*connected = t->connected;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int local_read_flags(git_transport *transport, int *flags)
|
||||
{
|
||||
transport_local *t = (transport_local *)transport;
|
||||
|
||||
*flags = t->flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void local_cancel(git_transport *transport)
|
||||
{
|
||||
transport_local *t = (transport_local *)transport;
|
||||
|
||||
git_atomic_set(&t->cancelled, 1);
|
||||
}
|
||||
|
||||
static int local_close(git_transport *transport)
|
||||
{
|
||||
transport_local *t = (transport_local *)transport;
|
||||
|
||||
t->parent.connected = 0;
|
||||
t->connected = 0;
|
||||
git_repository_free(t->repo);
|
||||
t->repo = NULL;
|
||||
|
||||
@ -204,18 +245,18 @@ static void local_free(git_transport *transport)
|
||||
{
|
||||
unsigned int i;
|
||||
transport_local *t = (transport_local *) transport;
|
||||
git_vector *vec = &transport->refs;
|
||||
git_pkt_ref *pkt;
|
||||
git_vector *vec = &t->refs;
|
||||
git_remote_head *head;
|
||||
|
||||
assert(transport);
|
||||
|
||||
git_vector_foreach (vec, i, pkt) {
|
||||
git__free(pkt->head.name);
|
||||
git__free(pkt);
|
||||
git_vector_foreach (vec, i, head) {
|
||||
git__free(head->name);
|
||||
git__free(head);
|
||||
}
|
||||
git_vector_free(vec);
|
||||
|
||||
git__free(t->parent.url);
|
||||
git__free(t->url);
|
||||
git__free(t);
|
||||
}
|
||||
|
||||
@ -223,20 +264,25 @@ static void local_free(git_transport *transport)
|
||||
* Public API *
|
||||
**************/
|
||||
|
||||
int git_transport_local(git_transport **out)
|
||||
int git_transport_local(git_transport **out, void *param)
|
||||
{
|
||||
transport_local *t;
|
||||
|
||||
GIT_UNUSED(param);
|
||||
|
||||
t = git__malloc(sizeof(transport_local));
|
||||
GITERR_CHECK_ALLOC(t);
|
||||
|
||||
memset(t, 0x0, sizeof(transport_local));
|
||||
|
||||
t->parent.own_logic = 1;
|
||||
|
||||
t->parent.connect = local_connect;
|
||||
t->parent.negotiate_fetch = local_negotiate_fetch;
|
||||
t->parent.close = local_close;
|
||||
t->parent.free = local_free;
|
||||
t->parent.ls = local_ls;
|
||||
t->parent.is_connected = local_is_connected;
|
||||
t->parent.read_flags = local_read_flags;
|
||||
t->parent.cancel = local_cancel;
|
||||
|
||||
*out = (git_transport *) t;
|
||||
|
||||
|
278
src/transports/smart.c
Normal file
278
src/transports/smart.c
Normal file
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* 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"
|
||||
#include "refs.h"
|
||||
|
||||
static int git_smart__recv_cb(gitno_buffer *buf)
|
||||
{
|
||||
transport_smart *t = (transport_smart *) buf->cb_data;
|
||||
size_t old_len, bytes_read;
|
||||
int error;
|
||||
|
||||
assert(t->current_stream);
|
||||
|
||||
old_len = buf->offset;
|
||||
|
||||
if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
|
||||
return error;
|
||||
|
||||
buf->offset += bytes_read;
|
||||
|
||||
if (t->packetsize_cb)
|
||||
t->packetsize_cb(bytes_read, t->packetsize_payload);
|
||||
|
||||
return (int)(buf->offset - old_len);
|
||||
}
|
||||
|
||||
GIT_INLINE(void) git_smart__reset_stream(transport_smart *t)
|
||||
{
|
||||
if (t->current_stream) {
|
||||
t->current_stream->free(t->current_stream);
|
||||
t->current_stream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int git_smart__set_callbacks(
|
||||
git_transport *transport,
|
||||
git_transport_message_cb progress_cb,
|
||||
git_transport_message_cb error_cb,
|
||||
void *message_cb_payload)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
|
||||
t->progress_cb = progress_cb;
|
||||
t->error_cb = error_cb;
|
||||
t->message_cb_payload = message_cb_payload;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_smart__connect(git_transport *transport, const char *url, int direction, int flags)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
git_smart_subtransport_stream *stream;
|
||||
int error;
|
||||
git_pkt *pkt;
|
||||
|
||||
git_smart__reset_stream(t);
|
||||
|
||||
t->url = git__strdup(url);
|
||||
GITERR_CHECK_ALLOC(t->url);
|
||||
|
||||
t->direction = direction;
|
||||
t->flags = flags;
|
||||
|
||||
if (GIT_DIR_FETCH == direction)
|
||||
{
|
||||
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0)
|
||||
return error;
|
||||
|
||||
/* Save off the current stream (i.e. socket) that we are working with */
|
||||
t->current_stream = stream;
|
||||
|
||||
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
|
||||
|
||||
/* 2 flushes for RPC; 1 for stateful */
|
||||
if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
|
||||
return error;
|
||||
|
||||
/* Strip the comment packet for RPC */
|
||||
if (t->rpc) {
|
||||
pkt = (git_pkt *)git_vector_get(&t->refs, 0);
|
||||
|
||||
if (!pkt || GIT_PKT_COMMENT != pkt->type) {
|
||||
giterr_set(GITERR_NET, "Invalid response");
|
||||
return -1;
|
||||
} else {
|
||||
/* Remove the comment pkt from the list */
|
||||
git_vector_remove(&t->refs, 0);
|
||||
git__free(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
/* We now have loaded the refs. */
|
||||
t->have_refs = 1;
|
||||
|
||||
if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0)
|
||||
return -1;
|
||||
|
||||
if (t->rpc)
|
||||
git_smart__reset_stream(t);
|
||||
|
||||
/* We're now logically connected. */
|
||||
t->connected = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
giterr_set(GITERR_NET, "Push not implemented");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
unsigned int i;
|
||||
git_pkt *p = NULL;
|
||||
|
||||
if (!t->have_refs) {
|
||||
giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_vector_foreach(&t->refs, i, p) {
|
||||
git_pkt_ref *pkt = NULL;
|
||||
|
||||
if (p->type != GIT_PKT_REF)
|
||||
continue;
|
||||
|
||||
pkt = (git_pkt_ref *)p;
|
||||
|
||||
if (list_cb(&pkt->head, payload))
|
||||
return GIT_EUSER;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
git_smart_subtransport_stream *stream;
|
||||
int error;
|
||||
|
||||
if (t->rpc)
|
||||
git_smart__reset_stream(t);
|
||||
|
||||
if (GIT_DIR_FETCH == t->direction) {
|
||||
if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
|
||||
return error;
|
||||
|
||||
/* If this is a stateful implementation, the stream we get back should be the same */
|
||||
assert(t->rpc || t->current_stream == stream);
|
||||
|
||||
/* Save off the current stream (i.e. socket) that we are working with */
|
||||
t->current_stream = stream;
|
||||
|
||||
if ((error = stream->write(stream, (const char *)data, len)) < 0)
|
||||
return error;
|
||||
|
||||
gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
giterr_set(GITERR_NET, "Push not implemented");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void git_smart__cancel(git_transport *transport)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
|
||||
git_atomic_set(&t->cancelled, 1);
|
||||
}
|
||||
|
||||
static int git_smart__is_connected(git_transport *transport, int *connected)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
|
||||
*connected = t->connected;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_smart__read_flags(git_transport *transport, int *flags)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
|
||||
*flags = t->flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_smart__close(git_transport *transport)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
|
||||
git_smart__reset_stream(t);
|
||||
|
||||
t->connected = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void git_smart__free(git_transport *transport)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
git_vector *refs = &t->refs;
|
||||
git_vector *common = &t->common;
|
||||
unsigned int i;
|
||||
git_pkt *p;
|
||||
|
||||
/* Make sure that the current stream is closed, if we have one. */
|
||||
git_smart__close(transport);
|
||||
|
||||
/* Free the subtransport */
|
||||
t->wrapped->free(t->wrapped);
|
||||
|
||||
git_vector_foreach(refs, i, p) {
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(refs);
|
||||
|
||||
git_vector_foreach(common, i, p) {
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(common);
|
||||
|
||||
git__free(t->url);
|
||||
git__free(t);
|
||||
}
|
||||
|
||||
int git_transport_smart(git_transport **out, void *param)
|
||||
{
|
||||
transport_smart *t;
|
||||
git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
|
||||
|
||||
if (!param)
|
||||
return -1;
|
||||
|
||||
t = (transport_smart *)git__calloc(sizeof(transport_smart), 1);
|
||||
GITERR_CHECK_ALLOC(t);
|
||||
|
||||
t->parent.set_callbacks = git_smart__set_callbacks;
|
||||
t->parent.connect = git_smart__connect;
|
||||
t->parent.close = git_smart__close;
|
||||
t->parent.free = git_smart__free;
|
||||
t->parent.negotiate_fetch = git_smart__negotiate_fetch;
|
||||
t->parent.download_pack = git_smart__download_pack;
|
||||
t->parent.ls = git_smart__ls;
|
||||
t->parent.is_connected = git_smart__is_connected;
|
||||
t->parent.read_flags = git_smart__read_flags;
|
||||
t->parent.cancel = git_smart__cancel;
|
||||
|
||||
t->rpc = definition->rpc;
|
||||
|
||||
if (git_vector_init(&t->refs, 16, NULL) < 0) {
|
||||
git__free(t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (definition->callback(&t->wrapped, &t->parent) < 0) {
|
||||
git__free(t);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out = (git_transport *) t;
|
||||
return 0;
|
||||
}
|
149
src/transports/smart.h
Normal file
149
src/transports/smart.h
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 "vector.h"
|
||||
#include "netops.h"
|
||||
#include "buffer.h"
|
||||
|
||||
#define GIT_SIDE_BAND_DATA 1
|
||||
#define GIT_SIDE_BAND_PROGRESS 2
|
||||
#define GIT_SIDE_BAND_ERROR 3
|
||||
|
||||
#define GIT_CAP_OFS_DELTA "ofs-delta"
|
||||
#define GIT_CAP_MULTI_ACK "multi_ack"
|
||||
#define GIT_CAP_SIDE_BAND "side-band"
|
||||
#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
|
||||
#define GIT_CAP_INCLUDE_TAG "include-tag"
|
||||
|
||||
enum git_pkt_type {
|
||||
GIT_PKT_CMD,
|
||||
GIT_PKT_FLUSH,
|
||||
GIT_PKT_REF,
|
||||
GIT_PKT_HAVE,
|
||||
GIT_PKT_ACK,
|
||||
GIT_PKT_NAK,
|
||||
GIT_PKT_PACK,
|
||||
GIT_PKT_COMMENT,
|
||||
GIT_PKT_ERR,
|
||||
GIT_PKT_DATA,
|
||||
GIT_PKT_PROGRESS,
|
||||
};
|
||||
|
||||
/* Used for multi-ack */
|
||||
enum git_ack_status {
|
||||
GIT_ACK_NONE,
|
||||
GIT_ACK_CONTINUE,
|
||||
GIT_ACK_COMMON,
|
||||
GIT_ACK_READY
|
||||
};
|
||||
|
||||
/* This would be a flush pkt */
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
} git_pkt;
|
||||
|
||||
struct git_pkt_cmd {
|
||||
enum git_pkt_type type;
|
||||
char *cmd;
|
||||
char *path;
|
||||
char *host;
|
||||
};
|
||||
|
||||
/* This is a pkt-line with some info in it */
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
git_remote_head head;
|
||||
char *capabilities;
|
||||
} git_pkt_ref;
|
||||
|
||||
/* Useful later */
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
git_oid oid;
|
||||
enum git_ack_status status;
|
||||
} git_pkt_ack;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
char comment[GIT_FLEX_ARRAY];
|
||||
} git_pkt_comment;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
int len;
|
||||
char data[GIT_FLEX_ARRAY];
|
||||
} git_pkt_data;
|
||||
|
||||
typedef git_pkt_data git_pkt_progress;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
char error[GIT_FLEX_ARRAY];
|
||||
} git_pkt_err;
|
||||
|
||||
typedef struct transport_smart_caps {
|
||||
int common:1,
|
||||
ofs_delta:1,
|
||||
multi_ack: 1,
|
||||
side_band:1,
|
||||
side_band_64k:1,
|
||||
include_tag:1;
|
||||
} transport_smart_caps;
|
||||
|
||||
typedef void (*packetsize_cb)(int received, void *payload);
|
||||
|
||||
typedef struct {
|
||||
git_transport parent;
|
||||
char *url;
|
||||
int direction;
|
||||
int flags;
|
||||
git_transport_message_cb progress_cb;
|
||||
git_transport_message_cb error_cb;
|
||||
void *message_cb_payload;
|
||||
git_smart_subtransport *wrapped;
|
||||
git_smart_subtransport_stream *current_stream;
|
||||
transport_smart_caps caps;
|
||||
git_vector refs;
|
||||
git_vector common;
|
||||
git_atomic cancelled;
|
||||
packetsize_cb packetsize_cb;
|
||||
void *packetsize_payload;
|
||||
unsigned rpc : 1,
|
||||
have_refs : 1,
|
||||
connected : 1;
|
||||
gitno_buffer buffer;
|
||||
char buffer_data[65536];
|
||||
} transport_smart;
|
||||
|
||||
/* smart_protocol.c */
|
||||
int git_smart__store_refs(transport_smart *t, int flushes);
|
||||
int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps);
|
||||
|
||||
int git_smart__negotiate_fetch(
|
||||
git_transport *transport,
|
||||
git_repository *repo,
|
||||
const git_remote_head * const *refs,
|
||||
size_t count);
|
||||
|
||||
int git_smart__download_pack(
|
||||
git_transport *transport,
|
||||
git_repository *repo,
|
||||
git_transfer_progress *stats,
|
||||
git_transfer_progress_callback progress_cb,
|
||||
void *progress_payload);
|
||||
|
||||
/* smart.c */
|
||||
int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
|
||||
|
||||
/* smart_pkt.c */
|
||||
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
|
||||
int git_pkt_buffer_flush(git_buf *buf);
|
||||
int git_pkt_send_flush(GIT_SOCKET s);
|
||||
int git_pkt_buffer_done(git_buf *buf);
|
||||
int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf);
|
||||
int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
|
||||
void git_pkt_free(git_pkt *pkt);
|
@ -12,12 +12,11 @@
|
||||
#include "git2/refs.h"
|
||||
#include "git2/revwalk.h"
|
||||
|
||||
#include "pkt.h"
|
||||
#include "smart.h"
|
||||
#include "util.h"
|
||||
#include "netops.h"
|
||||
#include "posix.h"
|
||||
#include "buffer.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
@ -335,7 +334,7 @@ int git_pkt_buffer_flush(git_buf *buf)
|
||||
return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
|
||||
}
|
||||
|
||||
static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf)
|
||||
static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)
|
||||
{
|
||||
git_buf str = GIT_BUF_INIT;
|
||||
char oid[GIT_OID_HEXSZ +1] = {0};
|
||||
@ -376,28 +375,32 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps
|
||||
* is overwrite the OID each time.
|
||||
*/
|
||||
|
||||
int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf)
|
||||
int git_pkt_buffer_wants(
|
||||
const git_remote_head * const *refs,
|
||||
size_t count,
|
||||
transport_smart_caps *caps,
|
||||
git_buf *buf)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
git_remote_head *head;
|
||||
size_t i = 0;
|
||||
const git_remote_head *head;
|
||||
|
||||
if (caps->common) {
|
||||
for (; i < refs->length; ++i) {
|
||||
head = refs->contents[i];
|
||||
for (; i < count; ++i) {
|
||||
head = refs[i];
|
||||
if (!head->local)
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0)
|
||||
if (buffer_want_with_caps(refs[i], caps, buf) < 0)
|
||||
return -1;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
for (; i < refs->length; ++i) {
|
||||
for (; i < count; ++i) {
|
||||
char oid[GIT_OID_HEXSZ];
|
||||
|
||||
head = refs->contents[i];
|
||||
head = refs[i];
|
||||
if (head->local)
|
||||
continue;
|
||||
|
476
src/transports/smart_protocol.c
Normal file
476
src/transports/smart_protocol.c
Normal file
@ -0,0 +1,476 @@
|
||||
/*
|
||||
* 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 "smart.h"
|
||||
#include "refs.h"
|
||||
|
||||
#define NETWORK_XFER_THRESHOLD (100*1024)
|
||||
|
||||
int git_smart__store_refs(transport_smart *t, int flushes)
|
||||
{
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
git_vector *refs = &t->refs;
|
||||
int error, flush = 0, recvd;
|
||||
const char *line_end;
|
||||
git_pkt *pkt;
|
||||
|
||||
do {
|
||||
if (buf->offset > 0)
|
||||
error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
|
||||
else
|
||||
error = GIT_EBUFS;
|
||||
|
||||
if (error < 0 && error != GIT_EBUFS)
|
||||
return -1;
|
||||
|
||||
if (error == GIT_EBUFS) {
|
||||
if ((recvd = gitno_recv(buf)) < 0)
|
||||
return -1;
|
||||
|
||||
if (recvd == 0 && !flush) {
|
||||
giterr_set(GITERR_NET, "Early EOF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
gitno_consume(buf, line_end);
|
||||
if (pkt->type == GIT_PKT_ERR) {
|
||||
giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
|
||||
git__free(pkt);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)
|
||||
return -1;
|
||||
|
||||
if (pkt->type == GIT_PKT_FLUSH) {
|
||||
flush++;
|
||||
git_pkt_free(pkt);
|
||||
}
|
||||
} while (flush < flushes);
|
||||
|
||||
return flush;
|
||||
}
|
||||
|
||||
int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
|
||||
{
|
||||
const char *ptr;
|
||||
|
||||
/* No refs or capabilites, odd but not a problem */
|
||||
if (pkt == NULL || pkt->capabilities == NULL)
|
||||
return 0;
|
||||
|
||||
ptr = pkt->capabilities;
|
||||
while (ptr != NULL && *ptr != '\0') {
|
||||
if (*ptr == ' ')
|
||||
ptr++;
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
|
||||
caps->common = caps->ofs_delta = 1;
|
||||
ptr += strlen(GIT_CAP_OFS_DELTA);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
|
||||
caps->common = caps->multi_ack = 1;
|
||||
ptr += strlen(GIT_CAP_MULTI_ACK);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
|
||||
caps->common = caps->include_tag = 1;
|
||||
ptr += strlen(GIT_CAP_INCLUDE_TAG);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Keep side-band check after side-band-64k */
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
|
||||
caps->common = caps->side_band_64k = 1;
|
||||
ptr += strlen(GIT_CAP_SIDE_BAND_64K);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
|
||||
caps->common = caps->side_band = 1;
|
||||
ptr += strlen(GIT_CAP_SIDE_BAND);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We don't know this capability, so skip it */
|
||||
ptr = strchr(ptr, ' ');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int recv_pkt(git_pkt **out, gitno_buffer *buf)
|
||||
{
|
||||
const char *ptr = buf->data, *line_end = ptr;
|
||||
git_pkt *pkt;
|
||||
int pkt_type, error = 0, ret;
|
||||
|
||||
do {
|
||||
if (buf->offset > 0)
|
||||
error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
|
||||
else
|
||||
error = GIT_EBUFS;
|
||||
|
||||
if (error == 0)
|
||||
break; /* return the pkt */
|
||||
|
||||
if (error < 0 && error != GIT_EBUFS)
|
||||
return -1;
|
||||
|
||||
if ((ret = gitno_recv(buf)) < 0)
|
||||
return -1;
|
||||
} while (error);
|
||||
|
||||
gitno_consume(buf, line_end);
|
||||
pkt_type = pkt->type;
|
||||
if (out != NULL)
|
||||
*out = pkt;
|
||||
else
|
||||
git__free(pkt);
|
||||
|
||||
return pkt_type;
|
||||
}
|
||||
|
||||
static int store_common(transport_smart *t)
|
||||
{
|
||||
git_pkt *pkt = NULL;
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
|
||||
do {
|
||||
if (recv_pkt(&pkt, buf) < 0)
|
||||
return -1;
|
||||
|
||||
if (pkt->type == GIT_PKT_ACK) {
|
||||
if (git_vector_insert(&t->common, pkt) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
git__free(pkt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} while (1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fetch_setup_walk(git_revwalk **out, git_repository *repo)
|
||||
{
|
||||
git_revwalk *walk;
|
||||
git_strarray refs;
|
||||
unsigned int i;
|
||||
git_reference *ref;
|
||||
|
||||
if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_revwalk_new(&walk, repo) < 0)
|
||||
return -1;
|
||||
|
||||
git_revwalk_sorting(walk, GIT_SORT_TIME);
|
||||
|
||||
for (i = 0; i < refs.count; ++i) {
|
||||
/* No tags */
|
||||
if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
|
||||
continue;
|
||||
|
||||
if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
|
||||
continue;
|
||||
if (git_revwalk_push(walk, git_reference_oid(ref)) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
git_strarray_free(&refs);
|
||||
*out = walk;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_reference_free(ref);
|
||||
git_strarray_free(&refs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
git_buf data = GIT_BUF_INIT;
|
||||
git_revwalk *walk = NULL;
|
||||
int error = -1, pkt_type;
|
||||
unsigned int i;
|
||||
git_oid oid;
|
||||
|
||||
/* No own logic, do our thing */
|
||||
if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
|
||||
return -1;
|
||||
|
||||
if (fetch_setup_walk(&walk, repo) < 0)
|
||||
goto on_error;
|
||||
/*
|
||||
* We don't support any kind of ACK extensions, so the negotiation
|
||||
* boils down to sending what we have and listening for an ACK
|
||||
* every once in a while.
|
||||
*/
|
||||
i = 0;
|
||||
while ((error = git_revwalk_next(&oid, walk)) == 0) {
|
||||
git_pkt_buffer_have(&oid, &data);
|
||||
i++;
|
||||
if (i % 20 == 0) {
|
||||
if (t->cancelled.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
error = GIT_EUSER;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git_pkt_buffer_flush(&data);
|
||||
if (git_buf_oom(&data))
|
||||
goto on_error;
|
||||
|
||||
if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_clear(&data);
|
||||
if (t->caps.multi_ack) {
|
||||
if (store_common(t) < 0)
|
||||
goto on_error;
|
||||
} else {
|
||||
pkt_type = recv_pkt(NULL, buf);
|
||||
|
||||
if (pkt_type == GIT_PKT_ACK) {
|
||||
break;
|
||||
} else if (pkt_type == GIT_PKT_NAK) {
|
||||
continue;
|
||||
} else {
|
||||
giterr_set(GITERR_NET, "Unexpected pkt type");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (t->common.length > 0)
|
||||
break;
|
||||
|
||||
if (i % 20 == 0 && t->rpc) {
|
||||
git_pkt_ack *pkt;
|
||||
unsigned int i;
|
||||
|
||||
if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_vector_foreach(&t->common, i, pkt) {
|
||||
git_pkt_buffer_have(&pkt->oid, &data);
|
||||
}
|
||||
|
||||
if (git_buf_oom(&data))
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (error < 0 && error != GIT_ITEROVER)
|
||||
goto on_error;
|
||||
|
||||
/* Tell the other end that we're done negotiating */
|
||||
if (t->rpc && t->common.length > 0) {
|
||||
git_pkt_ack *pkt;
|
||||
unsigned int i;
|
||||
|
||||
if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_vector_foreach(&t->common, i, pkt) {
|
||||
git_pkt_buffer_have(&pkt->oid, &data);
|
||||
}
|
||||
|
||||
if (git_buf_oom(&data))
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git_pkt_buffer_done(&data);
|
||||
if (t->cancelled.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
error = GIT_EUSER;
|
||||
goto on_error;
|
||||
}
|
||||
if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_free(&data);
|
||||
git_revwalk_free(walk);
|
||||
|
||||
/* Now let's eat up whatever the server gives us */
|
||||
if (!t->caps.multi_ack) {
|
||||
pkt_type = recv_pkt(NULL, buf);
|
||||
if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
|
||||
giterr_set(GITERR_NET, "Unexpected pkt type");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
git_pkt_ack *pkt;
|
||||
do {
|
||||
if (recv_pkt((git_pkt **)&pkt, buf) < 0)
|
||||
return -1;
|
||||
|
||||
if (pkt->type == GIT_PKT_NAK ||
|
||||
(pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
|
||||
git__free(pkt);
|
||||
break;
|
||||
}
|
||||
|
||||
git__free(pkt);
|
||||
} while (1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_revwalk_free(walk);
|
||||
git_buf_free(&data);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int no_sideband(transport_smart *t, git_indexer_stream *idx, gitno_buffer *buf, git_transfer_progress *stats)
|
||||
{
|
||||
int recvd;
|
||||
|
||||
do {
|
||||
if (t->cancelled.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
return GIT_EUSER;
|
||||
}
|
||||
|
||||
if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0)
|
||||
return -1;
|
||||
|
||||
gitno_consume_n(buf, buf->offset);
|
||||
|
||||
if ((recvd = gitno_recv(buf)) < 0)
|
||||
return -1;
|
||||
} while(recvd > 0);
|
||||
|
||||
if (git_indexer_stream_finalize(idx, stats))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct network_packetsize_payload
|
||||
{
|
||||
git_transfer_progress_callback callback;
|
||||
void *payload;
|
||||
git_transfer_progress *stats;
|
||||
git_off_t last_fired_bytes;
|
||||
};
|
||||
|
||||
static void network_packetsize(int received, void *payload)
|
||||
{
|
||||
struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
|
||||
|
||||
/* Accumulate bytes */
|
||||
npp->stats->received_bytes += received;
|
||||
|
||||
/* Fire notification if the threshold is reached */
|
||||
if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
|
||||
npp->last_fired_bytes = npp->stats->received_bytes;
|
||||
npp->callback(npp->stats, npp->payload);
|
||||
}
|
||||
}
|
||||
|
||||
int git_smart__download_pack(
|
||||
git_transport *transport,
|
||||
git_repository *repo,
|
||||
git_transfer_progress *stats,
|
||||
git_transfer_progress_callback progress_cb,
|
||||
void *progress_payload)
|
||||
{
|
||||
transport_smart *t = (transport_smart *)transport;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
gitno_buffer *buf = &t->buffer;
|
||||
git_indexer_stream *idx = NULL;
|
||||
int error = -1;
|
||||
struct network_packetsize_payload npp = {0};
|
||||
|
||||
if (progress_cb) {
|
||||
npp.callback = progress_cb;
|
||||
npp.payload = progress_payload;
|
||||
npp.stats = stats;
|
||||
t->packetsize_cb = &network_packetsize;
|
||||
t->packetsize_payload = &npp;
|
||||
}
|
||||
|
||||
if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0)
|
||||
return -1;
|
||||
|
||||
if (git_indexer_stream_new(&idx, git_buf_cstr(&path), progress_cb, progress_payload) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_buf_free(&path);
|
||||
memset(stats, 0, sizeof(git_transfer_progress));
|
||||
|
||||
/*
|
||||
* If the remote doesn't support the side-band, we can feed
|
||||
* the data directly to the indexer. Otherwise, we need to
|
||||
* check which one belongs there.
|
||||
*/
|
||||
if (!t->caps.side_band && !t->caps.side_band_64k) {
|
||||
if (no_sideband(t, idx, buf, stats) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_indexer_stream_free(idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
do {
|
||||
git_pkt *pkt;
|
||||
|
||||
if (t->cancelled.val) {
|
||||
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
|
||||
error = GIT_EUSER;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (recv_pkt(&pkt, buf) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (pkt->type == GIT_PKT_PROGRESS) {
|
||||
if (t->progress_cb) {
|
||||
git_pkt_progress *p = (git_pkt_progress *) pkt;
|
||||
t->progress_cb(p->data, p->len, t->message_cb_payload);
|
||||
}
|
||||
git__free(pkt);
|
||||
} else if (pkt->type == GIT_PKT_DATA) {
|
||||
git_pkt_data *p = (git_pkt_data *) pkt;
|
||||
if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0)
|
||||
goto on_error;
|
||||
|
||||
git__free(pkt);
|
||||
} else if (pkt->type == GIT_PKT_FLUSH) {
|
||||
/* A flush indicates the end of the packfile */
|
||||
git__free(pkt);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
|
||||
if (git_indexer_stream_finalize(idx, stats) < 0)
|
||||
goto on_error;
|
||||
|
||||
git_indexer_stream_free(idx);
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_buf_free(&path);
|
||||
git_indexer_stream_free(idx);
|
||||
return error;
|
||||
}
|
458
src/transports/winhttp.c
Normal file
458
src/transports/winhttp.c
Normal file
@ -0,0 +1,458 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef GIT_WINHTTP
|
||||
|
||||
#include "git2.h"
|
||||
#include "git2/transport.h"
|
||||
#include "buffer.h"
|
||||
#include "posix.h"
|
||||
#include "netops.h"
|
||||
#include "smart.h"
|
||||
|
||||
#include <winhttp.h>
|
||||
#pragma comment(lib, "winhttp")
|
||||
|
||||
#define WIDEN2(s) L ## s
|
||||
#define WIDEN(s) WIDEN2(s)
|
||||
|
||||
#define MAX_CONTENT_TYPE_LEN 100
|
||||
|
||||
static const char *prefix_http = "http://";
|
||||
static const char *prefix_https = "https://";
|
||||
static const char *upload_pack_service = "upload-pack";
|
||||
static const char *upload_pack_ls_service_url = "/info/refs?service=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 *post_verb = L"POST";
|
||||
static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
||||
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
|
||||
SECURITY_FLAG_IGNORE_UNKNOWN_CA;
|
||||
|
||||
#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport_stream parent;
|
||||
const char *service;
|
||||
const char *service_url;
|
||||
const wchar_t *verb;
|
||||
HINTERNET request;
|
||||
unsigned sent_request : 1,
|
||||
received_response : 1;
|
||||
} winhttp_stream;
|
||||
|
||||
typedef struct {
|
||||
git_smart_subtransport parent;
|
||||
git_transport *owner;
|
||||
const char *path;
|
||||
char *host;
|
||||
char *port;
|
||||
HINTERNET session;
|
||||
HINTERNET connection;
|
||||
unsigned use_ssl : 1,
|
||||
no_check_cert : 1;
|
||||
} winhttp_subtransport;
|
||||
|
||||
static int winhttp_stream_connect(winhttp_stream *s)
|
||||
{
|
||||
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
|
||||
wchar_t *types[] = { L"*/*", NULL };
|
||||
|
||||
/* Prepare URL */
|
||||
git_buf_printf(&buf, "%s%s", t->path, s->service_url);
|
||||
|
||||
if (git_buf_oom(&buf))
|
||||
return -1;
|
||||
|
||||
git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
|
||||
|
||||
/* Establish request */
|
||||
s->request = WinHttpOpenRequest(
|
||||
t->connection,
|
||||
s->verb,
|
||||
url,
|
||||
NULL,
|
||||
WINHTTP_NO_REFERER,
|
||||
types,
|
||||
t->use_ssl ? WINHTTP_FLAG_SECURE : 0);
|
||||
|
||||
if (!s->request) {
|
||||
giterr_set(GITERR_OS, "Failed to open request");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
/* Send Content-Type header -- only necessary on a POST */
|
||||
if (post_verb == s->verb) {
|
||||
git_buf_clear(&buf);
|
||||
if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0)
|
||||
goto on_error;
|
||||
|
||||
git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf));
|
||||
|
||||
if (WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) {
|
||||
giterr_set(GITERR_OS, "Failed to add a header to the request");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* If requested, disable certificate validation */
|
||||
if (t->use_ssl && t->no_check_cert) {
|
||||
if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
|
||||
(LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
|
||||
giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* We've done everything up to calling WinHttpSendRequest. */
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_buf_free(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int winhttp_stream_read(
|
||||
git_smart_subtransport_stream *stream,
|
||||
char *buffer,
|
||||
size_t buf_size,
|
||||
size_t *bytes_read)
|
||||
{
|
||||
winhttp_stream *s = (winhttp_stream *)stream;
|
||||
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
|
||||
/* Connect if necessary */
|
||||
if (!s->request && winhttp_stream_connect(s) < 0)
|
||||
return -1;
|
||||
|
||||
if (!s->sent_request &&
|
||||
!WinHttpSendRequest(s->request,
|
||||
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
WINHTTP_NO_REQUEST_DATA, 0,
|
||||
0, 0)) {
|
||||
giterr_set(GITERR_OS, "Failed to send request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
s->sent_request = 1;
|
||||
|
||||
if (!s->received_response) {
|
||||
DWORD status_code, status_code_length, content_type_length;
|
||||
char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
|
||||
wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
|
||||
|
||||
if (!WinHttpReceiveResponse(s->request, 0)) {
|
||||
giterr_set(GITERR_OS, "Failed to receive response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify that we got a 200 back */
|
||||
status_code_length = sizeof(status_code);
|
||||
|
||||
if (!WinHttpQueryHeaders(s->request,
|
||||
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
&status_code, &status_code_length,
|
||||
WINHTTP_NO_HEADER_INDEX)) {
|
||||
giterr_set(GITERR_OS, "Failed to retreive status code");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (HTTP_STATUS_OK != status_code) {
|
||||
giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify that we got the correct content-type back */
|
||||
if (post_verb == s->verb)
|
||||
snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
|
||||
else
|
||||
snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
|
||||
|
||||
git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8);
|
||||
content_type_length = sizeof(content_type);
|
||||
|
||||
if (!WinHttpQueryHeaders(s->request,
|
||||
WINHTTP_QUERY_CONTENT_TYPE,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
&content_type, &content_type_length,
|
||||
WINHTTP_NO_HEADER_INDEX)) {
|
||||
giterr_set(GITERR_OS, "Failed to retrieve response content-type");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (wcscmp(expected_content_type, content_type)) {
|
||||
giterr_set(GITERR_NET, "Received unexpected content-type");
|
||||
return -1;
|
||||
}
|
||||
|
||||
s->received_response = 1;
|
||||
}
|
||||
|
||||
if (!WinHttpReadData(s->request,
|
||||
(LPVOID)buffer,
|
||||
buf_size,
|
||||
(LPDWORD)bytes_read))
|
||||
{
|
||||
giterr_set(GITERR_OS, "Failed to read data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_stream_write(
|
||||
git_smart_subtransport_stream *stream,
|
||||
const char *buffer,
|
||||
size_t len)
|
||||
{
|
||||
winhttp_stream *s = (winhttp_stream *)stream;
|
||||
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
|
||||
DWORD bytes_written;
|
||||
|
||||
if (!s->request && winhttp_stream_connect(s) < 0)
|
||||
return -1;
|
||||
|
||||
/* Since we have to write the Content-Length header up front, we're
|
||||
* basically limited to a single call to write() per request. */
|
||||
if (!s->sent_request &&
|
||||
!WinHttpSendRequest(s->request,
|
||||
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
WINHTTP_NO_REQUEST_DATA, 0,
|
||||
(DWORD)len, 0)) {
|
||||
giterr_set(GITERR_OS, "Failed to send request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
s->sent_request = 1;
|
||||
|
||||
if (!WinHttpWriteData(s->request,
|
||||
(LPCVOID)buffer,
|
||||
(DWORD)len,
|
||||
&bytes_written)) {
|
||||
giterr_set(GITERR_OS, "Failed to send request");
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert((DWORD)len == bytes_written);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void winhttp_stream_free(git_smart_subtransport_stream *stream)
|
||||
{
|
||||
winhttp_stream *s = (winhttp_stream *)stream;
|
||||
|
||||
if (s->request) {
|
||||
WinHttpCloseHandle(s->request);
|
||||
s->request = NULL;
|
||||
}
|
||||
|
||||
git__free(s);
|
||||
}
|
||||
|
||||
static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream)
|
||||
{
|
||||
winhttp_stream *s;
|
||||
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
s = (winhttp_stream *)git__calloc(sizeof(winhttp_stream), 1);
|
||||
GITERR_CHECK_ALLOC(s);
|
||||
|
||||
s->parent.subtransport = &t->parent;
|
||||
s->parent.read = winhttp_stream_read;
|
||||
s->parent.write = winhttp_stream_write;
|
||||
s->parent.free = winhttp_stream_free;
|
||||
|
||||
*stream = (git_smart_subtransport_stream *)s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_connect(
|
||||
winhttp_subtransport *t,
|
||||
const char *url)
|
||||
{
|
||||
wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
|
||||
wchar_t host[GIT_WIN_PATH];
|
||||
int32_t port;
|
||||
const char *default_port;
|
||||
int ret;
|
||||
|
||||
if (!git__prefixcmp(url, prefix_http)) {
|
||||
url = url + strlen(prefix_http);
|
||||
default_port = "80";
|
||||
}
|
||||
|
||||
if (!git__prefixcmp(url, prefix_https)) {
|
||||
url += strlen(prefix_https);
|
||||
default_port = "443";
|
||||
t->use_ssl = 1;
|
||||
}
|
||||
|
||||
if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
|
||||
return ret;
|
||||
|
||||
t->path = strchr(url, '/');
|
||||
|
||||
/* Prepare port */
|
||||
if (git__strtol32(&port, t->port, NULL, 10) < 0)
|
||||
return -1;
|
||||
|
||||
/* Prepare host */
|
||||
git__utf8_to_16(host, GIT_WIN_PATH, t->host);
|
||||
|
||||
/* Establish session */
|
||||
t->session = WinHttpOpen(
|
||||
ua,
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
0);
|
||||
|
||||
if (!t->session) {
|
||||
giterr_set(GITERR_OS, "Failed to init WinHTTP");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Establish connection */
|
||||
t->connection = WinHttpConnect(
|
||||
t->session,
|
||||
host,
|
||||
port,
|
||||
0);
|
||||
|
||||
if (!t->connection) {
|
||||
giterr_set(GITERR_OS, "Failed to connect to host");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_uploadpack_ls(
|
||||
winhttp_subtransport *t,
|
||||
const char *url,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
winhttp_stream *s;
|
||||
|
||||
if (!t->connection &&
|
||||
winhttp_connect(t, url) < 0)
|
||||
return -1;
|
||||
|
||||
if (winhttp_stream_alloc(t, stream) < 0)
|
||||
return -1;
|
||||
|
||||
s = (winhttp_stream *)*stream;
|
||||
|
||||
s->service = upload_pack_service;
|
||||
s->service_url = upload_pack_ls_service_url;
|
||||
s->verb = get_verb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_uploadpack(
|
||||
winhttp_subtransport *t,
|
||||
const char *url,
|
||||
git_smart_subtransport_stream **stream)
|
||||
{
|
||||
winhttp_stream *s;
|
||||
|
||||
if (!t->connection &&
|
||||
winhttp_connect(t, url) < 0)
|
||||
return -1;
|
||||
|
||||
if (winhttp_stream_alloc(t, stream) < 0)
|
||||
return -1;
|
||||
|
||||
s = (winhttp_stream *)*stream;
|
||||
|
||||
s->service = upload_pack_service;
|
||||
s->service_url = upload_pack_service_url;
|
||||
s->verb = post_verb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winhttp_action(
|
||||
git_smart_subtransport_stream **stream,
|
||||
git_smart_subtransport *smart_transport,
|
||||
const char *url,
|
||||
git_smart_service_t action)
|
||||
{
|
||||
winhttp_subtransport *t = (winhttp_subtransport *)smart_transport;
|
||||
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GIT_SERVICE_UPLOADPACK_LS:
|
||||
return winhttp_uploadpack_ls(t, url, stream);
|
||||
|
||||
case GIT_SERVICE_UPLOADPACK:
|
||||
return winhttp_uploadpack(t, url, stream);
|
||||
}
|
||||
|
||||
*stream = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void winhttp_free(git_smart_subtransport *smart_transport)
|
||||
{
|
||||
winhttp_subtransport *t = (winhttp_subtransport *) smart_transport;
|
||||
|
||||
git__free(t->host);
|
||||
git__free(t->port);
|
||||
|
||||
if (t->connection) {
|
||||
WinHttpCloseHandle(t->connection);
|
||||
t->connection = NULL;
|
||||
}
|
||||
|
||||
if (t->session) {
|
||||
WinHttpCloseHandle(t->session);
|
||||
t->session = NULL;
|
||||
}
|
||||
|
||||
git__free(t);
|
||||
}
|
||||
|
||||
int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
|
||||
{
|
||||
winhttp_subtransport *t;
|
||||
int flags;
|
||||
|
||||
if (!out)
|
||||
return -1;
|
||||
|
||||
t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1);
|
||||
GITERR_CHECK_ALLOC(t);
|
||||
|
||||
t->owner = owner;
|
||||
t->parent.action = winhttp_action;
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* GIT_WINHTTP */
|
@ -1,5 +1,4 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "transport.h"
|
||||
#include "buffer.h"
|
||||
#include "path.h"
|
||||
#include "posix.h"
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "buffer.h"
|
||||
#include "refspec.h"
|
||||
#include "transport.h"
|
||||
#include "remote.h"
|
||||
|
||||
static git_remote *_remote;
|
||||
|
Loading…
Reference in New Issue
Block a user