commit 37e7d69e10aa17ac243e96c3205d766f02cf58a0 from: Omar Polo via: Thomas Adam date: Thu Apr 25 14:57:08 2024 UTC Add initial read-only http fetch support using a got-http helper. Currently we only support the smart protocol with a limited feature set. ok stsp@ tobhe@ commit - a6955b87407110ed6d51627190bbb4a514e885d1 commit + 37e7d69e10aa17ac243e96c3205d766f02cf58a0 blob - 548c0d44870a40bd593d7d989fd77521956d5680 blob + 92a8f05c92c261ded6044f2c5d6917dfdfe2a38e --- got/got.c +++ got/got.c @@ -1645,16 +1645,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; @@ -2527,16 +2527,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 - 5614664a97583d54ff1a82c6e278319cacf69d87 blob + 1ace60a92613fb58d2cb22c42741b851da1a8a85 --- lib/dial.c +++ lib/dial.c @@ -20,22 +20,31 @@ #include #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_compat.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 @@ -68,6 +77,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; } @@ -369,6 +385,62 @@ done: } else *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; + } } const struct got_error * blob - b677fb09af65bc9d83f1689fa013d968f88a89a6 blob + 0d3029c6dd549f7fa1721f81c03ecab2e2ad6457 --- lib/fetch.c +++ lib/fetch.c @@ -87,8 +87,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_CMD_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 - 4717b9365391333147b3bf4e949e34865e8da431 blob + 63de6cd1cf810d258095343358365fa993a0aa82 --- lib/got_lib_dial.h +++ lib/got_lib_dial.h @@ -25,5 +25,8 @@ const struct got_error *got_dial_ssh(pid_t *newpid, in const char *host, const char *port, const char *path, const char *command, 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); + const struct got_error *got_dial_parse_command(char **command, char **repo_path, const char *gitcmd); blob - e585687a74d6708a955fac1e86ce92bed195007c blob + 4fc76d28f9e074d3040aa5e7827b3865da7a3432 --- 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 - cfe6f82306dd349e977c779c4a3e58d9b231924c blob + f028559958958fe83aa010219104474331e82d36 --- lib/privsep.c +++ lib/privsep.c @@ -3559,6 +3559,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 - a76d4ffce3d5b5ebcf828a8dc15495bedb7d3b44 blob + 3a8ecdee63ef62b527396f4871529a4bfd568b5f --- libexec/got-fetch-pack/got-fetch-pack.c +++ libexec/got-fetch-pack/got-fetch-pack.c @@ -608,6 +608,10 @@ fetch_pack(int fd, int packfd, uint8_t *pack_sha1, * Perhaps it is an out-of-date mirror, or there * is a repository with unrelated history. */ + break; + } + if (n > 1 && buf[0] == 0x2) { + /* XXX: sideband? */ break; } if (n < 4 + SHA1_DIGEST_STRING_LENGTH || blob - /dev/null blob + ffeb9c647a7d2e1cd1f496e4f748206961a665e4 (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 hash.c error.c inflate.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 blob + 4ef081a3cb94b0823e86ad6cb1142cabd0226c3b (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 - 4a11620d84aa87c18fc5333e6c9cd1930310b2ab blob + f8d740bd2252175db9e6556d84516c93a5f3b684 --- libexec/got-read-gotconfig/got-read-gotconfig.c +++ libexec/got-read-gotconfig/got-read-gotconfig.c @@ -382,7 +382,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);