shithub: git9

Download patch

ref: 77f14aa86695e59554eb4698436ce1b76b93bdd2
parent: 22383ae54cfeb178b150dc9417b0d773cc72c434
author: Ori Bernstein <[email protected]>
date: Mon Feb 10 00:10:49 EST 2020

add http support

--- a/fetch.c
+++ b/fetch.c
@@ -136,12 +136,12 @@
 }
 
 int
-fetchpack(int fd, int pfd, char *packtmp)
+fetchpack(Conn *c, int pfd, char *packtmp)
 {
 	char buf[Pktmax], idxtmp[256], *sp[3];
 	Hash h, *have, *want;
 	int nref, refsz;
-	int i, n, req;
+	int i, j, n, req;
 	vlong packsz;
 	Object *o;
 
@@ -150,7 +150,7 @@
 	have = emalloc(refsz * sizeof(have[0]));
 	want = emalloc(refsz * sizeof(want[0]));
 	while(1){
-		n = readpkt(fd, buf, sizeof(buf));
+		n = readpkt(c, buf, sizeof(buf));
 		if(n == -1)
 			return -1;
 		if(n == 0)
@@ -175,6 +175,8 @@
 		nref++;
 	}
 
+	if(writephase(c) == -1)
+		sysfatal("write: %r");
 	req = 0;
 	for(i = 0; i < nref; i++){
 		if(hasheq(&have[i], &want[i]))
@@ -183,42 +185,44 @@
 			unref(o);
 			continue;
 		}
-		n = snprint(buf, sizeof(buf), "want %H", want[i]);
-		if(writepkt(fd, buf, n) == -1)
+		n = snprint(buf, sizeof(buf), "want %H\n", want[i]);
+		if(writepkt(c, buf, n) == -1)
 			sysfatal("could not send want for %H", want[i]);
-		req = 1; 
+		req = 1;
 	}
-	flushpkt(fd);
+	flushpkt(c);
 	for(i = 0; i < nref; i++){
 		if(hasheq(&have[i], &Zhash))
 			continue;
 		n = snprint(buf, sizeof(buf), "have %H\n", have[i]);
-		if(writepkt(fd, buf, n + 1) == -1)
+		if(writepkt(c, buf, n + 1) == -1)
 			sysfatal("could not send have for %H", have[i]);
 	}
-	if(!req){
-		flushpkt(fd);
-	}
+	if(!req)
+		flushpkt(c);
+
 	n = snprint(buf, sizeof(buf), "done\n");
-	if(writepkt(fd, buf, n) == -1)
-		sysfatal("lost connection write");
+	if(writepkt(c, buf, n) == -1)
+		sysfatal("write: %r");
 	if(!req)
 		return 0;
-
-	if((n = readpkt(fd, buf, sizeof(buf))) == -1)
-		sysfatal("lost connection read");
+	if(readphase(c) == -1)
+		sysfatal("read: %r");
+	if((n = readpkt(c, buf, sizeof(buf))) == -1)
+		sysfatal("read: %r");
 	buf[n] = 0;
 
 	fprint(2, "fetching...\n");
 	packsz = 0;
 	while(1){
-		n = readn(fd, buf, sizeof buf);
+		n = readn(c->rfd, buf, sizeof buf);
 		if(n == 0)
 			break;
 		if(n == -1 || write(pfd, buf, n) != n)
-			sysfatal("could not fetch packfile: %r");
+			sysfatal("fetch packfile: %r");
 		packsz += n;
 	}
+	closeconn(c);
 	if(seek(pfd, 0, 0) == -1)
 		sysfatal("packfile seek: %r");
 	if(checkhash(pfd, packsz, &h) == -1)
@@ -248,7 +252,8 @@
 {
 	char proto[Nproto], host[Nhost], port[Nport];
 	char repo[Nrepo], path[Npath];
-	int fd, pfd;
+	int r, pfd;
+	Conn c;
 
 	ARGBEGIN{
 	case 'b':	fetchbranch=EARGF(usage());	break;
@@ -260,8 +265,8 @@
 	gitinit();
 	if(argc != 1)
 		usage();
-	fd = -1;
 
+	r = -1;
 	if(mkoutpath(packtmp) == -1)
 		sysfatal("could not create %s: %r", packtmp);
 	if((pfd = create(packtmp, ORDWR, 0644)) == -1)
@@ -270,17 +275,18 @@
 	if(parseuri(argv[0], proto, host, port, path, repo) == -1)
 		sysfatal("bad uri %s", argv[0]);
 	if(strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0)
-		fd = dialssh(host, port, path, "upload");
+		r = dialssh(&c, host, port, path, "upload");
 	else if(strcmp(proto, "git") == 0)
-		fd = dialgit(host, port, path, "upload");
-	else if(strcmp(proto, "http") == 0 || strcmp(proto, "git+http") == 0)
-		sysfatal("http clone not implemented");
+		r = dialgit(&c, host, port, path, "upload");
+	else if(strcmp(proto, "http") == 0 || strcmp(proto, "https") == 0)
+		r = dialhttp(&c, host, port, path, "upload");
 	else
 		sysfatal("unknown protocol %s", proto);
 	
-	if(fd == -1)
+	if(r == -1)
 		sysfatal("could not dial %s:%s: %r", proto, host);
-	if(fetchpack(fd, pfd, packtmp) == -1)
+	if(fetchpack(&c, pfd, packtmp) == -1)
 		sysfatal("fetch failed: %r");
+	closeconn(&c);
 	exits(nil);
 }
--- a/git.h
+++ b/git.h
@@ -4,6 +4,7 @@
 #include <flate.h>
 #include <regexp.h>
 
+typedef struct Conn	Conn;
 typedef struct Hash	Hash;
 typedef struct Cinfo	Cinfo;
 typedef struct Tinfo	Tinfo;
@@ -30,7 +31,7 @@
 	Nbranch	= 32,
 };
 
-typedef enum {
+enum {
 	GNone	= 0,
 	GCommit	= 1,
 	GTree	= 2,
@@ -48,6 +49,12 @@
 	Cparsed	= 1 << 5,
 };
 
+enum {
+	ConnRaw,
+	ConnSsh,
+	ConnHttp,
+};
+
 struct Objlist {
 	int idx;
 
@@ -72,6 +79,18 @@
 	uchar h[20];
 };
 
+struct Conn {
+	int type;
+	int rfd;
+	int wfd;
+
+	/* only used by http */
+	int cfd;
+	char *url;	/* note, first GET uses a different url */
+	char *dir;
+	char *direction;
+};
+
 struct Dirent {
 	char *name;
 	int mode;
@@ -234,9 +253,13 @@
 void	die(char *, ...);
 
 /* proto handling */
-int	readpkt(int, char*, int);
-int	writepkt(int, char*, int);
-int	flushpkt(int);
+int	readpkt(Conn*, char*, int);
+int	writepkt(Conn*, char*, int);
+int	flushpkt(Conn*);
 int	parseuri(char *, char *, char *, char *, char *, char *);
-int	dialssh(char *, char *, char *, char *);
-int	dialgit(char *, char *, char *, char *);
+int	dialssh(Conn*, char *, char *, char *, char *);
+int	dialgit(Conn*, char *, char *, char *, char *);
+int	dialhttp(Conn*, char *, char *, char *, char *);
+int	readphase(Conn *);
+int	writephase(Conn *);
+void	closeconn(Conn *);
--- a/proto.c
+++ b/proto.c
@@ -3,16 +3,20 @@
 
 #include "git.h"
 
+#define Useragent	"useragent git/2.24.1"
+#define Contenthdr	"headers Content-Type: application/x-git-%s-pack-request"
+#define Accepthdr	"headers Accept: application/x-git-%s-pack-result"
+
 int chattygit;
 
 int
-readpkt(int fd, char *buf, int nbuf)
+readpkt(Conn *c, char *buf, int nbuf)
 {
 	char len[5];
 	char *e;
 	int n;
 
-	if(readn(fd, len, 4) == -1)
+	if(readn(c->rfd, len, 4) == -1)
 		return -1;
 	len[4] = 0;
 	n = strtol(len, &e, 16);
@@ -26,7 +30,7 @@
 	n  -= 4;
 	if(n >= nbuf)
 		sysfatal("buffer too small");
-	if(readn(fd, buf, n) != n)
+	if(readn(c->rfd, buf, n) != n)
 		return -1;
 	buf[n] = 0;
 	if(chattygit)
@@ -35,15 +39,15 @@
 }
 
 int
-writepkt(int fd, char *buf, int nbuf)
+writepkt(Conn *c, char *buf, int nbuf)
 {
 	char len[5];
 
 
 	snprint(len, sizeof(len), "%04x", nbuf + 4);
-	if(write(fd, len, 4) != 4)
+	if(write(c->wfd, len, 4) != 4)
 		return -1;
-	if(write(fd, buf, nbuf) != nbuf)
+	if(write(c->wfd, buf, nbuf) != nbuf)
 		return -1;
 	if(chattygit){
 		fprint(2, "writepkt: %s:\t", len);
@@ -54,11 +58,11 @@
 }
 
 int
-flushpkt(int fd)
+flushpkt(Conn *c)
 {
 	if(chattygit)
 		fprint(2, "writepkt: 0000\n");
-	return write(fd, "0000", 4);
+	return write(c->wfd, "0000", 4);
 }
 
 static void
@@ -85,8 +89,20 @@
 		werrstr("missing protocol");
 		return -1;
 	}
-	grab(proto, Nproto, uri, p);
-	hasport = (strcmp(proto, "git") == 0 || strstr(proto, "http") == proto);
+	if(strncmp(uri, "git+", 4) == 0)
+		grab(proto, Nproto, uri + 4, p);
+	else
+		grab(proto, Nproto, uri, p);
+	*port = 0;
+	hasport = 1;
+	if(strcmp(proto, "git") == 0)
+		snprint(port, Nport, "9418");
+	else if(strncmp(proto, "https", 5) == 0)
+		snprint(port, Nport, "443");
+	else if(strncmp(proto, "http", 4) == 0)
+		snprint(port, Nport, "80");
+	else
+		hasport = 0;
 	s = p + 3;
 	p = nil;
 	if(!hasport){
@@ -107,7 +123,6 @@
 		grab(port, Nport, q + 1, p);
 	}else{
 		grab(host, Nhost, s, p);
-		snprint(port, Nport, "9418");
 	}
 	
 	snprint(path, Npath, "%s", p);
@@ -124,8 +139,100 @@
 }
 
 int
-dialssh(char *host, char *, char *path, char *direction)
+webclone(Conn *c, char *url)
 {
+	char buf[16];
+	int n, conn;
+
+	if((c->cfd = open("/mnt/web/clone", ORDWR)) < 0)
+		goto err;
+	if((n = read(c->cfd, buf, sizeof(buf)-1)) == -1)
+		goto err;
+	buf[n] = 0;
+	conn = atoi(buf);
+
+	/* github will behave differently based on useragent */
+	if(write(c->cfd, Useragent, sizeof(Useragent)) == -1)
+		return -1;
+	if(chattygit)
+		fprint(2, "open url %s\n", url);
+	if(fprint(c->cfd, "url %s", url) == -1)
+		goto err;
+	free(c->dir);
+	c->dir = smprint("/mnt/web/%d", conn);
+	return 0;
+err:
+	if(c->cfd != -1)
+		close(c->cfd);
+	return -1;
+}
+
+int
+webopen(Conn *c, char *file, int mode)
+{
+	char path[128];
+	int fd;
+
+	snprint(path, sizeof(path), "%s/%s", c->dir, file);
+	if((fd = open(path, mode)) == -1)
+		return -1;
+	return fd;
+}
+
+int
+issmarthttp(Conn *c, char *direction)
+{
+	char buf[Pktmax+1], svc[128];
+	int n;
+
+	if((n = readpkt(c, buf, sizeof(buf))) == -1)
+		sysfatal("http read: %r");
+	buf[n] = 0;
+	snprint(svc, sizeof(svc), "# service=git-%s-pack\n", direction);
+	if(strncmp(svc, buf, n) != 0){
+		werrstr("dumb http protocol not supported");
+		return -1;
+	}
+	if(readpkt(c, buf, sizeof(buf)) != 0){
+		werrstr("protocol garble: expected flushpkt");
+		return -1;
+	}
+	return 0;
+}
+
+int
+dialhttp(Conn *c, char *host, char *port, char *path, char *direction)
+{
+	char *geturl, *suff, *hsep, *psep;
+
+	suff = "";
+	hsep = "";
+	psep = "";
+	if(port && strlen(port) != 0)
+		hsep = ":";
+	if(path && path[0] != '/')
+		psep = "/";
+	memset(c, 0, sizeof(*c));
+	geturl = smprint("https://%s%s%s%s%s%s/info/refs?service=git-%s-pack", host, hsep, port, psep, path, suff, direction);
+	c->type = ConnHttp;
+	c->url = smprint("https://%s%s%s%s%s%s/git-%s-pack", host, hsep, port, psep, path, suff, direction);
+	c->cfd = webclone(c, geturl);
+	free(geturl);
+	if(c->cfd == -1)
+		return -1;
+	c->rfd = webopen(c, "body", OREAD);
+	c->wfd = -1;
+	if(c->rfd == -1)
+		return -1;
+	if(issmarthttp(c, direction) == -1)
+		return -1;
+	c->direction = estrdup(direction);
+	return 0;
+}
+
+int
+dialssh(Conn *c, char *host, char *, char *path, char *direction)
+{
 	int pid, pfd[2];
 	char cmd[64];
 
@@ -144,31 +251,99 @@
 		execl("/bin/ssh", "ssh", host, cmd, path, nil);
 	}else{
 		close(pfd[0]);
-		return pfd[1];
+		c->type = ConnSsh;
+		c->rfd = pfd[1];
+		c->wfd = dup(pfd[1], -1);
+		return 0;
 	}
 	return -1;
 }
 
 int
-dialgit(char *host, char *port, char *path, char *direction)
+dialgit(Conn *c, char *host, char *port, char *path, char *direction)
 {
 	char *ds, *p, *e, cmd[512];
 	int fd;
 
 	ds = netmkaddr(host, "tcp", port);
+	if(chattygit)
+		fprint(2, "dial %s git-%s-pack %s\n", ds, direction, path);
 	fd = dial(ds, nil, nil, nil);
 	if(fd == -1)
 		return -1;
-	if(chattygit)
-		fprint(2, "dial %s %s git-%s-pack %s\n", host, port, direction, path);
 	p = cmd;
 	e = cmd + sizeof(cmd);
 	p = seprint(p, e - 1, "git-%s-pack %s", direction, path);
 	p = seprint(p + 1, e, "host=%s", host);
-	if(writepkt(fd, cmd, p - cmd + 1) == -1){
-		print("failed to write message\n");
+	c->type = ConnRaw;
+	c->rfd = fd;
+	c->wfd = dup(fd, -1);
+	if(writepkt(c, cmd, p - cmd + 1) == -1){
+		fprint(2, "failed to write message\n");
 		close(fd);
 		return -1;
 	}
-	return fd;
+	return 0;
+}
+
+int
+writephase(Conn *c)
+{
+	char hdr[128];
+	int n;
+
+	if(chattygit)
+		fprint(2, "start write phase\n");
+	if(c->type != ConnHttp)
+		return 0;
+
+	if(c->wfd != -1)
+		close(c->wfd);
+	if(c->cfd != -1)
+		close(c->cfd);
+	if((c->cfd = webclone(c, c->url)) == -1)
+		return -1;
+	n = snprint(hdr, sizeof(hdr), Contenthdr, c->direction);
+	if(write(c->cfd, hdr, n) == -1)
+		return -1;
+	n = snprint(hdr, sizeof(hdr), Accepthdr, c->direction);
+	if(write(c->cfd, hdr, n) == -1)
+		return -1;
+	if((c->wfd = webopen(c, "postbody", OWRITE)) == -1)
+		return -1;
+	c->rfd = -1;
+	return 0;
+}
+
+int
+readphase(Conn *c)
+{
+	if(chattygit)
+		fprint(2, "start read phase\n");
+	if(c->type != ConnHttp)
+		return 0;
+	if(close(c->wfd) == -1)
+		return -1;
+	if((c->rfd = webopen(c, "body", OREAD)) == -1)
+		return -1;
+	c->wfd = -1;
+	return 0;
+}
+
+void
+closeconn(Conn *c)
+{
+	close(c->rfd);
+	close(c->wfd);
+	switch(c->type){
+	case ConnRaw:
+
+		break;
+	case ConnSsh:
+		free(wait());
+		break;
+	case ConnHttp:
+		close(c->cfd);
+		break;
+	}
 }
--- a/send.c
+++ b/send.c
@@ -152,7 +152,7 @@
 }
 
 int
-writepack(int fd, Update *upd, int nupd)
+writepack(Conn *c, Update *upd, int nupd)
 {
 	Objset send, skip;
 	Object *o, *p;
@@ -216,18 +216,18 @@
 
 	st = nil;
 	PUTBE32(buf, send.nobj);
-	if(hwrite(fd, "PACK\0\0\0\02", 8, &st) != 8)
+	if(hwrite(c->wfd, "PACK\0\0\0\02", 8, &st) != 8)
 		return -1;
-	if(hwrite(fd, buf, 4, &st) == -1)
+	if(hwrite(c->wfd, buf, 4, &st) == -1)
 		return -1;
 	for(i = 0; i < send.sz; i++){
 		if(!send.obj[i])
 			continue;
-		if(writeobject(fd, send.obj[i], &st) == -1)
+		if(writeobject(c->wfd, send.obj[i], &st) == -1)
 			return -1;
 	}
 	sha1(nil, 0, h.h, st);
-	if(write(fd, h.h, sizeof(h.h)) == -1)
+	if(write(c->wfd, h.h, sizeof(h.h)) == -1)
 		return -1;
 	return 0;
 }
@@ -279,7 +279,7 @@
 }
 
 int
-sendpack(int fd)
+sendpack(Conn *c)
 {
 	int i, n, r, nupd, nsp, send;
 	char buf[Pktmax], *sp[3];
@@ -288,7 +288,7 @@
 
 	nupd = readours(&upd);
 	while(1){
-		n = readpkt(fd, buf, sizeof(buf));
+		n = readpkt(c, buf, sizeof(buf));
 		if(n == -1)
 			return -1;
 		if(n == 0)
@@ -305,6 +305,8 @@
 		snprint(u->ref, sizeof(u->ref), sp[1]);
 	}
 
+	if(writephase(c) == -1)
+		return -1;
 	r = 0;
 	send = 0;
 	for(i = 0; i < nupd; i++){
@@ -343,7 +345,7 @@
 			buf[n++] = '\0';
 			n += snprint(buf + n, sizeof(buf) - n, " report-status");
 		}
-		if(writepkt(fd, buf, n) == -1)
+		if(writepkt(c, buf, n) == -1)
 			sysfatal("unable to send update pkt");
 		/*
 		 * If we're rolling back with a force push, the other side already
@@ -352,17 +354,19 @@
 		if(a == nil || b == nil || ancestor(b, a) != b)
 			send = 1;
 	}
-	flushpkt(fd);
+	flushpkt(c);
 	if(!send)
 		print("nothing to send\n");
 	if(send){
 		if(chattygit)
 			fprint(2, "sending pack...\n");
-		if(writepack(fd, upd, nupd) == -1)
+		if(writepack(c, upd, nupd) == -1)
 			return -1;
 
+		if(readphase(c) == -1)
+			return -1;
 		/* We asked for a status report, may as well use it. */
-		while((n = readpkt(fd, buf, sizeof(buf))) > 0){
+		while((n = readpkt(c, buf, sizeof(buf))) > 0){
  			buf[n] = 0;
 			if(chattygit)
 				fprint(2, "done sending pack, status %s\n", buf);
@@ -400,20 +404,27 @@
 	char proto[Nproto], host[Nhost], port[Nport];
 	char repo[Nrepo], path[Npath];
 	char *br;
-	int fd;
+	Conn c;
+	int r;
 
 	ARGBEGIN{
 	default:
-		usage();	break;
+		usage();
+		break;
 	case 'd':
-		chattygit++;	break;
+		chattygit++;
+		break;
 	case 'f':
-		force++;	break;
+		force++;
+		break;
 	case 'r':
 		if(nremoved == nelem(removed))
 			sysfatal("too many deleted branches");
 		removed[nremoved++] = EARGF(usage());
 		break;
+	case 'a':
+		sendall++;
+		break;
 	case 'b':
 		br = EARGF(usage());
 		if(strncmp(br, "refs/heads/", strlen("refs/heads/")) == 0)
@@ -431,21 +442,23 @@
 	gitinit();
 	if(argc != 1)
 		usage();
-	fd = -1;
+	r = -1;
 	if(parseuri(argv[0], proto, host, port, path, repo) == -1)
 		sysfatal("bad uri %s", argv[0]);
-	if(strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0)
-		fd = dialssh(host, port, path, "receive");
+
+	if(strcmp(proto, "ssh") == 0)
+		r = dialssh(&c, host, port, path, "receive");
 	else if(strcmp(proto, "git") == 0)
-		fd = dialgit(host, port, path, "receive");
-	else if(strcmp(proto, "http") == 0 || strcmp(proto, "git+http") == 0)
-		sysfatal("http clone not implemented");
+		r = dialgit(&c, host, port, path, "receive");
+	else if(strcmp(proto, "http") == 0 || strcmp(proto, "https") == 0)
+		r = dialhttp(&c, host, port, path, "receive");
 	else
 		sysfatal("unknown protocol %s", proto);
 	
-	if(fd == -1)
+	if(r == -1)
 		sysfatal("could not dial %s:%s: %r", proto, host);
-	if(sendpack(fd) == -1)
+	if(sendpack(&c) == -1)
 		sysfatal("send failed: %r");
+	closeconn(&c);
 	exits(nil);
 }