mirror of
https://git.proxmox.com/git/libgit2
synced 2025-11-01 13:47:36 +00:00
Merge pull request #405 from carlosmn/http-ls
Implement ls-remote over HTTP
This commit is contained in:
commit
8114ee4c95
@ -22,7 +22,10 @@ STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1"
|
||||
SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}")
|
||||
|
||||
# Find required dependencies
|
||||
INCLUDE_DIRECTORIES(src include)
|
||||
INCLUDE_DIRECTORIES(src include deps/http-parser)
|
||||
|
||||
FILE(GLOB SRC_HTTP deps/http-parser/*.c)
|
||||
|
||||
IF (NOT WIN32)
|
||||
FIND_PACKAGE(ZLIB)
|
||||
ENDIF()
|
||||
@ -91,7 +94,7 @@ ELSE()
|
||||
ENDIF ()
|
||||
|
||||
# Compile and link libgit2
|
||||
ADD_LIBRARY(git2 ${SRC} ${SRC_ZLIB})
|
||||
ADD_LIBRARY(git2 ${SRC} ${SRC_ZLIB} ${SRC_HTTP})
|
||||
|
||||
IF (WIN32)
|
||||
TARGET_LINK_LIBRARIES(git2 ws2_32)
|
||||
@ -122,7 +125,7 @@ IF (BUILD_TESTS)
|
||||
INCLUDE_DIRECTORIES(tests)
|
||||
FILE(GLOB SRC_TEST tests/t??-*.c)
|
||||
|
||||
ADD_EXECUTABLE(libgit2_test tests/test_main.c tests/test_lib.c tests/test_helpers.c ${SRC} ${SRC_TEST} ${SRC_ZLIB})
|
||||
ADD_EXECUTABLE(libgit2_test tests/test_main.c tests/test_lib.c tests/test_helpers.c ${SRC} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP})
|
||||
TARGET_LINK_LIBRARIES(libgit2_test ${CMAKE_THREAD_LIBS_INIT})
|
||||
IF (WIN32)
|
||||
TARGET_LINK_LIBRARIES(libgit2_test ws2_32)
|
||||
|
||||
23
deps/http-parser/LICENSE-MIT
vendored
Normal file
23
deps/http-parser/LICENSE-MIT
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
|
||||
Igor Sysoev.
|
||||
|
||||
Additional changes are licensed under the same terms as NGINX and
|
||||
copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
1776
deps/http-parser/http_parser.c
vendored
Normal file
1776
deps/http-parser/http_parser.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
276
deps/http-parser/http_parser.h
vendored
Normal file
276
deps/http-parser/http_parser.h
vendored
Normal file
@ -0,0 +1,276 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HTTP_PARSER_VERSION_MAJOR 1
|
||||
#define HTTP_PARSER_VERSION_MINOR 0
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(_MSC_VER)
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
|
||||
typedef unsigned int size_t;
|
||||
typedef int ssize_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to
|
||||
* the error reporting facility.
|
||||
*/
|
||||
#ifndef HTTP_PARSER_DEBUG
|
||||
# define HTTP_PARSER_DEBUG 0
|
||||
#endif
|
||||
|
||||
|
||||
/* Maximium header size allowed */
|
||||
#define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
typedef struct http_parser_result http_parser_result;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be call arbitrarally
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_path"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
/* Request Methods */
|
||||
enum http_method
|
||||
{ HTTP_DELETE = 0
|
||||
, HTTP_GET
|
||||
, HTTP_HEAD
|
||||
, HTTP_POST
|
||||
, HTTP_PUT
|
||||
/* pathological */
|
||||
, HTTP_CONNECT
|
||||
, HTTP_OPTIONS
|
||||
, HTTP_TRACE
|
||||
/* webdav */
|
||||
, HTTP_COPY
|
||||
, HTTP_LOCK
|
||||
, HTTP_MKCOL
|
||||
, HTTP_MOVE
|
||||
, HTTP_PROPFIND
|
||||
, HTTP_PROPPATCH
|
||||
, HTTP_UNLOCK
|
||||
/* subversion */
|
||||
, HTTP_REPORT
|
||||
, HTTP_MKACTIVITY
|
||||
, HTTP_CHECKOUT
|
||||
, HTTP_MERGE
|
||||
/* upnp */
|
||||
, HTTP_MSEARCH
|
||||
, HTTP_NOTIFY
|
||||
, HTTP_SUBSCRIBE
|
||||
, HTTP_UNSUBSCRIBE
|
||||
/* RFC-5789 */
|
||||
, HTTP_PATCH
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_TRAILING = 1 << 3
|
||||
, F_UPGRADE = 1 << 4
|
||||
, F_SKIPBODY = 1 << 5
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_path, "the on_path callback failed") \
|
||||
XX(CB_query_string, "the on_query_string callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_fragment, "the on_fragment callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(UNKNOWN, "an unknown error occurred")
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
/* Get the line number that generated the current error */
|
||||
#if HTTP_PARSER_DEBUG
|
||||
#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno)
|
||||
#else
|
||||
#define HTTP_PARSER_ERRNO_LINE(p) 0
|
||||
#endif
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned char type : 2;
|
||||
unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned char state;
|
||||
unsigned char header_state;
|
||||
unsigned char index;
|
||||
|
||||
uint32_t nread;
|
||||
int64_t content_length;
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned short status_code; /* responses only */
|
||||
unsigned char method; /* requests only */
|
||||
unsigned char http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned char upgrade : 1;
|
||||
|
||||
#if HTTP_PARSER_DEBUG
|
||||
uint32_t error_lineno;
|
||||
#endif
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
};
|
||||
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns true, then this will be should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
12
src/buffer.c
12
src/buffer.c
@ -99,3 +99,15 @@ void git_buf_free(git_buf *buf)
|
||||
{
|
||||
free(buf->ptr);
|
||||
}
|
||||
|
||||
void git_buf_clear(git_buf *buf)
|
||||
{
|
||||
buf->size = 0;
|
||||
}
|
||||
|
||||
void git_buf_consume(git_buf *buf, const char *end)
|
||||
{
|
||||
size_t consumed = end - buf->ptr;
|
||||
memmove(buf->ptr, end, buf->size - consumed);
|
||||
buf->size -= consumed;
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ void git_buf_puts(git_buf *buf, const char *string);
|
||||
void git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
|
||||
const char *git_buf_cstr(git_buf *buf);
|
||||
void git_buf_free(git_buf *buf);
|
||||
void git_buf_clear(git_buf *buf);
|
||||
void git_buf_consume(git_buf *buf, const char *end);
|
||||
|
||||
#define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1)
|
||||
|
||||
|
||||
35
src/netops.c
35
src/netops.c
@ -27,7 +27,7 @@ void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, int fd)
|
||||
memset(buf, 0x0, sizeof(gitno_buffer));
|
||||
memset(data, 0x0, len);
|
||||
buf->data = data;
|
||||
buf->len = len - 1;
|
||||
buf->len = len;
|
||||
buf->offset = 0;
|
||||
buf->fd = fd;
|
||||
}
|
||||
@ -84,6 +84,7 @@ int gitno_connect(const char *host, const char *port)
|
||||
ret = getaddrinfo(host, port, &hints, &info);
|
||||
if (ret != 0) {
|
||||
error = GIT_EOSERR;
|
||||
info = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@ -121,7 +122,7 @@ int gitno_send(int s, const char *msg, size_t len, int flags)
|
||||
while (off < len) {
|
||||
ret = send(s, msg + off, len - off, flags);
|
||||
if (ret < 0)
|
||||
return GIT_EOSERR;
|
||||
return git__throw(GIT_EOSERR, "Error sending data: %s", strerror(errno));
|
||||
|
||||
off += ret;
|
||||
}
|
||||
@ -143,3 +144,33 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
|
||||
/* The select(2) interface is silly */
|
||||
return select(buf->fd + 1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
|
||||
{
|
||||
char *colon, *slash, *delim;
|
||||
int error = GIT_SUCCESS;
|
||||
|
||||
colon = strchr(url, ':');
|
||||
slash = strchr(url, '/');
|
||||
|
||||
if (slash == NULL)
|
||||
return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /");
|
||||
|
||||
if (colon == NULL) {
|
||||
*port = git__strdup(default_port);
|
||||
} else {
|
||||
*port = git__strndup(colon + 1, slash - colon - 1);
|
||||
}
|
||||
if (*port == NULL)
|
||||
return GIT_ENOMEM;;
|
||||
|
||||
|
||||
delim = colon == NULL ? slash : colon;
|
||||
*host = git__strndup(url, delim - url);
|
||||
if (*host == NULL) {
|
||||
free(*port);
|
||||
error = GIT_ENOMEM;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -29,4 +29,6 @@ int gitno_connect(const char *host, const char *port);
|
||||
int gitno_send(int s, const char *msg, size_t len, int flags);
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
26
src/pkt.c
26
src/pkt.c
@ -80,13 +80,30 @@ static int pack_pkt(git_pkt **out)
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int comment_pkt(git_pkt **out, const char *line, size_t len)
|
||||
{
|
||||
git_pkt_comment *pkt;
|
||||
|
||||
pkt = git__malloc(sizeof(git_pkt_comment) + len + 1);
|
||||
if (pkt == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
pkt->type = GIT_PKT_COMMENT;
|
||||
memcpy(pkt->comment, line, len);
|
||||
pkt->comment[len] = '\0';
|
||||
|
||||
*out = (git_pkt *) pkt;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse an other-ref line.
|
||||
*/
|
||||
static int ref_pkt(git_pkt **out, const char *line, size_t len)
|
||||
{
|
||||
git_pkt_ref *pkt;
|
||||
int error, has_caps = 0;
|
||||
int error;
|
||||
|
||||
pkt = git__malloc(sizeof(git_pkt_ref));
|
||||
if (pkt == NULL)
|
||||
@ -110,9 +127,6 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len)
|
||||
line += GIT_OID_HEXSZ + 1;
|
||||
len -= (GIT_OID_HEXSZ + 1);
|
||||
|
||||
if (strlen(line) < len)
|
||||
has_caps = 1;
|
||||
|
||||
if (line[len - 1] == '\n')
|
||||
--len;
|
||||
|
||||
@ -124,7 +138,7 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len)
|
||||
memcpy(pkt->head.name, line, len);
|
||||
pkt->head.name[len] = '\0';
|
||||
|
||||
if (has_caps) {
|
||||
if (strlen(pkt->head.name) < len) {
|
||||
pkt->capabilities = strchr(pkt->head.name, '\0') + 1;
|
||||
}
|
||||
|
||||
@ -227,6 +241,8 @@ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_
|
||||
error = ack_pkt(head, line, len);
|
||||
else if (!git__prefixcmp(line, "NAK"))
|
||||
error = nak_pkt(head);
|
||||
else if (*line == '#')
|
||||
error = comment_pkt(head, line, len);
|
||||
else
|
||||
error = ref_pkt(head, line, len);
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ enum git_pkt_type {
|
||||
GIT_PKT_ACK,
|
||||
GIT_PKT_NAK,
|
||||
GIT_PKT_PACK,
|
||||
GIT_PKT_COMMENT,
|
||||
};
|
||||
|
||||
/* Used for multi-ack */
|
||||
@ -56,6 +57,11 @@ typedef struct {
|
||||
enum git_ack_status status;
|
||||
} git_pkt_ack;
|
||||
|
||||
typedef struct {
|
||||
enum git_pkt_type type;
|
||||
char comment[GIT_FLEX_ARRAY];
|
||||
} git_pkt_comment;
|
||||
|
||||
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
|
||||
int git_pkt_send_flush(int s);
|
||||
int git_pkt_send_done(int s);
|
||||
|
||||
403
src/transport-http.c
Normal file
403
src/transport-http.c
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* In addition to the permissions in the GNU General Public License,
|
||||
* the authors give you unlimited permission to link the compiled
|
||||
* version of this file into combinations with other programs,
|
||||
* and to distribute those combinations without any restriction
|
||||
* coming from the use of this file. (The General Public License
|
||||
* restrictions do apply in other respects; for example, they cover
|
||||
* modification of the file, and distribution when not linked into
|
||||
* a combined executable.)
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "git2.h"
|
||||
#include "http_parser.h"
|
||||
|
||||
#include "transport.h"
|
||||
#include "common.h"
|
||||
#include "netops.h"
|
||||
#include "buffer.h"
|
||||
#include "pkt.h"
|
||||
|
||||
typedef enum {
|
||||
NONE,
|
||||
FIELD,
|
||||
VALUE
|
||||
} last_cb_type;
|
||||
|
||||
typedef struct {
|
||||
git_transport parent;
|
||||
git_vector refs;
|
||||
int socket;
|
||||
git_buf buf;
|
||||
git_remote_head **heads;
|
||||
int error;
|
||||
int transfer_finished :1,
|
||||
ct_found :1,
|
||||
ct_finished :1,
|
||||
last_cb :3;
|
||||
char *content_type;
|
||||
char *service;
|
||||
} transport_http;
|
||||
|
||||
static int gen_request(git_buf *buf, const char *url, const char *host, const char *service)
|
||||
{
|
||||
const char *path = url;
|
||||
|
||||
path = strchr(path, '/');
|
||||
if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
|
||||
path = "/";
|
||||
|
||||
git_buf_printf(buf, "GET %s/info/refs?service=git-%s HTTP/1.1\r\n", path, service);
|
||||
git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
|
||||
git_buf_printf(buf, "Host: %s\r\n", host);
|
||||
git_buf_puts(buf, "Accept: */*\r\n" "Pragma: no-cache\r\n\r\n");
|
||||
|
||||
if (git_buf_oom(buf))
|
||||
return GIT_ENOMEM;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int do_connect(transport_http *t, const char *service)
|
||||
{
|
||||
int s = -1, error;;
|
||||
const char *url = t->parent.url, *prefix = "http://";
|
||||
char *host = NULL, *port = NULL;
|
||||
git_buf request = GIT_BUF_INIT;
|
||||
|
||||
if (!git__prefixcmp(url, prefix))
|
||||
url += strlen(prefix);
|
||||
|
||||
error = gitno_extract_host_and_port(&host, &port, url, "80");
|
||||
if (error < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
t->service = git__strdup(service);
|
||||
if (t->service == NULL) {
|
||||
error = GIT_ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
s = gitno_connect(host, port);
|
||||
if (s < GIT_SUCCESS) {
|
||||
error = git__throw(error, "Failed to connect to host");
|
||||
}
|
||||
t->socket = s;
|
||||
|
||||
/* Generate and send the HTTP request */
|
||||
error = gen_request(&request, url, host, service);
|
||||
if (error < GIT_SUCCESS) {
|
||||
error = git__throw(error, "Failed to generate request");
|
||||
goto cleanup;
|
||||
}
|
||||
error = gitno_send(s, git_buf_cstr(&request), strlen(git_buf_cstr(&request)), 0);
|
||||
if (error < GIT_SUCCESS)
|
||||
error = git__rethrow(error, "Failed to send the HTTP request");
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&request);
|
||||
free(host);
|
||||
free(port);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
if (t->last_cb == VALUE && t->ct_found) {
|
||||
t->ct_finished = 1;
|
||||
t->ct_found = 0;
|
||||
t->content_type = git__strdup(git_buf_cstr(buf));
|
||||
if (t->content_type == NULL)
|
||||
return t->error = GIT_ENOMEM;
|
||||
git_buf_clear(buf);
|
||||
}
|
||||
|
||||
if (t->ct_found) {
|
||||
t->last_cb = FIELD;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (t->last_cb != FIELD)
|
||||
git_buf_clear(buf);
|
||||
|
||||
git_buf_put(buf, str, len);
|
||||
t->last_cb = FIELD;
|
||||
|
||||
return git_buf_oom(buf);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (t->ct_finished) {
|
||||
t->last_cb = VALUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (t->last_cb == VALUE)
|
||||
git_buf_put(buf, str, len);
|
||||
|
||||
if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
|
||||
t->ct_found = 1;
|
||||
git_buf_clear(buf);
|
||||
git_buf_put(buf, str, len);
|
||||
}
|
||||
|
||||
t->last_cb = VALUE;
|
||||
|
||||
return git_buf_oom(buf);
|
||||
}
|
||||
|
||||
static int on_headers_complete(http_parser *parser)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
git_buf *buf = &t->buf;
|
||||
|
||||
if (t->content_type == NULL) {
|
||||
t->content_type = git__strdup(git_buf_cstr(buf));
|
||||
if (t->content_type == NULL)
|
||||
return t->error = GIT_ENOMEM;
|
||||
}
|
||||
|
||||
git_buf_clear(buf);
|
||||
git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
|
||||
if (git_buf_oom(buf))
|
||||
return GIT_ENOMEM;
|
||||
|
||||
if (strcmp(t->content_type, git_buf_cstr(buf)))
|
||||
return t->error = git__throw(GIT_EOBJCORRUPTED, "Content-Type '%s' is wrong", t->content_type);
|
||||
|
||||
git_buf_clear(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_body_store_refs(http_parser *parser, const char *str, size_t len)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
git_buf *buf = &t->buf;
|
||||
git_vector *refs = &t->refs;
|
||||
int error;
|
||||
const char *line_end, *ptr;
|
||||
static int first_pkt = 1;
|
||||
|
||||
if (len == 0) { /* EOF */
|
||||
if (buf->size != 0)
|
||||
return t->error = git__throw(GIT_ERROR, "EOF and unprocessed data");
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
git_buf_put(buf, str, len);
|
||||
ptr = buf->ptr;
|
||||
while (1) {
|
||||
git_pkt *pkt;
|
||||
|
||||
if (buf->size == 0)
|
||||
return 0;
|
||||
|
||||
error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size);
|
||||
if (error == GIT_ESHORTBUFFER)
|
||||
return 0; /* Ask for more */
|
||||
if (error < GIT_SUCCESS)
|
||||
return t->error = git__rethrow(error, "Failed to parse pkt-line");
|
||||
|
||||
git_buf_consume(buf, line_end);
|
||||
|
||||
if (first_pkt) {
|
||||
first_pkt = 0;
|
||||
if (pkt->type != GIT_PKT_COMMENT)
|
||||
return t->error = git__throw(GIT_EOBJCORRUPTED, "Not a valid smart HTTP response");
|
||||
}
|
||||
|
||||
error = git_vector_insert(refs, pkt);
|
||||
if (error < GIT_SUCCESS)
|
||||
return t->error = git__rethrow(error, "Failed to add pkt to list");
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int on_message_complete(http_parser *parser)
|
||||
{
|
||||
transport_http *t = (transport_http *) parser->data;
|
||||
|
||||
t->transfer_finished = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int store_refs(transport_http *t)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
http_parser parser;
|
||||
http_parser_settings settings;
|
||||
char buffer[1024];
|
||||
gitno_buffer buf;
|
||||
|
||||
http_parser_init(&parser, HTTP_RESPONSE);
|
||||
parser.data = t;
|
||||
memset(&settings, 0x0, sizeof(http_parser_settings));
|
||||
settings.on_header_field = on_header_field;
|
||||
settings.on_header_value = on_header_value;
|
||||
settings.on_headers_complete = on_headers_complete;
|
||||
settings.on_body = on_body_store_refs;
|
||||
settings.on_message_complete = on_message_complete;
|
||||
|
||||
gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);
|
||||
|
||||
while(1) {
|
||||
size_t parsed;
|
||||
|
||||
error = gitno_recv(&buf);
|
||||
if (error < GIT_SUCCESS)
|
||||
return git__rethrow(error, "Error receiving data from network");
|
||||
|
||||
parsed = http_parser_execute(&parser, &settings, buf.data, buf.offset);
|
||||
/* Both should happen at the same time */
|
||||
if (parsed != buf.offset || t->error < GIT_SUCCESS)
|
||||
return git__rethrow(t->error, "Error parsing HTTP data");
|
||||
|
||||
gitno_consume_n(&buf, parsed);
|
||||
|
||||
if (error == 0 || t->transfer_finished)
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int http_connect(git_transport *transport, int direction)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
int error;
|
||||
|
||||
if (direction == GIT_DIR_PUSH)
|
||||
return git__throw(GIT_EINVALIDARGS, "Pushing over HTTP is not supported");
|
||||
|
||||
t->parent.direction = direction;
|
||||
error = git_vector_init(&t->refs, 16, NULL);
|
||||
if (error < GIT_SUCCESS)
|
||||
return git__rethrow(error, "Failed to init refs vector");
|
||||
|
||||
error = do_connect(t, "upload-pack");
|
||||
if (error < GIT_SUCCESS) {
|
||||
error = git__rethrow(error, "Failed to connect to host");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
error = store_refs(t);
|
||||
|
||||
cleanup:
|
||||
git_buf_clear(&t->buf);
|
||||
git_buf_free(&t->buf);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int http_ls(git_transport *transport, git_headarray *array)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
git_vector *refs = &t->refs;
|
||||
unsigned int i;
|
||||
int len = 0;
|
||||
git_pkt_ref *p;
|
||||
|
||||
array->heads = git__calloc(refs->length, sizeof(git_remote_head*));
|
||||
if (array->heads == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
git_vector_foreach(refs, i, p) {
|
||||
if (p->type != GIT_PKT_REF)
|
||||
continue;
|
||||
|
||||
array->heads[len] = &p->head;
|
||||
len++;
|
||||
}
|
||||
|
||||
array->len = len;
|
||||
t->heads = array->heads;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int http_close(git_transport *transport)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
int error;
|
||||
|
||||
error = close(t->socket);
|
||||
if (error < 0)
|
||||
return git__throw(GIT_EOSERR, "Failed to close the socket: %s", strerror(errno));
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static void http_free(git_transport *transport)
|
||||
{
|
||||
transport_http *t = (transport_http *) transport;
|
||||
git_vector *refs = &t->refs;
|
||||
unsigned int i;
|
||||
git_pkt *p;
|
||||
|
||||
git_vector_foreach(refs, i, p) {
|
||||
git_pkt_free(p);
|
||||
}
|
||||
git_vector_free(refs);
|
||||
free(t->heads);
|
||||
free(t->content_type);
|
||||
free(t->service);
|
||||
free(t->parent.url);
|
||||
free(t);
|
||||
}
|
||||
|
||||
|
||||
int git_transport_http(git_transport **out)
|
||||
{
|
||||
transport_http *t;
|
||||
|
||||
t = git__malloc(sizeof(transport_http));
|
||||
if (t == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
memset(t, 0x0, sizeof(transport_http));
|
||||
|
||||
t->parent.connect = http_connect;
|
||||
t->parent.ls = http_ls;
|
||||
t->parent.close = http_close;
|
||||
t->parent.free = http_free;
|
||||
|
||||
*out = (git_transport *) t;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
@ -15,7 +15,7 @@ struct {
|
||||
git_transport_cb fn;
|
||||
} transports[] = {
|
||||
{"git://", git_transport_git},
|
||||
{"http://", git_transport_dummy},
|
||||
{"http://", git_transport_http},
|
||||
{"https://", git_transport_dummy},
|
||||
{"file://", git_transport_local},
|
||||
{"git+ssh://", git_transport_dummy},
|
||||
|
||||
@ -107,6 +107,7 @@ struct git_transport {
|
||||
|
||||
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_dummy(struct git_transport **transport);
|
||||
|
||||
#endif
|
||||
|
||||
@ -84,37 +84,6 @@ cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
/* The URL should already have been stripped of the protocol */
|
||||
static int extract_host_and_port(char **host, char **port, const char *url)
|
||||
{
|
||||
char *colon, *slash, *delim;
|
||||
int error = GIT_SUCCESS;
|
||||
|
||||
colon = strchr(url, ':');
|
||||
slash = strchr(url, '/');
|
||||
|
||||
if (slash == NULL)
|
||||
return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /");
|
||||
|
||||
if (colon == NULL) {
|
||||
*port = git__strdup(GIT_DEFAULT_PORT);
|
||||
} else {
|
||||
*port = git__strndup(colon + 1, slash - colon - 1);
|
||||
}
|
||||
if (*port == NULL)
|
||||
return GIT_ENOMEM;;
|
||||
|
||||
|
||||
delim = colon == NULL ? slash : colon;
|
||||
*host = git__strndup(url, delim - url);
|
||||
if (*host == NULL) {
|
||||
free(*port);
|
||||
error = GIT_ENOMEM;
|
||||
}
|
||||
|
||||
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
|
||||
@ -130,9 +99,10 @@ static int do_connect(transport_git *t, const char *url)
|
||||
if (!git__prefixcmp(url, prefix))
|
||||
url += strlen(prefix);
|
||||
|
||||
error = extract_host_and_port(&host, &port, url);
|
||||
error = gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
s = gitno_connect(host, port);
|
||||
connected = 1;
|
||||
error = send_request(s, NULL, url);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user