From: Omar Polo Subject: WIP: read-only https support To: gameoftrees@openbsd.org Date: Sun, 20 Nov 2022 10:45:14 +0100 just scratching an itch; there are plenty of web forges online and I don't have an account for every one of them (not that I want to). I can use git to clone, but then i'd miss all the niceties :) Diff belows adds an initial read-only HTTP/S support for got fetch and clone. The code is incomplete, wip, etc... use it at your own risk. Sharing just in case somebody wants to play along. This is done with a new libexec helper "got-http" (an alternative name could be "got-dial-http"?) To minimize the changes needed to the dial and fetch_pack API I decided to write an helper that behaves like ssh(1) as far as got is concerned. Under the hood, it transforms what got asks into HTTP requests. Only the "smart" HTTP protocol is supported, the "dumb" one not. (as of now at least) The "smart" HTTP protocol behaves almost as git over ssh, but needs two HTTP requests: - a first one to do the "discovery" (see if the remote server is "smart") and fetch the refs - a POST where we send our have/want line and fetch the packfile The dumb one is just a bare git repo served via a web server (could be httpd(8)) and needs us to fetch all the objects manually and do the resolving by ourselves. To be fair I'm not thrilled at the idea of implementing it. got-http is pledged "stdio inet dns" and is not unveiled by default unlike the other libexec helpers. It also can't be sandboxed with capsicum(4) on FreeBSD and I don't want to go thru the pain of trying to sandbox it with landlock on linux (needs to access certs.pem and probably more stuff there?) At the moment it "works." I managed to clone repos from github (including ports.git) and from sr.ht. Incremental fetches also seems to work, in part at least. There's still some bits of how the server replies that I'm not following. For example, here's an excerpt of a partial fetch: 00000000 30 30 30 38 4e 41 4b 0a 30 30 32 39 02 45 6e 75 |0008NAK.0029.Enu| 00000010 6d 65 72 61 74 69 6e 67 20 6f 62 6a 65 63 74 73 |merating objects| 00000020 3a 20 31 37 37 33 39 32 34 2c 20 64 6f 6e 65 2e |: 1773924, done.| 00000030 0a 30 30 32 36 02 43 6f 75 6e 74 69 6e 67 20 6f |.0026.Counting o| ... 000021a0 30 25 20 28 37 39 34 30 2f 37 39 34 30 29 2c 20 |0% (7940/7940), | 000021b0 64 6f 6e 65 2e 0a 30 30 31 30 01 50 41 43 4b 00 |done..0010.PACK.| 000021c0 00 00 02 00 1b 11 32 30 30 35 01 64 93 16 78 9c |......2005.d..x.| We get a NAK and then side-band info, which seems to confuse got-fetch-pack that excepts at least one ACK. (see the XXX below.) In some case partial fetches seems to degrade into a full fetch, still investigating. diff /home/op/w/gotd commit - 4cad5be9f88baeb0583b4b63a546f5815929a270 path + /home/op/w/gotd blob - ad0fec63ec6030f40937e7bf6836769359bb55ba file + got/got.c --- got/got.c +++ got/got.c @@ -1649,16 +1649,16 @@ cmd_clone(int argc, char *argv[]) err(1, "pledge"); #endif } else if (strcmp(proto, "git+ssh") == 0 || - strcmp(proto, "ssh") == 0) { + strcmp(proto, "ssh") == 0 || + strcmp(proto, "git+http") == 0 || + strcmp(proto, "http") == 0 || + strcmp(proto, "git+https") == 0 || + strcmp(proto, "https") == 0) { #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec " "sendfd unveil", NULL) == -1) err(1, "pledge"); #endif - } else if (strcmp(proto, "http") == 0 || - strcmp(proto, "git+http") == 0) { - error = got_error_path(proto, GOT_ERR_NOT_IMPL); - goto done; } else { error = got_error_path(proto, GOT_ERR_BAD_PROTO); goto done; @@ -2514,16 +2514,16 @@ cmd_fetch(int argc, char *argv[]) err(1, "pledge"); #endif } else if (strcmp(proto, "git+ssh") == 0 || - strcmp(proto, "ssh") == 0) { + strcmp(proto, "ssh") == 0 || + strcmp(proto, "git+http") == 0 || + strcmp(proto, "http") == 0 || + strcmp(proto, "git+https") == 0 || + strcmp(proto, "https") == 0) { #ifndef PROFILE if (pledge("stdio rpath wpath cpath fattr flock proc exec " "sendfd unveil", NULL) == -1) err(1, "pledge"); #endif - } else if (strcmp(proto, "http") == 0 || - strcmp(proto, "git+http") == 0) { - error = got_error_path(proto, GOT_ERR_NOT_IMPL); - goto done; } else { error = got_error_path(proto, GOT_ERR_BAD_PROTO); goto done; blob - 3325c8994f55721d8588155cdc72b95d11fd2248 file + lib/dial.c --- lib/dial.c +++ lib/dial.c @@ -18,19 +18,28 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include #include #include #include +#include #include "got_error.h" #include "got_path.h" +#include "got_object.h" #include "got_lib_dial.h" +#include "got_lib_delta.h" +#include "got_lib_object.h" +#include "got_lib_privsep.h" #include "got_dial.h" #ifndef nitems @@ -63,6 +72,13 @@ got_dial_apply_unveil(const char *proto) } } + if (strstr(proto, "http") != NULL) { + if (unveil(GOT_PATH_PROG_HTTP, "x") != 0) { + return got_error_from_errno2("unveil", + GOT_PATH_PROG_HTTP); + } + } + return NULL; } @@ -321,3 +337,59 @@ done: *newfd = fd; return err; } + +const struct got_error * +got_dial_http(pid_t *newpid, int *newfd, const char *host, + const char *port, const char *path, int verbosity, int tls) +{ + const struct got_error *error = NULL; + int pid, pfd[2]; + const char *argv[8]; + int i = 0; + + *newpid = -1; + *newfd = -1; + + if (!port) + port = tls ? "443" : "80"; + + argv[i++] = GOT_PATH_PROG_HTTP; + if (verbosity == -1) + argv[i++] = "-q"; + else if (verbosity > 0) + argv[i++] = "-v"; + argv[i++] = "--"; + argv[i++] = tls ? "https" : "http"; + argv[i++] = host; + argv[i++] = port; + argv[i++] = path; + argv[i++] = NULL; + assert(i <= nitems(argv)); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pfd) == -1) + return got_error_from_errno("socketpair"); + + pid = fork(); + if (pid == -1) { + error = got_error_from_errno("fork"); + close(pfd[0]); + close(pfd[1]); + return error; + } else if (pid == 0) { + if (close(pfd[1]) == -1) + err(1, "close"); + if (dup2(pfd[0], 0) == -1) + err(1, "dup2"); + if (dup2(pfd[0], 1) == -1) + err(1, "dup2"); + if (execv(GOT_PATH_PROG_HTTP, (char *const *)argv) == -1) + err(1, "execv"); + abort(); /* not reached */ + } else { + if (close(pfd[0]) == -1) + return got_error_from_errno("close"); + *newpid = pid; + *newfd = pfd[1]; + return NULL; + } +} blob - 0dd641076040553ff06ef8320ae4686dabdd6786 file + lib/fetch.c --- lib/fetch.c +++ lib/fetch.c @@ -91,8 +91,12 @@ got_fetch_connect(pid_t *fetchpid, int *fetchfd, const else if (strcmp(proto, "git") == 0) err = got_dial_git(fetchfd, host, port, server_path, GOT_DIAL_DIRECTION_FETCH); - else if (strcmp(proto, "http") == 0 || strcmp(proto, "git+http") == 0) - err = got_error_path(proto, GOT_ERR_NOT_IMPL); + else if (strcmp(proto, "http") == 0 || + strcmp(proto, "git+http") == 0 || + strcmp(proto, "https") == 0 || + strcmp(proto, "git+https") == 0) + err = got_dial_http(fetchpid, fetchfd, host, port, + server_path, verbosity, strstr(proto, "https") != NULL); else err = got_error_path(proto, GOT_ERR_BAD_PROTO); return err; blob - cbaf4ea224445b13ae795d7ec9ddb72fda40ab52 file + lib/got_lib_dial.h --- lib/got_lib_dial.h +++ lib/got_lib_dial.h @@ -24,3 +24,6 @@ const struct got_error *got_dial_ssh(pid_t *newpid, in const struct got_error *got_dial_ssh(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, const char *direction, int verbosity); + +const struct got_error *got_dial_http(pid_t *newpid, int *newfd, + const char *host, const char *port, const char *path, int, int); blob - 37d537ab48623fa50dd909cf2725e58151958e35 file + lib/got_lib_privsep.h --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -48,6 +48,7 @@ #define GOT_PROG_FETCH_PACK got-fetch-pack #define GOT_PROG_INDEX_PACK got-index-pack #define GOT_PROG_SEND_PACK got-send-pack +#define GOT_PROG_HTTP got-http #define GOT_STRINGIFY(x) #x #define GOT_STRINGVAL(x) GOT_STRINGIFY(x) @@ -77,6 +78,8 @@ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_SEND_PACK) #define GOT_PATH_PROG_INDEX_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_INDEX_PACK) +#define GOT_PATH_PROG_HTTP \ + GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_HTTP) enum got_imsg_type { /* An error occured while processing a request. */ blob - 174d412f27d59a5180400ca4bbd33f6a4b8e5564 file + lib/privsep.c --- lib/privsep.c +++ lib/privsep.c @@ -3483,6 +3483,7 @@ got_privsep_unveil_exec_helpers(void) GOT_PATH_PROG_FETCH_PACK, GOT_PATH_PROG_INDEX_PACK, GOT_PATH_PROG_SEND_PACK, + /* GOT_PATH_PROG_HTTP explicitly excluded */ }; size_t i; blob - cfd4876a2dfa135816bb51fb862396c0cd6a4331 file + libexec/Makefile --- libexec/Makefile +++ libexec/Makefile @@ -1,6 +1,6 @@ SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \ got-read-tag got-fetch-pack got-index-pack got-read-pack \ got-read-gitconfig got-read-gotconfig got-send-pack \ - got-read-patch + got-read-patch got-http .include blob - 07e2042c17a6d66588622b9d6a7fb1e9c158cfbd file + libexec/got-fetch-pack/got-fetch-pack.c --- libexec/got-fetch-pack/got-fetch-pack.c +++ libexec/got-fetch-pack/got-fetch-pack.c @@ -541,6 +541,10 @@ fetch_pack(int fd, int packfd, uint8_t *pack_sha1, /* Server has not located our objects yet. */ continue; } + if (n > 1 && buf[0] == 0x2) { + /* XXX: sideband? */ + break; + } if (n < 4 + SHA1_DIGEST_STRING_LENGTH || strncmp(buf, "ACK ", 4) != 0) { err = got_error_msg(GOT_ERR_BAD_PACKET, blob - /dev/null file + libexec/got-http/Makefile (mode 644) --- /dev/null +++ libexec/got-http/Makefile @@ -0,0 +1,18 @@ +.PATH:${.CURDIR}/../../lib + +.include "../../got-version.mk" + +PROG= got-http +SRCS= got-http.c error.c inflate.c sha1.c pollfd.c + +CPPFLAGS= -I${.CURDIR}/../../include -I${.CURDIR}/../../lib + +.if defined(PROFILE) +LDADD= -lutil_p -lz_p -ltls_p +.else +LDADD= -lutil -lz -ltls +.endif + +DPADD= ${LIBZ} ${LIBUTIL} ${LIBTLS} + +.include blob - /dev/null file + libexec/got-http/got-http.c (mode 644) --- /dev/null +++ libexec/got-http/got-http.c @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_version.h" + +#define UPLOAD_PACK_ADV "application/x-git-upload-pack-advertisement" +#define UPLOAD_PACK_REQ "application/x-git-upload-pack-request" +#define UPLOAD_PACK_RES "application/x-git-upload-pack-result" + +#define HTTP_BUFSIZ 4096 +#define GOT_USERAGENT "got/" GOT_VERSION_STR +#define MINIMUM(a, b) ((a) < (b) ? (a) : (b)) +#define hasprfx(str, p) (strncasecmp(str, p, strlen(p)) == 0) + +#define DEBUG_HTTP 1 + +FILE *tmp; + +static int verbose; + +static long long +hexstrtonum(const char *str, long long min, long long max, const char **errstr) +{ + long long lval; + char *cp; + + errno = 0; + lval = strtoll(str, &cp, 16); + if (*str == '\0' || *cp != '\0') { + *errstr = "not a number"; + return 0; + } + if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) || + lval < min || lval > max) { + *errstr = "out of range"; + return 0; + } + + *errstr = NULL; + return lval; +} + +static int +stdio_tls_write(void *arg, const char *buf, int len) +{ + struct tls *ctx = arg; + ssize_t ret; + + do { + ret = tls_write(ctx, buf, len); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + if (ret == -1) + warn("tls_write: %s", tls_error(ctx)); + + return ret; +} + +static int +stdio_tls_read(void *arg, char *buf, int len) +{ + struct tls *ctx = arg; + ssize_t ret; + + do { + ret = tls_read(ctx, buf, len); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + if (ret == -1) + warn("tls_read: %s", tls_error(ctx)); + + return ret; +} + +static int +stdio_tls_close(void *arg) +{ + struct tls *ctx = arg; + int ret; + + do { + ret = tls_close(ctx); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + return ret; +} + +static FILE * +dial(int https, const char *host, const char *port) +{ + FILE *fp; + struct tls *ctx; + struct tls_config *conf; + struct addrinfo hints, *res, *res0; + int r, error, saved_errno, fd = -1; + const char *cause = NULL; + + if (https) { + if ((conf = tls_config_new()) == NULL) + errx(1, "failed to create TLS configuration"); + if ((ctx = tls_client()) == NULL) + errx(1, "failed to create TLS client"); + if (tls_configure(ctx, conf) == -1) + errx(1, "TLS configuration failure: %s", + tls_error(ctx)); + tls_config_free(conf); + + if (tls_connect(ctx, host, port) == -1) { + warnx("connect to %s:%s: %s", host, port, + tls_error(ctx)); + tls_close(ctx); + return NULL; + } + do { + r = tls_handshake(ctx); + } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); + fp = funopen(ctx, stdio_tls_read, stdio_tls_write, NULL, + stdio_tls_close); + if (fp == NULL) { + warn("funopen"); + tls_free(ctx); + } + return fp; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + if (error) { + warnx("%s", gai_strerror(error)); + return NULL; + } + + for (res = res0; res; res = res->ai_next) { + fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (fd == -1) { + cause = "socket"; + continue; + } + + if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) + break; + + cause = "connect"; + saved_errno = errno; + close(fd); + fd = -1; + errno = saved_errno; + } + freeaddrinfo(res0); + + if (fd == -1) { + warn("%s", cause); + return NULL; + } + + if ((fp = fdopen(fd, "r+")) == NULL) { + warn("fdopen"); + close(fd); + } + return fp; +} + +static FILE * +http_open(int https, const char *method, const char *host, const char *port, + const char *path, const char *path_sufx, const char *query, + const char *ctype) +{ + FILE *fp; + const char *chdr = NULL, *te = ""; + char *p, *req; + int r; + + if ((fp = dial(https, host, port)) == NULL) + return NULL; + + if (path_sufx != NULL && *path && path[strlen(path) - 1] == '/') + path_sufx++; /* skip the slash */ + + if (strcmp(method, "POST") == 0) + te = "\r\nTransfer-Encoding: chunked\r\n"; + + if (ctype) + chdr = "Content-Type: "; + + r = asprintf(&p, "%s/%s%s%s", path, path_sufx, + query ? "?" : "", query ? query : ""); + if (r == -1) + err(1, "asprintf"); + + r = asprintf(&req, "%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Connection: close\r\n" + "User-agent: %s\r\n" + "%s%s%s\r\n", + method, p, host, GOT_USERAGENT, + chdr ? chdr : "", ctype ? ctype : "", te); + free(p); + if (r == -1) + err(1, "asprintf"); + + if (verbose > 0) + fprintf(stderr, "%s: %s", getprogname(), req); + + if (fwrite(req, 1, r, fp) != r) { + free(req); + fclose(fp); + return NULL; + } + free(req); + + return fp; +} + +static int +http_parse_reply(FILE *fp, int *chunked, const char *expected_ctype) +{ + char *cp, *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + *chunked = 0; + + if ((linelen = getline(&line, &linesize, fp)) == -1) { + warn("%s: getline", __func__); + return -1; + } + + if ((cp = strchr(line, '\r')) == NULL) { + warnx("malformed HTTP response"); + goto err; + } + *cp = '\0'; + + if ((cp = strchr(line, ' ')) == NULL) { + warnx("malformed HTTP response"); + goto err; + } + cp++; + + if (strncmp(cp, "200 ", 4) != 0) { + warnx("malformed HTTP response"); + goto err; + } + + while ((linelen = getline(&line, &linesize, fp)) != -1) { + if (line[linelen-1] == '\n') + line[--linelen] = '\0'; + if (linelen > 0 && line[linelen-1] == '\r') + line[--linelen] = '\0'; + + if (*line == '\0') + break; + + if (hasprfx(line, "content-type:")) { + cp = strchr(line, ':') + 1; + cp += strspn(cp, " \t"); + cp[strcspn(cp, " \t")] = '\0'; + if (strcmp(cp, expected_ctype) != 0) { + warnx("server not using the \"smart\" " + "HTTP protocol."); + goto err; + } + } + + if (hasprfx(line, "transfer-encoding:")) { + cp = strchr(line, ':') + 1; + cp += strspn(cp, " \t"); + cp[strcspn(cp, " \t")] = '\0'; + if (strcmp(cp, "chunked") != 0) { + warnx("unknown transfer-encoding"); + goto err; + } + *chunked = 1; + } + } + + free(line); + return 0; + +err: + free(line); + return -1; +} + +static ssize_t +http_read(FILE *fp, int chunked, size_t *chunksz, void *buf, size_t bufsz) +{ + const char *errstr; + char *cp, *line = NULL; + size_t r, linesize = 0; + ssize_t ret = 0, linelen; + + if (!chunked) { + r = fread(buf, 1, bufsz, fp); + if (r == 0 && ferror(fp)) + return -1; +#if DEBUG_HTTP + fwrite(buf, 1, r, stderr); +#endif + return r; + } + + while (bufsz > 0) { + if (*chunksz == 0) { + again: + if ((linelen = getline(&line, &linesize, fp)) == -1) { + if (ferror(fp)) { + warn("%s: getline", __func__); + ret = -1; + } + break; + } + + if ((cp = strchr(line, '\r')) == NULL) { + warnx("invalid HTTP chunk: missing CR"); + ret = -1; + break; + } + *cp = '\0'; + + if (*line == '\0') + goto again; /* was the CRLF after the chunk */ + + *chunksz = hexstrtonum(line, 0, INT_MAX, &errstr); + if (errstr != NULL) { + warnx("invalid HTTP chunk: size is %s (%s)", + errstr, line); + ret = -1; + break; + } + + if (*chunksz == 0) + break; + } + + r = fread(buf, 1, MINIMUM(*chunksz, bufsz), fp); + if (r == 0) { + if (ferror(fp)) + ret = -1; + break; + } + +#if DEBUG_HTTP + if (tmp) + fwrite(buf, 1, r, tmp); + /* fwrite(buf, 1, r, stderr); */ +#endif + ret += r; + buf += r; + bufsz -= r; + *chunksz -= r; + } + + free(line); + return ret; +} + +static void +http_chunk(FILE *fp, const void *buf, size_t len) +{ + /* fprintf(stderr, "> %.*s", (int)len, (char *)buf); */ + + fprintf(fp, "%zx\r\n", len); + if (fwrite(buf, 1, len, fp) != len || + fwrite("\r\n", 1, 2, fp) != 2) + err(1, "%s fwrite", __func__); +} + +static int +get_refs(int https, const char *host, const char *port, const char *path) +{ + char buf[HTTP_BUFSIZ]; + const char *errstr, *sufx = "/info/refs"; + FILE *fp; + size_t skip, chunksz = 0; + ssize_t r; + int chunked; + + fp = http_open(https, "GET", host, port, path, sufx, + "service=git-upload-pack", NULL); + if (fp == NULL) + return -1; + + if (http_parse_reply(fp, &chunked, UPLOAD_PACK_ADV) == -1) { + fclose(fp); + return -1; + } + + /* skip first pack; why git over http is like this? */ + r = http_read(fp, chunked, &chunksz, buf, 4); + if (r <= 0) { + fclose(fp); + return -1; + } + buf[4] = '\0'; + skip = hexstrtonum(buf, 0, INT_MAX, &errstr); + if (errstr != NULL) { + warnx("pktlen is %s", errstr); + fclose(fp); + return -1; + } + + /* TODO: validate it's # service=git-upload-pack\n */ + while (skip > 0) { + r = http_read(fp, chunked, &chunksz, buf, + MINIMUM(skip, sizeof(buf))); + if (r <= 0) { + fclose(fp); + return -1; + } + + skip -= r; + } + + for (;;) { + r = http_read(fp, chunked, &chunksz, buf, sizeof(buf)); + if (r == -1) { + fclose(fp); + return -1; + } + + if (r == 0) + break; + + fwrite(buf, 1, r, stdout); + } + + fflush(stdout); + fclose(fp); + return 0; +} + +static int +upload_request(int https, const char *host, const char *port, const char *path, + FILE *in) +{ + const char *errstr; + char buf[HTTP_BUFSIZ]; + FILE *fp; + ssize_t r; + size_t chunksz = 0; + long long t; + int chunked; + + fp = http_open(https, "POST", host, port, path, "/git-upload-pack", + NULL, UPLOAD_PACK_REQ); + if (fp == NULL) + return -1; + + for (;;) { + r = fread(buf, 1, 4, in); + if (r != 4) + goto err; + + buf[4] = '\0'; + t = hexstrtonum(buf, 0, sizeof(buf), &errstr); + if (errstr != NULL) { + warnx("pktline len is %s", errstr); + goto err; + } + + /* no idea why 0000 is not enough. */ + if (t == 0) { + const char *x = "00000009done\n"; + http_chunk(fp, x, strlen(x)); + http_chunk(fp, NULL, 0); + break; + } + + if (t < 6) { + warnx("pktline len is too small"); + goto err; + } + + r = fread(buf + 4, 1, t - 4, in); + if (r != t - 4) + goto err; + + http_chunk(fp, buf, t); + } + + if (http_parse_reply(fp, &chunked, UPLOAD_PACK_RES) == -1) + goto err; + + for (;;) { + r = http_read(fp, chunked, &chunksz, buf, sizeof(buf)); + if (r == -1) { + fclose(fp); + return -1; + } + + if (r == 0) + break; + + fwrite(buf, 1, r, stdout); + } + + fclose(fp); + return 0; + +err: + fclose(fp); + return -1; +} + +static __dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-qv] proto host port path\n", + getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct pollfd pfd; + const char *host, *port, *path; + int https = 0; + int ch; +#if 0 + static int attached; + while (!attached) + sleep(1); +#endif + +#if !DEBUG_HTTP || defined(PROFILE) + if (pledge("stdio inet dns", NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "qv")) != -1) { + switch (ch) { + case 'q': + verbose = -1; + break; + case 'v': + verbose++; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 4) + usage(); + + https = strcmp(argv[0], "https") == 0; + + host = argv[1]; + port = argv[2]; + path = argv[3]; + + if (get_refs(https, host, port, path) == -1) + errx(1, "failed to get refs"); + +#if DEBUG_HTTP + tmp = fopen("/tmp/pck", "w"); +#endif + + pfd.fd = 0; + pfd.events = POLLIN; + if (poll(&pfd, 1, INFTIM) == -1) + err(1, "poll"); + + if ((ch = fgetc(stdin)) == EOF) + return 0; + + ungetc(ch, stdin); + if (upload_request(https, host, port, path, stdin) == -1) { + fflush(tmp); + errx(1, "failed to upload request"); + } + + return 0; +} blob - 45aaadb8bcd9513fe5075ceaa0fc416d03874885 file + libexec/got-read-gotconfig/got-read-gotconfig.c --- libexec/got-read-gotconfig/got-read-gotconfig.c +++ libexec/got-read-gotconfig/got-read-gotconfig.c @@ -383,7 +383,11 @@ validate_protocol(const char *protocol, const char *re if (strcmp(protocol, "ssh") != 0 && strcmp(protocol, "git+ssh") != 0 && - strcmp(protocol, "git") != 0) { + strcmp(protocol, "git") != 0 && + strcmp(protocol, "git+http") != 0 && + strcmp(protocol, "http") != 0 && + strcmp(protocol, "https") != 0 && + strcmp(protocol, "git+https") != 0) { snprintf(msg, sizeof(msg),"unknown protocol \"%s\" " "for remote repository \"%s\"", protocol, repo_name); return got_error_msg(GOT_ERR_PARSE_CONFIG, msg);