8bd5be7c70
If the server only supports the dumb protocol, the first 4 bytes of response will be the initial part of the hash of the first ref. The http-protocol documentation says that we should fall back to the dumb protocol when we don't see a content-type of application/x-$servicename-advertisement. Check this before attempting to read a smart git packet.
515 lines
10 KiB
C
515 lines
10 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <ctype.h>
|
|
|
|
#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"
|
|
|
|
enum {
|
|
Nproto = 16,
|
|
Nport = 16,
|
|
Nhost = 256,
|
|
Npath = 128,
|
|
Nrepo = 64,
|
|
Nbranch = 32,
|
|
};
|
|
|
|
void
|
|
tracepkt(int v, char *pfx, char *b, int n)
|
|
{
|
|
char *f;
|
|
int o, i;
|
|
|
|
if(chattygit < v)
|
|
return;
|
|
o = 0;
|
|
f = emalloc(n*4 + 1);
|
|
for(i = 0; i < n; i++){
|
|
if(isprint(b[i])){
|
|
f[o++] = b[i];
|
|
continue;
|
|
}
|
|
f[o++] = '\\';
|
|
switch(b[i]){
|
|
case '\\': f[o++] = '\\'; break;
|
|
case '\n': f[o++] = 'n'; break;
|
|
case '\r': f[o++] = 'r'; break;
|
|
case '\v': f[o++] = 'v'; break;
|
|
case '\0': f[o++] = '0'; break;
|
|
default:
|
|
f[o++] = 'x';
|
|
f[o++] = "0123456789abcdef"[(b[i]>>4)&0xf];
|
|
f[o++] = "0123456789abcdef"[(b[i]>>0)&0xf];
|
|
break;
|
|
}
|
|
}
|
|
f[o] = '\0';
|
|
fprint(2, "%s %04x:\t%s\n", pfx, n, f);
|
|
free(f);
|
|
}
|
|
|
|
int
|
|
readpkt(Conn *c, char *buf, int nbuf)
|
|
{
|
|
char len[5];
|
|
char *e;
|
|
int n;
|
|
|
|
if(readn(c->rfd, len, 4) == -1)
|
|
return -1;
|
|
len[4] = 0;
|
|
n = strtol(len, &e, 16);
|
|
if(n == 0){
|
|
dprint(1, "=r=> 0000\n");
|
|
return 0;
|
|
}
|
|
if(e != len + 4 || n <= 4)
|
|
sysfatal("pktline: bad length '%s'", len);
|
|
n -= 4;
|
|
if(n >= nbuf)
|
|
sysfatal("pktline: undersize buffer");
|
|
if(readn(c->rfd, buf, n) != n)
|
|
return -1;
|
|
buf[n] = 0;
|
|
tracepkt(1, "=r=>", buf, n);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
writepkt(Conn *c, char *buf, int nbuf)
|
|
{
|
|
char len[5];
|
|
|
|
|
|
snprint(len, sizeof(len), "%04x", nbuf + 4);
|
|
if(write(c->wfd, len, 4) != 4)
|
|
return -1;
|
|
if(write(c->wfd, buf, nbuf) != nbuf)
|
|
return -1;
|
|
tracepkt(1, "<=w=", buf, nbuf);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
flushpkt(Conn *c)
|
|
{
|
|
dprint(1, "<=w= 0000\n");
|
|
return write(c->wfd, "0000", 4);
|
|
}
|
|
|
|
static void
|
|
grab(char *dst, int n, char *p, char *e)
|
|
{
|
|
int l;
|
|
|
|
l = e - p;
|
|
if(l >= n)
|
|
sysfatal("overlong component");
|
|
memcpy(dst, p, l);
|
|
dst[l] = 0;
|
|
}
|
|
|
|
static int
|
|
parseuri(char *uri, char *proto, char *host, char *port, char *path, char *repo)
|
|
{
|
|
char *s, *p, *q;
|
|
int n, hasport;
|
|
print("uri: \"%s\"\n", uri);
|
|
|
|
p = strstr(uri, "://");
|
|
if(p == nil)
|
|
snprint(proto, Nproto, "ssh");
|
|
else 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 if(strncmp(proto, "hjgit", 5) == 0)
|
|
snprint(port, Nport, "17021");
|
|
else if(strncmp(proto, "gits", 5) == 0)
|
|
snprint(port, Nport, "9419");
|
|
else
|
|
hasport = 0;
|
|
s = (p != nil) ? p + 3 : uri;
|
|
p = nil;
|
|
if(!hasport){
|
|
p = strstr(s, ":");
|
|
if(p != nil)
|
|
p++;
|
|
}
|
|
if(p == nil)
|
|
p = strstr(s, "/");
|
|
if(p == nil || strlen(p) == 1){
|
|
werrstr("missing path");
|
|
return -1;
|
|
}
|
|
|
|
q = memchr(s, ':', p - s);
|
|
if(q){
|
|
grab(host, Nhost, s, q);
|
|
grab(port, Nport, q + 1, p);
|
|
}else{
|
|
grab(host, Nhost, s, p);
|
|
}
|
|
|
|
snprint(path, Npath, "%s", p);
|
|
if((q = strrchr(p, '/')) != nil)
|
|
p = q + 1;
|
|
if(strlen(p) == 0){
|
|
werrstr("missing repository in uri");
|
|
return -1;
|
|
}
|
|
n = strlen(p);
|
|
if(hassuffix(p, ".git"))
|
|
n -= 4;
|
|
grab(repo, Nrepo, p, p + n);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
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;
|
|
dprint(1, "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;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int
|
|
issmarthttp(Conn *c, char *direction)
|
|
{
|
|
char buf[Pktmax+1], svc[128];
|
|
int fd, n;
|
|
|
|
if((fd = webopen(c, "contenttype", OREAD)) == -1)
|
|
return -1;
|
|
n = readn(fd, buf, sizeof(buf) - 1);
|
|
close(fd);
|
|
if(n == -1)
|
|
return -1;
|
|
buf[n] = '\0';
|
|
snprint(svc, sizeof(svc), "application/x-git-%s-pack-advertisement", direction);
|
|
if(strcmp(svc, buf) != 0){
|
|
werrstr("dumb http protocol not supported");
|
|
return -1;
|
|
}
|
|
|
|
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("invalid initial packet line");
|
|
return -1;
|
|
}
|
|
if(readpkt(c, buf, sizeof(buf)) != 0){
|
|
werrstr("protocol garble: expected flushpkt");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int
|
|
dialssh(Conn *c, char *host, char *, char *path, char *direction)
|
|
{
|
|
int pid, pfd[2];
|
|
char cmd[64];
|
|
|
|
if(pipe(pfd) == -1)
|
|
sysfatal("unable to open pipe: %r");
|
|
pid = fork();
|
|
if(pid == -1)
|
|
sysfatal("unable to fork");
|
|
if(pid == 0){
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(pfd[0], 1);
|
|
snprint(cmd, sizeof(cmd), "git-%s-pack", direction);
|
|
dprint(1, "exec ssh '%s' '%s' %s\n", host, cmd, path);
|
|
execl("/bin/ssh", "ssh", host, cmd, path, nil);
|
|
sysfatal("exec: %r");
|
|
}
|
|
close(pfd[0]);
|
|
c->type = ConnSsh;
|
|
c->rfd = pfd[1];
|
|
c->wfd = dup(pfd[1], -1);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
githandshake(Conn *c, char *host, char *path, char *direction)
|
|
{
|
|
char *p, *e, cmd[512];
|
|
|
|
p = cmd;
|
|
e = cmd + sizeof(cmd);
|
|
p = seprint(p, e - 1, "git-%s-pack %s", direction, path);
|
|
if(host != nil)
|
|
p = seprint(p + 1, e, "host=%s", host);
|
|
if(writepkt(c, cmd, p - cmd + 1) == -1){
|
|
fprint(2, "failed to write message\n");
|
|
closeconn(c);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dialhjgit(Conn *c, char *host, char *port, char *path, char *direction, int auth)
|
|
{
|
|
char *ds;
|
|
int pid, pfd[2];
|
|
|
|
if((ds = netmkaddr(host, "tcp", port)) == nil)
|
|
return -1;
|
|
if(pipe(pfd) == -1)
|
|
sysfatal("unable to open pipe: %r");
|
|
pid = fork();
|
|
if(pid == -1)
|
|
sysfatal("unable to fork");
|
|
if(pid == 0){
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(pfd[0], 1);
|
|
dprint(1, "exec tlsclient -a %s\n", ds);
|
|
if(auth)
|
|
execl("/bin/tlsclient", "tlsclient", "-a", ds, nil);
|
|
else
|
|
execl("/bin/tlsclient", "tlsclient", ds, nil);
|
|
sysfatal("exec: %r");
|
|
}
|
|
close(pfd[0]);
|
|
c->type = ConnGit9;
|
|
c->rfd = pfd[1];
|
|
c->wfd = dup(pfd[1], -1);
|
|
return githandshake(c, host, path, direction);
|
|
}
|
|
|
|
void
|
|
initconn(Conn *c, int rd, int wr)
|
|
{
|
|
c->type = ConnGit;
|
|
c->rfd = rd;
|
|
c->wfd = wr;
|
|
}
|
|
|
|
static int
|
|
dialgit(Conn *c, char *host, char *port, char *path, char *direction)
|
|
{
|
|
char *ds;
|
|
int fd;
|
|
|
|
if((ds = netmkaddr(host, "tcp", port)) == nil)
|
|
return -1;
|
|
dprint(1, "dial %s git-%s-pack %s\n", ds, direction, path);
|
|
fd = dial(ds, nil, nil, nil);
|
|
if(fd == -1)
|
|
return -1;
|
|
c->type = ConnGit;
|
|
c->rfd = fd;
|
|
c->wfd = dup(fd, -1);
|
|
return githandshake(c, host, path, direction);
|
|
}
|
|
|
|
static int
|
|
servelocal(Conn *c, char *path, char *direction)
|
|
{
|
|
int pid, pfd[2];
|
|
|
|
if(pipe(pfd) == -1)
|
|
sysfatal("unable to open pipe: %r");
|
|
pid = fork();
|
|
if(pid == -1)
|
|
sysfatal("unable to fork");
|
|
if(pid == 0){
|
|
close(pfd[1]);
|
|
dup(pfd[0], 0);
|
|
dup(pfd[0], 1);
|
|
execl("/bin/git/serve", "serve", "-w", nil);
|
|
sysfatal("exec: %r");
|
|
}
|
|
close(pfd[0]);
|
|
c->type = ConnGit;
|
|
c->rfd = pfd[1];
|
|
c->wfd = dup(pfd[1], -1);
|
|
return githandshake(c, nil, path, direction);
|
|
}
|
|
|
|
static int
|
|
localrepo(char *uri, char *path, int npath)
|
|
{
|
|
int fd;
|
|
|
|
snprint(path, npath, "%s/.git/../", uri);
|
|
fd = open(path, OREAD);
|
|
if(fd < 0)
|
|
return -1;
|
|
if(fd2path(fd, path, npath) != 0){
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gitconnect(Conn *c, char *uri, char *direction)
|
|
{
|
|
char proto[Nproto], host[Nhost], port[Nport];
|
|
char repo[Nrepo], path[Npath];
|
|
|
|
memset(c, 0, sizeof(Conn));
|
|
c->rfd = c->wfd = c->cfd = -1;
|
|
|
|
if(localrepo(uri, path, sizeof(path)) == 0)
|
|
return servelocal(c, path, direction);
|
|
|
|
if(parseuri(uri, proto, host, port, path, repo) == -1){
|
|
werrstr("bad uri %s", uri);
|
|
return -1;
|
|
}
|
|
if(strcmp(proto, "ssh") == 0)
|
|
return dialssh(c, host, port, path, direction);
|
|
else if(strcmp(proto, "git") == 0)
|
|
return dialgit(c, host, port, path, direction);
|
|
else if(strcmp(proto, "hjgit") == 0)
|
|
return dialhjgit(c, host, port, path, direction, 1);
|
|
else if(strcmp(proto, "gits") == 0)
|
|
return dialhjgit(c, host, port, path, direction, 0);
|
|
else if(strcmp(proto, "http") == 0 || strcmp(proto, "https") == 0)
|
|
return dialhttp(c, host, port, path, direction);
|
|
werrstr("unknown protocol %s", proto);
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
writephase(Conn *c)
|
|
{
|
|
char hdr[128];
|
|
int n;
|
|
|
|
dprint(1, "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)
|
|
{
|
|
dprint(1, "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 ConnGit:
|
|
break;
|
|
case ConnGit9:
|
|
case ConnSsh:
|
|
free(wait());
|
|
break;
|
|
case ConnHttp:
|
|
close(c->cfd);
|
|
break;
|
|
}
|
|
}
|