ref: f7e50dd002ab7060dc23d3431de85429520ca6d8
parent: 48df9a55a8a81f67d92934210842fecb7c9500ba
author: Jacob Moody <moody@posixcafe.org>
date: Mon Sep 1 13:03:50 EDT 2025
add list, refactor a bit
--- a/README
+++ b/README
@@ -1,7 +1,10 @@
Basics:
Requires $AWS_ACCESS_KEY_ID, $AWS_SECRET_ACCESS_KEY, and $AWS_ENDPOINT_URL_S3 defined
- Usage: s3cp source s3://<bucket>/destination
- Usage: s3cp s3://<bucket>/source destination
+ Usage: 6.out cat s3://bucket/file
+ Usage: 6.out cp source s3://bucket/destination
+ Usage: 6.out cp s3://bucket/source <destination>
+ Usage: 6.out rm s3://bucket/path
+ Usage: 6.out ls s3://bucket/prefix
Specifics/Bugs:
Uses webfs(4)
--- a/mkfile
+++ b/mkfile
@@ -1,7 +1,12 @@
</$objtype/mkfile
BIN=$home/bin/$objtype
-TARG=\
- s3cp\
-</sys/src/cmd/mkmany
+OFILES=\
+ xml.$O\
+ s3.$O\
+
+HFILES=\
+ xml.h\
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/s3.c
@@ -1,0 +1,382 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
+#include "xml.h"
+
+typedef struct {+ char *endpoint;
+ char *host;
+ char *access;
+ char *secret;
+ char *bucket;
+ char *region;
+} S3;
+
+typedef struct {+ char method[16];
+ char time[128];
+ uchar payhash[SHA2_256dlen];
+ char authhdr[512];
+ char mime[32];
+} Hreq;
+
+static void
+datetime(char *date, int ndate, char *time, int ntime)
+{+ Tm t;
+
+ tmnow(&t, nil);
+ snprint(date, ndate, "%τ", tmfmt(&t, "YYYYMMDD"));
+ snprint(time, ntime, "%sT%τZ", date, tmfmt(&t, "hhmmss"));
+}
+
+#define hmac(data, dlen, key, klen, digest) hmac_sha2_256(data, dlen, key, klen, digest, nil)
+
+static void
+mkkey(char *key, char *date, char *region, char *service, uchar out[SHA2_256dlen])
+{+ char buf[256];
+
+ snprint(buf, sizeof buf, "AWS4%s", key);
+ hmac((uchar*)date, strlen(date), (uchar*)buf, strlen(buf), out);
+ hmac((uchar*)region, strlen(region), out, SHA2_256dlen, (uchar*)buf);
+ hmac((uchar*)service, strlen(service), (uchar*)buf, SHA2_256dlen, out);
+ hmac((uchar*)"aws4_request", 12, out, SHA2_256dlen, out);
+}
+
+static void
+mkhreq(Hreq *hreq, S3 *s3, char *method, char *path)
+{+ char date[64];
+ uchar key[SHA2_256dlen], sig[SHA2_256dlen];
+ char buf[512], req[512];
+ char *sgndhdr;
+
+ datetime(date, sizeof date, hreq->time, sizeof hreq->time);
+ if(strcmp(method, "PUT") == 0){+ snprint(buf, sizeof buf, "content-type:%s\nhost:%s\nx-amz-content-sha256:%.*lH\nx-amz-date:%s\n",
+ hreq->mime, s3->host, SHA2_256dlen, hreq->payhash, hreq->time);
+ sgndhdr = "content-type;host;x-amz-content-sha256;x-amz-date";
+ } else if(strcmp(method, "GET") == 0 || strcmp(method, "DELETE")==0){+ hreq->mime[0] = 0;
+ sha2_256(nil, 0, hreq->payhash, nil);
+ snprint(buf, sizeof buf, "host:%s\nx-amz-date:%s\n", s3->host, hreq->time);
+ sgndhdr = "host;x-amz-date";
+ } else
+ sysfatal("invalid method");+
+ snprint(req, sizeof req, "%s\n/%s/%s\n%s\n%s\n%s\n%.*lH",
+ method, s3->bucket, path, "", buf, sgndhdr, SHA2_256dlen, hreq->payhash);
+ sha2_256((uchar*)req, strlen(req), key, nil);
+ snprint(buf, sizeof buf, "%s\n%s\n%s/%s/%s/aws4_request\n%.*lH",
+ "AWS4-HMAC-SHA256", hreq->time, date, s3->region, "s3", SHA2_256dlen, key);
+ mkkey(s3->secret, date, s3->region, "s3", key);
+ hmac((uchar*)buf, strlen(buf), key, SHA2_256dlen, sig);
+
+ snprint(hreq->authhdr, sizeof hreq->authhdr, "%s Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%.*lH",
+ "AWS4-HMAC-SHA256", s3->access, date, s3->region, "s3", sgndhdr, SHA2_256dlen, sig);
+ snprint(hreq->method, sizeof hreq->method, "%s", method);
+}
+
+static void
+prep(S3 *s3, int cfd, char *path, Hreq *hreq)
+{+ if(fprint(cfd, "url %s/%s/%s", s3->endpoint, s3->bucket, path) < 0)
+ sysfatal("url: %r");+ if(fprint(cfd, "request %s", hreq->method) < 0)
+ sysfatal("request: %r");+ if(fprint(cfd, "headers Authorization:%s", hreq->authhdr) < 0)
+ sysfatal("headers2: %r");+ if(fprint(cfd, "headers x-amz-date:%s\nx-amz-content-sha256:%.*lH", hreq->time, SHA2_256dlen, hreq->payhash) < 0)
+ sysfatal("headers: %r");+ if(hreq->mime[0] != 0 && fprint(cfd, "contenttype %s", hreq->mime) < 0)
+ sysfatal("contenttype: %r");+}
+
+static void
+download(S3 *s3, int cfd, char *conn, char *path, Biobuf *local)
+{+ int bfd;
+ long n;
+ char buf[64];
+ char data[8192];
+ Hreq hreq;
+
+ mkhreq(&hreq, s3, "GET", path);
+ prep(s3, cfd, path, &hreq);
+ snprint(buf, sizeof buf, "/mnt/web/%s/body", conn);
+ bfd = open(buf, OREAD);
+ if(bfd < 0)
+ sysfatal("download body: %r");+ for(;;){+ n = read(bfd, data, sizeof data);
+ if(n < 0)
+ sysfatal("download body: %r");+ if(n == 0)
+ return;
+ Bwrite(local, data, n);
+ }
+}
+
+static void
+mimetype(char *path, char *out, int nout)
+{+ int p[2];
+ char buf[256];
+ int n;
+
+ pipe(p);
+ switch(fork()){+ case 0:
+ close(0);
+ dup(p[0], 1);
+ close(2);
+ close(p[0]);
+ close(p[1]);
+ execl("/bin/file", "file", "-m", path, nil);+ sysfatal("execl: %r");+ default:
+ close(p[0]);
+ n = read(p[1], buf, sizeof buf - 1);
+ if(n <= 0)
+ sysfatal("no mimetype found");+ buf[n - 1] = 0;
+ snprint(out, nout, "%s", buf);
+ }
+}
+
+static void
+upload(S3 *s3, int cfd, char *conn, char *localpath, char *remotepath)
+{+ DigestState *ds;
+ uchar data[8192];
+ long n;
+ int fd, bfd;
+ char buf[256];
+ Hreq hreq;
+
+ mimetype(localpath, hreq.mime, sizeof hreq.mime);
+ fd = open(localpath, OREAD);
+ if(fd < 0)
+ sysfatal("upload open: %r");+ for(ds = nil;;){+ n = read(fd, data, sizeof data);
+ if(n < 0)
+ sysfatal("file read: %r");+ if(n == 0)
+ break;
+ ds = sha2_256(data, n, nil, ds);
+ }
+ sha2_256(nil, 0, hreq.payhash, ds);
+ seek(fd, 0, 0);
+
+ mkhreq(&hreq, s3, "PUT", remotepath);
+ prep(s3, cfd, remotepath, &hreq);
+ snprint(buf, sizeof buf, "/mnt/web/%s/postbody", conn);
+ bfd = open(buf, OWRITE);
+ if(bfd < 0)
+ sysfatal("upload postbody open: %r");+ for(;;){+ n = read(fd, buf, sizeof buf);
+ if(n < 0)
+ sysfatal("file read: %r");+ if(n == 0)
+ break;
+ if(write(bfd, buf, n) < 0)
+ sysfatal("upload write: %r");+ }
+ close(bfd);
+
+ snprint(buf, sizeof buf, "/mnt/web/%s/body", conn);
+ if((bfd = open(buf, OREAD)) < 0)
+ sysfatal("open3: %r");+ close(bfd);
+}
+
+static int
+parseuri(S3 *s3, char *path, int npath, char *arg)
+{+ char *p;
+
+ if(strstr(arg, "s3://") != arg)
+ return -1;
+ arg+=5;
+ p = strchr(arg, '/');
+ if(p == nil || p == arg)
+ return -1;
+ snprint(path, npath, "%s", p+1);
+ s3->bucket = strdup(arg);
+ s3->bucket[p-arg] = 0;
+ return 0;
+}
+
+_Noreturn static void
+usage(void)
+{+ fprint(2, "Requires $AWS_ACCESS_KEY_ID, $AWS_SECRET_ACCESS_KEY, and $AWS_ENDPOINT_URL_S3 defined\n");
+ fprint(2, "Usage: %s cat s3://bucket/file\n", argv0);
+ fprint(2, "Usage: %s cp source s3://bucket/destination\n", argv0);
+ fprint(2, "Usage: %s cp s3://bucket/source <destination>\n", argv0);
+ fprint(2, "Usage: %s rm s3://bucket/path\n", argv0);
+ fprint(2, "Usage: %s ls s3://bucket/prefix\n", argv0);
+ exits("usage");+}
+
+static void
+cp(S3 *s3, int cfd, char *conn, int argc, char **argv)
+{+ char path[512];
+ Biobuf *b;
+ int fd;
+
+ if(argc == 0 || argc > 2)
+ usage();
+ if(parseuri(s3, path, sizeof path, argv[0]) == 0){+ if(argc > 1 && parseuri(s3, path, sizeof path, argv[1]) == 0)
+ sysfatal("s3:// → s3:// is not implemented");+ if(argc == 1)
+ fd = 1;
+ else {+ fd = create(argv[1], OWRITE, 0644);
+ if(fd < 0)
+ sysfatal("create: %r");+ }
+ b = Bfdopen(fd, OWRITE);
+ if(b == nil)
+ sysfatal("Bfdopen: %r");+ download(s3, cfd, conn, path, b);
+ return;
+ }
+ if(argc == 1 || parseuri(s3, path, sizeof path, argv[1]) < 0)
+ usage();
+ upload(s3, cfd, conn, argv[0], path);
+}
+
+static void
+delete(S3 *s3, int cfd, char *conn, int argc, char **argv)
+{+ int fd;
+ char buf[256];
+ Hreq hreq;
+ char path[512];
+
+ if(argc == 0)
+ usage();
+ if(parseuri(s3, path, sizeof path, argv[0]) < 0)
+ usage();
+ mkhreq(&hreq, s3, "DELETE", path);
+ prep(s3, cfd, path, &hreq);
+ snprint(buf, sizeof buf, "/mnt/web/%s/body", conn);
+ fd = open(buf, OREAD);
+ if(fd < 0)
+ sysfatal("delete failed: %r");+ close(fd);
+}
+
+static void
+list(S3 *s3, int cfd, char *conn, int argc, char **argv)
+{+ int p[2];
+ Biobuf *in, *out;
+ Xelem *x;
+ char path[512];
+
+ if(argc == 0)
+ usage();
+ if(parseuri(s3, path, sizeof path, argv[0]) < 0){+ fprint(2, "parseuri failed\n");
+ usage();
+ }
+ if(pipe(p) < 0)
+ sysfatal("pipe: %r");+ switch(fork()){+ case -1:
+ sysfatal("fork: %r");+ case 0:
+ close(p[1]);
+ in = Bfdopen(p[0], OWRITE);
+ if(in == nil)
+ sysfatal("Bfdopen: %r");+ download(s3, cfd, conn, path, in);
+ exits(nil);
+ default:
+ close(p[0]);
+ break;
+ }
+ out = Bfdopen(p[1], OREAD);
+ if(out == nil)
+ sysfatal("Bfdopen: %r");+ x = xmlread(out, 0);
+ if(x == nil)
+ sysfatal("file was not valid XML, maybe not a prefix?");+ if((x = xmlget(x, "Contents", nil)) == nil)
+ sysfatal("xml did not have Contents field");+
+ for(; x != nil && xmlget(x, "Key", nil) != nil; x = x->next){+ print("%s\n", xmlget(x, "Key", nil)->v);+ }
+}
+
+struct {+ char *cmd;
+ void (*fn)(S3*,int,char*,int,char**);
+} cmdtab[] = {+ { "cp", cp },+ { "cat", cp },+ { "rm", delete },+ { "ls", list },+};
+
+void
+main(int argc , char **argv)
+{+ S3 s3;
+ int fd;
+ int i;
+ long n;
+ char buf[64];
+
+ tmfmtinstall();
+ fmtinstall('H', encodefmt);+ ARGBEGIN{+ default:
+ usage();
+ break;
+ }ARGEND
+ if(argc == 0)
+ usage();
+
+ s3.access = getenv("AWS_ACCESS_KEY_ID");+ s3.secret = getenv("AWS_SECRET_ACCESS_KEY");+ s3.endpoint = getenv("AWS_ENDPOINT_URL_S3");+ s3.region = getenv("AWS_DEFAULT_REGION");+ if(s3.access == nil || s3.secret == nil || s3.endpoint == nil)
+ usage();
+ if(s3.region == nil)
+ s3.region = "auto";
+
+ s3.host = strstr(s3.endpoint, "://");
+ if(s3.host == nil)
+ sysfatal("invalid endpoint url");+ s3.host += 3;
+
+ fd = open("/mnt/web/clone", ORDWR);+ if(fd < 0)
+ sysfatal("open: %r");+ n = read(fd, buf, sizeof buf - 1);
+ if(n <= 0)
+ sysfatal("read: %r");+ buf[n-1] = 0;
+
+ for(i = 0; i < nelem(cmdtab); i++){+ if(strcmp(argv[0], cmdtab[i].cmd) != 0)
+ continue;
+ argv++;
+ argc--;
+ cmdtab[i].fn(&s3, fd, buf, argc, argv);
+ exits(nil);
+ }
+ sysfatal("unsupported cmd: %s", argv[0]);+}
--- a/s3cp.c
+++ /dev/null
@@ -1,299 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <mp.h>
-#include <libsec.h>
-
-typedef struct {- char *endpoint;
- char *host;
- char *access;
- char *secret;
- char *bucket;
- char *region;
-} S3;
-
-typedef struct {- char method[16];
- char time[128];
- uchar payhash[SHA2_256dlen];
- char authhdr[512];
- char mime[32];
-} Hreq;
-
-static void
-datetime(char *date, int ndate, char *time, int ntime)
-{- Tm t;
-
- tmnow(&t, nil);
- snprint(date, ndate, "%τ", tmfmt(&t, "YYYYMMDD"));
- snprint(time, ntime, "%sT%τZ", date, tmfmt(&t, "hhmmss"));
-}
-
-#define hmac(data, dlen, key, klen, digest) hmac_sha2_256(data, dlen, key, klen, digest, nil)
-
-static void
-mkkey(char *key, char *date, char *region, char *service, uchar out[SHA2_256dlen])
-{- char buf[256];
-
- snprint(buf, sizeof buf, "AWS4%s", key);
- hmac((uchar*)date, strlen(date), (uchar*)buf, strlen(buf), out);
- hmac((uchar*)region, strlen(region), out, SHA2_256dlen, (uchar*)buf);
- hmac((uchar*)service, strlen(service), (uchar*)buf, SHA2_256dlen, out);
- hmac((uchar*)"aws4_request", 12, out, SHA2_256dlen, out);
-}
-
-static void
-mkhreq(Hreq *hreq, S3 *s3, char *method, char *path)
-{- char date[64];
- uchar key[SHA2_256dlen], sig[SHA2_256dlen];
- char buf[512], req[512];
- char *sgndhdr;
-
- datetime(date, sizeof date, hreq->time, sizeof hreq->time);
- if(strcmp(method, "PUT") == 0){- snprint(buf, sizeof buf, "content-type:%s\nhost:%s\nx-amz-content-sha256:%.*lH\nx-amz-date:%s\n",
- hreq->mime, s3->host, SHA2_256dlen, hreq->payhash, hreq->time);
- sgndhdr = "content-type;host;x-amz-content-sha256;x-amz-date";
- } else if(strcmp(method, "GET") == 0 || strcmp(method, "DELETE")==0){- hreq->mime[0] = 0;
- sha2_256(nil, 0, hreq->payhash, nil);
- snprint(buf, sizeof buf, "host:%s\nx-amz-date:%s\n", s3->host, hreq->time);
- sgndhdr = "host;x-amz-date";
- } else
- sysfatal("invalid method");-
- snprint(req, sizeof req, "%s\n/%s/%s\n%s\n%s\n%s\n%.*lH",
- method, s3->bucket, path, "", buf, sgndhdr, SHA2_256dlen, hreq->payhash);
- sha2_256((uchar*)req, strlen(req), key, nil);
- snprint(buf, sizeof buf, "%s\n%s\n%s/%s/%s/aws4_request\n%.*lH",
- "AWS4-HMAC-SHA256", hreq->time, date, s3->region, "s3", SHA2_256dlen, key);
- mkkey(s3->secret, date, s3->region, "s3", key);
- hmac((uchar*)buf, strlen(buf), key, SHA2_256dlen, sig);
-
- snprint(hreq->authhdr, sizeof hreq->authhdr, "%s Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%.*lH",
- "AWS4-HMAC-SHA256", s3->access, date, s3->region, "s3", sgndhdr, SHA2_256dlen, sig);
- snprint(hreq->method, sizeof hreq->method, "%s", method);
-}
-
-static void
-prep(S3 *s3, int cfd, char *path, Hreq *hreq)
-{- if(fprint(cfd, "url %s/%s/%s", s3->endpoint, s3->bucket, path) < 0)
- sysfatal("url: %r");- if(fprint(cfd, "request %s", hreq->method) < 0)
- sysfatal("request: %r");- if(fprint(cfd, "headers Authorization:%s", hreq->authhdr) < 0)
- sysfatal("headers2: %r");- if(fprint(cfd, "headers x-amz-date:%s\nx-amz-content-sha256:%.*lH", hreq->time, SHA2_256dlen, hreq->payhash) < 0)
- sysfatal("headers: %r");- if(hreq->mime[0] != 0 && fprint(cfd, "contenttype %s", hreq->mime) < 0)
- sysfatal("contenttype: %r");-}
-
-static void
-download(S3 *s3, char *path, char *localpath, int cfd, char *conn)
-{- int fd, bfd;
- long n;
- char buf[64];
- char data[8192];
- Hreq hreq;
-
- fd = create(localpath, OWRITE, 0644);
- if(fd < 0)
- sysfatal("download create: %r");- mkhreq(&hreq, s3, "GET", path);
- prep(s3, cfd, path, &hreq);
- snprint(buf, sizeof buf, "/mnt/web/%s/body", conn);
- bfd = open(buf, OREAD);
- if(bfd < 0)
- sysfatal("download body: %r");- for(;;){- n = read(bfd, data, sizeof data);
- if(n < 0)
- sysfatal("download body: %r");- if(n == 0)
- return;
- write(fd, data, n);
- }
-}
-
-static void
-mimetype(char *path, char *out, int nout)
-{- int p[2];
- char buf[256];
- int n;
-
- pipe(p);
- switch(fork()){- case 0:
- close(0);
- dup(p[0], 1);
- close(2);
- close(p[0]);
- close(p[1]);
- execl("/bin/file", "file", "-m", path, nil);- sysfatal("execl: %r");- default:
- close(p[0]);
- n = read(p[1], buf, sizeof buf - 1);
- if(n <= 0)
- sysfatal("no mimetype found");- buf[n - 1] = 0;
- snprint(out, nout, "%s", buf);
- }
-}
-
-static void
-upload(S3 *s3, char *path, char *localpath, int cfd, char *conn)
-{- DigestState *ds;
- uchar data[8192];
- long n;
- int fd, bfd;
- char buf[256];
- Hreq hreq;
-
- mimetype(localpath, hreq.mime, sizeof hreq.mime);
- fd = open(localpath, OREAD);
- if(fd < 0)
- sysfatal("upload open: %r");- for(ds = nil;;){- n = read(fd, data, sizeof data);
- if(n < 0)
- sysfatal("file read: %r");- if(n == 0)
- break;
- ds = sha2_256(data, n, nil, ds);
- }
- sha2_256(nil, 0, hreq.payhash, ds);
- seek(fd, 0, 0);
-
- mkhreq(&hreq, s3, "PUT", path);
- prep(s3, cfd, path, &hreq);
- snprint(buf, sizeof buf, "/mnt/web/%s/postbody", conn);
- bfd = open(buf, OWRITE);
- if(bfd < 0)
- sysfatal("upload postbody open: %r");- for(;;){- n = read(fd, buf, sizeof buf);
- if(n < 0)
- sysfatal("file read: %r");- if(n == 0)
- break;
- if(write(bfd, buf, n) < 0)
- sysfatal("upload write: %r");- }
- close(bfd);
-
- snprint(buf, sizeof buf, "/mnt/web/%s/body", conn);
- if((bfd = open(buf, OREAD)) < 0)
- sysfatal("open3: %r");- close(bfd);
-}
-
-static void
-delete(S3 *s3, char *path, char*, int cfd, char *conn)
-{- int fd;
- char buf[256];
- Hreq hreq;
-
- mkhreq(&hreq, s3, "DELETE", path);
- prep(s3, cfd, path, &hreq);
- snprint(buf, sizeof buf, "/mnt/web/%s/body", conn);
- fd = open(buf, OREAD);
- if(fd < 0)
- sysfatal("delete failed: %r");- close(fd);
-}
-
-_Noreturn static void
-usage(void)
-{- fprint(2, "Requires $AWS_ACCESS_KEY_ID, $AWS_SECRET_ACCESS_KEY, and $AWS_ENDPOINT_URL_S3 defined\n");
- fprint(2, "Usage: %s source s3://<bucket>/destination\n", argv0);
- fprint(2, "Usage: %s s3://<bucket>/source destination\n", argv0);
- exits("usage");-}
-
-void
-main(int argc , char **argv)
-{- S3 s3;
- int fd;
- long n;
- char buf[64];
- char *p, *path, *localpath;
- void (*op)(S3*,char*,char*,int,char*);
- int rflag;
-
- tmfmtinstall();
- fmtinstall('H', encodefmt);- rflag = 0;
- ARGBEGIN{- case 'r':
- rflag++;
- break;
- default:
- usage();
- break;
- }ARGEND
- if(rflag && argc < 1)
- usage();
- else if(!rflag && argc < 2)
- usage();
-
- s3.access = getenv("AWS_ACCESS_KEY_ID");- s3.secret = getenv("AWS_SECRET_ACCESS_KEY");- s3.endpoint = getenv("AWS_ENDPOINT_URL_S3");- s3.region = getenv("AWS_DEFAULT_REGION");- if(s3.access == nil || s3.secret == nil || s3.endpoint == nil)
- usage();
- if(s3.region == nil)
- s3.region = "auto";
-
- s3.host = strstr(s3.endpoint, "://");
- if(s3.host == nil)
- sysfatal("invalid endpoint url");- s3.host += 3;
-
- if(strstr(argv[0], "s3://")==argv[0]){- s3.bucket = strdup(argv[0]+5);
- if(rflag){- op = delete;
- localpath = nil;
- } else {- if(strstr(argv[1], "s3://")==argv[1])
- sysfatal("s3:// → s3:// not implemented");- localpath = strdup(argv[1]);
- op = download;
- }
- } else if(!rflag && strstr(argv[1], "s3://")==argv[1]){- localpath = strdup(argv[0]);
- s3.bucket = strdup(argv[1]+5);
- op = upload;
- } else
- usage();
-
- p = strchr(s3.bucket, '/');
- if(p == nil)
- sysfatal("no path provided within bucket");- *p = 0;
- path = p+1;
-
- fd = open("/mnt/web/clone", ORDWR);- if(fd < 0)
- sysfatal("open: %r");- n = read(fd, buf, sizeof buf - 1);
- if(n <= 0)
- sysfatal("read: %r");- buf[n-1] = 0;
-
- op(&s3, path, localpath, fd, buf);
- exits(nil);
-}
--- /dev/null
+++ b/xml.c
@@ -1,0 +1,319 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "xml.h"
+
+static char *escmap[] =
+{+ "\x06\""",
+ "\x06\''",
+ "\x04<<",
+ "\x04>>",
+ "\x05&&",
+};
+
+enum
+{+ Xmlvalue = 2,
+};
+
+static char *
+unxml(char *orig)
+{+ char *s, *o, *e;
+ int i, rsz;
+ Rune r;
+
+ for(s = orig, o = orig; *s != 0;){+next:
+ if(*s == '\r'){+ *o++ = '\n';
+ s += s[1] == '\n' ? 2 : 1;
+ continue;
+ }
+
+ rsz = chartorune(&r, s);
+
+ if(r == '&'){+ if(s[1] == '#' && (e = strchr(s+2, ';')) != nil && e != s+2){+ s += 2;
+ if(*s == 'x'){+ *s = '0';
+ o += dec16((uchar*)o, e-o, s, e-s);
+ }else if(isdigit(*s)){+ *o++ = atoi(s);
+ }
+ s = e+1;
+ continue;
+ }else{+ for(i = 0; i < nelem(escmap); i++){+ if(strncmp(s, &escmap[i][2], escmap[i][0]) == 0){+ *o++ = escmap[i][1];
+ s += escmap[i][0];
+ goto next;
+ }
+ }
+ }
+ }
+
+ memmove(o, s, rsz);
+ s += rsz;
+ o += rsz;
+ }
+
+ *o = 0;
+ return orig;
+}
+
+static Xattr *
+xmlattr(char *s, int *err)
+{+ Xattr *a, *attrs;
+ char *p;
+
+ attrs = nil;
+ *err = 0;
+
+ for(; *s;){+ a = mallocz(sizeof(*a), 1);
+ a->n = s;
+ for(; *s && *s != '='; s++);
+ if(*s != '='){+ werrstr("xml sucks (%d)", *s);+ goto error;
+ }
+ *s++ = 0;
+ if(*s != '\'' && *s != '\"'){+ werrstr("xml is complicated (%d)", *s);+ goto error;
+ }
+ a->v = s+1;
+ s = utfrune(a->v, *s);
+ if(s == nil){+ werrstr("xml is broken");+ goto error;
+ }
+ *s++ = 0;
+ a->next = attrs;
+ a->n = unxml(a->n);
+ a->v = unxml(a->v);
+ attrs = a;
+ if(*s == ' ')
+ s++;
+ if((p = strchr(a->n, ':')) != nil && strncmp(p, ":zdef", 5) == 0)
+ *p = 0;
+ }
+
+ return attrs;
+error:
+ *err = 1;
+ free(a);
+ for(; attrs != nil; attrs = a){+ a = attrs->next;
+ free(attrs);
+ }
+ return nil;
+}
+
+static Xelem *
+xmlread_(Biobufhdr *h, Xelem *par, int flags)
+{+ char *s, *t;
+ Xelem *x, *ch;
+ int r, closed, len, err;
+
+ x = nil;
+
+ for(;;){+ r = Bgetrune(h);
+ if(r < 0){+ werrstr("xmlread: %r");+ goto error;
+ }
+ if(r == '<')
+ break;
+ if(isspacerune(r))
+ continue;
+ if(flags & Xmlvalue && par != nil){+ Bungetrune(h);
+ if((s = Brdstr(h, '<', 1)) == nil){+ werrstr("xmlread: %r");+ goto error;
+ }
+ par->v = unxml(s);
+ if((s = Brdstr(h, '>', 1)) == nil){+ free(par->v);
+ par->v = nil;
+ werrstr("xmlread: %r");+ }
+ free(s);
+ return nil;
+ }
+ werrstr("xmlread: unexpected rune (%C)", r);+ goto error;
+ }
+
+ s = Brdstr(h, '>', 1);
+ if(s == nil){+ werrstr("xmlread: %r");+ goto error;
+ }
+ if(s[0] == '/'){+ free(s);
+ return nil;
+ }
+ if(s[0] == '?'){+ free(s);
+ return xmlread_(h, par, flags);
+ }
+
+ x = mallocz(sizeof(*x), 1);
+ x->priv = s;
+ x->n = s;
+
+ if(strncmp(x->n, "zdef", 4) == 0){+ if((x->n = strchr(x->n, ':')) == nil){+ werrstr("xmlread: zdef without ':'");+ goto error;
+ }
+ x->n += 1;
+ }
+
+ len = strlen(s);
+ if(s[len-1] == '/' || s[len-1] == '?'){+ closed = 1;
+ s[len-1] = 0;
+ }else
+ closed = flags & Xmlstartonly;
+
+ for(; *s && *s != ' '; s++);
+ if(*s){+ *s++ = 0;
+ x->a = xmlattr(s, &err);
+ if(err != 0)
+ goto error;
+ }
+
+ if(strcmp(x->n, "html") == 0){+ for(len = 0;; len += r){+ s = Brdstr(h, '>', 0);
+ if(s == nil){+ werrstr("xmlread: %r");+ goto error;
+ }
+
+ r = strlen(s);
+ x->v = realloc(x->v, len + r + 1);
+ if(x->v == nil){+ werrstr("xmlread: %r");+ goto error;
+ }
+ strcpy(x->v+len, s);
+ free(s);
+ t = strstr(x->v+len, "</html>");
+ if(t != nil){+ *t = 0;
+ return x;
+ }
+ }
+ }
+
+ if(!closed){+ for(;;){+ flags = Xmlvalue;
+ ch = xmlread_(h, x, flags);
+ if(ch == nil)
+ break;
+ ch->next = x->ch;
+ x->ch = ch;
+ }
+ }
+
+ return x;
+
+error:
+ xmlfree(x);
+ return nil;
+}
+
+Xelem *
+xmlread(Biobuf *b, int flags)
+{+ return xmlread_(b, nil, flags & Xmlstartonly);
+}
+
+void
+xmlfree(Xelem *x)
+{+ Xattr *a, *ta;
+ Xelem *n, *n2;
+
+ if(x == nil)
+ return;
+
+ xmlfree(x->ch);
+ free(x->v);
+ x->ch = nil;
+ x->v = nil;
+ free(x->priv);
+ for(a = x->a; a != nil; a = ta){+ ta = a->next;
+ free(a);
+ }
+
+ for(n = x->next; n != nil; n = n2){+ n2 = n->next;
+ n->next = nil;
+ xmlfree(n);
+ }
+
+ free(x);
+}
+
+Xelem *
+xmlget(Xelem *x, char *path, ...)
+{+ char **s;
+
+ for(s = &path; *s != nil; s++){+ for(x = x->ch; x != nil && strcmp(x->n, *s) != 0; x = x->next);
+ if(x == nil)
+ return nil;
+ }
+
+ return x;
+}
+
+Xattr *
+xmlgetattr(Xattr *a, char *name)
+{+ for(; a != nil; a = a->next)
+ if(strcmp(a->n, name) == 0)
+ return a;
+ return nil;
+}
+
+static void
+xmlprint_(Xelem *x, int fd, int off)
+{+ Xattr *a;
+
+ for(; x != nil; x = x->next){+ fprint(fd, "%*c%q", off, ' ', x->n);
+ if(x->v != nil)
+ fprint(fd, "=%#q", x->v);
+ for(a = x->a; a != nil; a = a->next)
+ fprint(fd, " %q=%#q", a->n, a->v);
+ fprint(fd, "\n");
+ off += 4;
+ xmlprint_(x->ch, fd, off);
+ off -= 4;
+ }
+}
+
+void
+xmlprint(Xelem *x, int fd)
+{+ xmlprint_(x, fd, 0);
+}
--- /dev/null
+++ b/xml.h
@@ -1,0 +1,30 @@
+typedef struct Xelem Xelem;
+typedef struct Xattr Xattr;
+
+struct Xelem
+{+ char *n;
+ char *v;
+ Xattr *a;
+ Xelem *ch;
+ Xelem *next;
+ void *priv;
+};
+
+struct Xattr
+{+ char *n;
+ char *v;
+ Xattr *next;
+};
+
+enum
+{+ Xmlstartonly = 1,
+};
+
+Xelem *xmlread(Biobuf *b, int flags);
+void xmlfree(Xelem *x);
+Xelem *xmlget(Xelem *x, char *path, ...);
+Xattr *xmlgetattr(Xattr *a, char *n);
+void xmlprint(Xelem *x, int fd);
--
⑨