| #include <u.h> |
| #include <libc.h> |
| #include <bio.h> |
| #include <flate.h> |
| #include "zip.h" |
| |
| enum |
| { |
| BufSize = 4096 |
| }; |
| |
| static int cheader(Biobuf *bin, ZipHead *zh); |
| static int copyout(int ofd, Biobuf *bin, long len); |
| static int crcwrite(void *ofd, void *buf, int n); |
| static int findCDir(Biobuf *bin, char *file); |
| static int get1(Biobuf *b); |
| static int get2(Biobuf *b); |
| static u32int get4(Biobuf *b); |
| static char *getname(Biobuf *b, int len); |
| static int header(Biobuf *bin, ZipHead *zh); |
| static long msdos2time(int time, int date); |
| static int sunzip(Biobuf *bin); |
| static int sunztable(Biobuf *bin); |
| static void trailer(Biobuf *bin, ZipHead *zh); |
| static int unzip(Biobuf *bin, char *file); |
| static int unzipEntry(Biobuf *bin, ZipHead *czh); |
| static int unztable(Biobuf *bin, char *file); |
| static int wantFile(char *file); |
| |
| static void *emalloc(u32int); |
| static void error(char*, ...); |
| /* #pragma varargck argpos error 1 */ |
| |
| static Biobuf bin; |
| static u32int crc; |
| static u32int *crctab; |
| static int debug; |
| static char *delfile; |
| static int lower; |
| static int nwant; |
| static u32int rlen; |
| static int settimes; |
| static int stdout; |
| static int verbose; |
| static char **want; |
| static int wbad; |
| static u32int wlen; |
| static jmp_buf zjmp; |
| |
| static void |
| usage(void) |
| { |
| fprint(2, "usage: unzip [-tsv] [-f zipfile] [file ...]\n"); |
| exits("usage"); |
| } |
| |
| void |
| main(int argc, char *argv[]) |
| { |
| char *zfile; |
| int fd, ok, table, stream; |
| |
| table = 0; |
| stream = 0; |
| zfile = nil; |
| ARGBEGIN{ |
| case 'D': |
| debug++; |
| break; |
| case 'c': |
| stdout++; |
| break; |
| case 'i': |
| lower++; |
| break; |
| case 'f': |
| zfile = ARGF(); |
| if(zfile == nil) |
| usage(); |
| break; |
| case 's': |
| stream++; |
| break; |
| case 't': |
| table++; |
| break; |
| case 'T': |
| settimes++; |
| break; |
| case 'v': |
| verbose++; |
| break; |
| default: |
| usage(); |
| break; |
| }ARGEND |
| |
| nwant = argc; |
| want = argv; |
| |
| crctab = mkcrctab(ZCrcPoly); |
| ok = inflateinit(); |
| if(ok != FlateOk) |
| sysfatal("inflateinit failed: %s\n", flateerr(ok)); |
| |
| if(zfile == nil){ |
| Binit(&bin, 0, OREAD); |
| zfile = "<stdin>"; |
| }else{ |
| fd = open(zfile, OREAD); |
| if(fd < 0) |
| sysfatal("can't open %s: %r", zfile); |
| Binit(&bin, fd, OREAD); |
| } |
| |
| if(table){ |
| if(stream) |
| ok = sunztable(&bin); |
| else |
| ok = unztable(&bin, zfile); |
| }else{ |
| if(stream) |
| ok = sunzip(&bin); |
| else |
| ok = unzip(&bin, zfile); |
| } |
| |
| exits(ok ? nil: "errors"); |
| } |
| |
| /* |
| * print the table of contents from the "central directory structure" |
| */ |
| static int |
| unztable(Biobuf *bin, char *file) |
| { |
| ZipHead zh; |
| int volatile entries; |
| |
| entries = findCDir(bin, file); |
| if(entries < 0) |
| return 0; |
| |
| if(verbose > 1) |
| print("%d items in the archive\n", entries); |
| while(entries-- > 0){ |
| if(setjmp(zjmp)){ |
| free(zh.file); |
| return 0; |
| } |
| |
| memset(&zh, 0, sizeof(zh)); |
| if(!cheader(bin, &zh)) |
| return 1; |
| |
| if(wantFile(zh.file)){ |
| if(verbose) |
| print("%-32s %10lud %s", zh.file, zh.uncsize, ctime(msdos2time(zh.modtime, zh.moddate))); |
| else |
| print("%s\n", zh.file); |
| |
| if(verbose > 1){ |
| print("\tmade by os %d vers %d.%d\n", zh.madeos, zh.madevers/10, zh.madevers % 10); |
| print("\textract by os %d vers %d.%d\n", zh.extos, zh.extvers/10, zh.extvers % 10); |
| print("\tflags %x\n", zh.flags); |
| print("\tmethod %d\n", zh.meth); |
| print("\tmod time %d\n", zh.modtime); |
| print("\tmod date %d\n", zh.moddate); |
| print("\tcrc %lux\n", zh.crc); |
| print("\tcompressed size %lud\n", zh.csize); |
| print("\tuncompressed size %lud\n", zh.uncsize); |
| print("\tinternal attributes %ux\n", zh.iattr); |
| print("\texternal attributes %lux\n", zh.eattr); |
| print("\tstarts at %ld\n", zh.off); |
| } |
| } |
| |
| free(zh.file); |
| zh.file = nil; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * print the "local file header" table of contents |
| */ |
| static int |
| sunztable(Biobuf *bin) |
| { |
| ZipHead zh; |
| vlong off; |
| u32int hcrc, hcsize, huncsize; |
| int ok, err; |
| |
| ok = 1; |
| for(;;){ |
| if(setjmp(zjmp)){ |
| free(zh.file); |
| return 0; |
| } |
| |
| memset(&zh, 0, sizeof(zh)); |
| if(!header(bin, &zh)) |
| return ok; |
| |
| hcrc = zh.crc; |
| hcsize = zh.csize; |
| huncsize = zh.uncsize; |
| |
| wlen = 0; |
| rlen = 0; |
| crc = 0; |
| wbad = 0; |
| |
| if(zh.meth == 0){ |
| if(!copyout(-1, bin, zh.csize)) |
| error("reading data for %s failed: %r", zh.file); |
| }else if(zh.meth == 8){ |
| off = Boffset(bin); |
| err = inflate((void*)-1, crcwrite, bin, (int(*)(void*))Bgetc); |
| if(err != FlateOk) |
| error("inflate %s failed: %s", zh.file, flateerr(err)); |
| rlen = Boffset(bin) - off; |
| }else |
| error("can't handle compression method %d for %s", zh.meth, zh.file); |
| |
| trailer(bin, &zh); |
| |
| if(wantFile(zh.file)){ |
| if(verbose) |
| print("%-32s %10lud %s", zh.file, zh.uncsize, ctime(msdos2time(zh.modtime, zh.moddate))); |
| else |
| print("%s\n", zh.file); |
| |
| if(verbose > 1){ |
| print("\textract by os %d vers %d.%d\n", zh.extos, zh.extvers / 10, zh.extvers % 10); |
| print("\tflags %x\n", zh.flags); |
| print("\tmethod %d\n", zh.meth); |
| print("\tmod time %d\n", zh.modtime); |
| print("\tmod date %d\n", zh.moddate); |
| print("\tcrc %lux\n", zh.crc); |
| print("\tcompressed size %lud\n", zh.csize); |
| print("\tuncompressed size %lud\n", zh.uncsize); |
| if((zh.flags & ZTrailInfo) && (hcrc || hcsize || huncsize)){ |
| print("\theader crc %lux\n", zh.crc); |
| print("\theader compressed size %lud\n", zh.csize); |
| print("\theader uncompressed size %lud\n", zh.uncsize); |
| } |
| } |
| } |
| |
| if(zh.crc != crc) |
| error("crc mismatch for %s", zh.file); |
| if(zh.uncsize != wlen) |
| error("output size mismatch for %s", zh.file); |
| if(zh.csize != rlen) |
| error("input size mismatch for %s", zh.file); |
| |
| |
| free(zh.file); |
| zh.file = nil; |
| } |
| } |
| |
| /* |
| * extract files using the info in the central directory structure |
| */ |
| static int |
| unzip(Biobuf *bin, char *file) |
| { |
| ZipHead zh; |
| vlong off; |
| int volatile ok, eok, entries; |
| |
| entries = findCDir(bin, file); |
| if(entries < 0) |
| return 0; |
| |
| ok = 1; |
| while(entries-- > 0){ |
| if(setjmp(zjmp)){ |
| free(zh.file); |
| return 0; |
| } |
| memset(&zh, 0, sizeof(zh)); |
| if(!cheader(bin, &zh)) |
| return ok; |
| |
| |
| off = Boffset(bin); |
| if(wantFile(zh.file)){ |
| if(Bseek(bin, zh.off, 0) < 0){ |
| fprint(2, "unzip: can't seek to start of %s, skipping\n", zh.file); |
| ok = 0; |
| }else{ |
| eok = unzipEntry(bin, &zh); |
| if(eok <= 0){ |
| fprint(2, "unzip: skipping %s\n", zh.file); |
| ok = 0; |
| } |
| } |
| } |
| |
| free(zh.file); |
| zh.file = nil; |
| |
| if(Bseek(bin, off, 0) < 0){ |
| fprint(2, "unzip: can't seek to start of next entry, terminating extraction\n"); |
| return 0; |
| } |
| } |
| |
| return ok; |
| } |
| |
| /* |
| * extract files using the info the "local file headers" |
| */ |
| static int |
| sunzip(Biobuf *bin) |
| { |
| int eok; |
| |
| for(;;){ |
| eok = unzipEntry(bin, nil); |
| if(eok == 0) |
| return 1; |
| if(eok < 0) |
| return 0; |
| } |
| } |
| |
| static int |
| makedir(char *s) |
| { |
| int f; |
| |
| if (access(s, AEXIST) == 0) |
| return -1; |
| f = create(s, OREAD, DMDIR | 0777); |
| if (f >= 0) |
| close(f); |
| return f; |
| } |
| |
| static void |
| mkpdirs(char *s) |
| { |
| int done = 0; |
| char *p = s; |
| |
| while (!done && (p = strchr(p + 1, '/')) != nil) { |
| *p = '\0'; |
| done = (access(s, AEXIST) < 0 && makedir(s) < 0); |
| *p = '/'; |
| } |
| } |
| |
| /* |
| * extracts a single entry from a zip file |
| * czh is the optional corresponding central directory entry |
| */ |
| static int |
| unzipEntry(Biobuf *bin, ZipHead *czh) |
| { |
| Dir *d; |
| ZipHead zh; |
| char *p; |
| vlong off; |
| int fd, isdir, ok, err; |
| |
| zh.file = nil; |
| if(setjmp(zjmp)){ |
| delfile = nil; |
| free(zh.file); |
| return -1; |
| } |
| |
| memset(&zh, 0, sizeof(zh)); |
| if(!header(bin, &zh)) |
| return 0; |
| |
| ok = 1; |
| isdir = 0; |
| |
| fd = -1; |
| if(wantFile(zh.file)){ |
| if(verbose) |
| fprint(2, "extracting %s\n", zh.file); |
| |
| if(czh != nil && czh->extos == ZDos){ |
| isdir = czh->eattr & ZDDir; |
| if(isdir && zh.uncsize != 0) |
| fprint(2, "unzip: ignoring directory data for %s\n", zh.file); |
| } |
| if(zh.meth == 0 && zh.uncsize == 0){ |
| p = strchr(zh.file, '\0'); |
| if(p > zh.file && p[-1] == '/') |
| isdir = 1; |
| } |
| |
| if(stdout){ |
| if(ok && !isdir) |
| fd = 1; |
| }else if(isdir){ |
| fd = create(zh.file, OREAD, DMDIR | 0775); |
| if(fd < 0){ |
| mkpdirs(zh.file); |
| fd = create(zh.file, OREAD, DMDIR | 0775); |
| } |
| if(fd < 0){ |
| d = dirstat(zh.file); |
| if(d == nil || (d->mode & DMDIR) != DMDIR){ |
| fprint(2, "unzip: can't create directory %s: %r\n", zh.file); |
| ok = 0; |
| } |
| free(d); |
| } |
| }else if(ok){ |
| fd = create(zh.file, OWRITE, 0664); |
| if(fd < 0){ |
| mkpdirs(zh.file); |
| fd = create(zh.file, OWRITE, 0664); |
| } |
| if(fd < 0){ |
| fprint(2, "unzip: can't create %s: %r\n", zh.file); |
| ok = 0; |
| }else |
| delfile = zh.file; |
| } |
| } |
| |
| wlen = 0; |
| rlen = 0; |
| crc = 0; |
| wbad = 0; |
| |
| if(zh.meth == 0){ |
| if(!copyout(fd, bin, zh.csize)) |
| error("copying data for %s failed: %r", zh.file); |
| }else if(zh.meth == 8){ |
| off = Boffset(bin); |
| err = inflate((void*)(uintptr)fd, crcwrite, bin, (int(*)(void*))Bgetc); |
| if(err != FlateOk) |
| error("inflate failed: %s", flateerr(err)); |
| rlen = Boffset(bin) - off; |
| }else |
| error("can't handle compression method %d for %s", zh.meth, zh.file); |
| |
| trailer(bin, &zh); |
| |
| if(zh.crc != crc) |
| error("crc mismatch for %s", zh.file); |
| if(zh.uncsize != wlen) |
| error("output size mismatch for %s", zh.file); |
| if(zh.csize != rlen) |
| error("input size mismatch for %s", zh.file); |
| |
| delfile = nil; |
| free(zh.file); |
| |
| if(fd >= 0 && !stdout){ |
| if(settimes){ |
| d = dirfstat(fd); |
| if(d != nil){ |
| d->mtime = msdos2time(zh.modtime, zh.moddate); |
| if(d->mtime) |
| dirfwstat(fd, d); |
| } |
| } |
| close(fd); |
| } |
| |
| return ok; |
| } |
| |
| static int |
| wantFile(char *file) |
| { |
| int i, n; |
| |
| if(nwant == 0) |
| return 1; |
| for(i = 0; i < nwant; i++){ |
| if(strcmp(want[i], file) == 0) |
| return 1; |
| n = strlen(want[i]); |
| if(strncmp(want[i], file, n) == 0 && file[n] == '/') |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* |
| * find the start of the central directory |
| * returns the number of entries in the directory, |
| * or -1 if there was an error |
| */ |
| static int |
| findCDir(Biobuf *bin, char *file) |
| { |
| vlong ecoff; |
| long off, size; |
| int entries, zclen, dn, ds, de; |
| |
| ecoff = Bseek(bin, -ZECHeadSize, 2); |
| if(ecoff < 0){ |
| fprint(2, "unzip: can't seek to contents of %s; try adding -s\n", file); |
| return -1; |
| } |
| if(setjmp(zjmp)) |
| return -1; |
| |
| if(get4(bin) != ZECHeader){ |
| fprint(2, "unzip: bad magic number for contents of %s\n", file); |
| return -1; |
| } |
| dn = get2(bin); |
| ds = get2(bin); |
| de = get2(bin); |
| entries = get2(bin); |
| size = get4(bin); |
| off = get4(bin); |
| zclen = get2(bin); |
| while(zclen-- > 0) |
| get1(bin); |
| |
| if(verbose > 1){ |
| print("table starts at %ld for %ld bytes\n", off, size); |
| if(ecoff - size != off) |
| print("\ttable should start at %lld-%ld=%lld\n", ecoff, size, ecoff-size); |
| if(dn || ds || de != entries) |
| print("\tcurrent disk=%d start disk=%d table entries on this disk=%d\n", dn, ds, de); |
| } |
| |
| if(Bseek(bin, off, 0) != off){ |
| fprint(2, "unzip: can't seek to start of contents of %s\n", file); |
| return -1; |
| } |
| |
| return entries; |
| } |
| |
| static int |
| cheader(Biobuf *bin, ZipHead *zh) |
| { |
| u32int v; |
| int flen, xlen, fclen; |
| |
| v = get4(bin); |
| if(v != ZCHeader){ |
| if(v == ZECHeader) |
| return 0; |
| error("bad magic number %lux", v); |
| } |
| zh->madevers = get1(bin); |
| zh->madeos = get1(bin); |
| zh->extvers = get1(bin); |
| zh->extos = get1(bin); |
| zh->flags = get2(bin); |
| zh->meth = get2(bin); |
| zh->modtime = get2(bin); |
| zh->moddate = get2(bin); |
| zh->crc = get4(bin); |
| zh->csize = get4(bin); |
| zh->uncsize = get4(bin); |
| flen = get2(bin); |
| xlen = get2(bin); |
| fclen = get2(bin); |
| get2(bin); /* disk number start */ |
| zh->iattr = get2(bin); |
| zh->eattr = get4(bin); |
| zh->off = get4(bin); |
| |
| zh->file = getname(bin, flen); |
| |
| while(xlen-- > 0) |
| get1(bin); |
| |
| while(fclen-- > 0) |
| get1(bin); |
| |
| return 1; |
| } |
| |
| static int |
| header(Biobuf *bin, ZipHead *zh) |
| { |
| u32int v; |
| int flen, xlen; |
| |
| v = get4(bin); |
| if(v != ZHeader){ |
| if(v == ZCHeader) |
| return 0; |
| error("bad magic number %lux at %lld", v, Boffset(bin)-4); |
| } |
| zh->extvers = get1(bin); |
| zh->extos = get1(bin); |
| zh->flags = get2(bin); |
| zh->meth = get2(bin); |
| zh->modtime = get2(bin); |
| zh->moddate = get2(bin); |
| zh->crc = get4(bin); |
| zh->csize = get4(bin); |
| zh->uncsize = get4(bin); |
| flen = get2(bin); |
| xlen = get2(bin); |
| |
| zh->file = getname(bin, flen); |
| |
| while(xlen-- > 0) |
| get1(bin); |
| |
| return 1; |
| } |
| |
| static void |
| trailer(Biobuf *bin, ZipHead *zh) |
| { |
| if(zh->flags & ZTrailInfo){ |
| zh->crc = get4(bin); |
| zh->csize = get4(bin); |
| zh->uncsize = get4(bin); |
| } |
| } |
| |
| static char* |
| getname(Biobuf *bin, int len) |
| { |
| char *s; |
| int i, c; |
| |
| s = emalloc(len + 1); |
| for(i = 0; i < len; i++){ |
| c = get1(bin); |
| if(lower) |
| c = tolower(c); |
| s[i] = c; |
| } |
| s[i] = '\0'; |
| return s; |
| } |
| |
| static int |
| crcwrite(void *out, void *buf, int n) |
| { |
| int fd, nw; |
| |
| wlen += n; |
| crc = blockcrc(crctab, crc, buf, n); |
| fd = (int)(uintptr)out; |
| if(fd < 0) |
| return n; |
| nw = write(fd, buf, n); |
| if(nw != n) |
| wbad = 1; |
| return nw; |
| } |
| |
| static int |
| copyout(int ofd, Biobuf *bin, long len) |
| { |
| char buf[BufSize]; |
| int n; |
| |
| for(; len > 0; len -= n){ |
| n = len; |
| if(n > BufSize) |
| n = BufSize; |
| n = Bread(bin, buf, n); |
| if(n <= 0) |
| return 0; |
| rlen += n; |
| if(crcwrite((void*)(uintptr)ofd, buf, n) != n) |
| return 0; |
| } |
| return 1; |
| } |
| |
| static u32int |
| get4(Biobuf *b) |
| { |
| u32int v; |
| int i, c; |
| |
| v = 0; |
| for(i = 0; i < 4; i++){ |
| c = Bgetc(b); |
| if(c < 0) |
| error("unexpected eof reading file information"); |
| v |= c << (i * 8); |
| } |
| return v; |
| } |
| |
| static int |
| get2(Biobuf *b) |
| { |
| int i, c, v; |
| |
| v = 0; |
| for(i = 0; i < 2; i++){ |
| c = Bgetc(b); |
| if(c < 0) |
| error("unexpected eof reading file information"); |
| v |= c << (i * 8); |
| } |
| return v; |
| } |
| |
| static int |
| get1(Biobuf *b) |
| { |
| int c; |
| |
| c = Bgetc(b); |
| if(c < 0) |
| error("unexpected eof reading file information"); |
| return c; |
| } |
| |
| static long |
| msdos2time(int time, int date) |
| { |
| Tm tm; |
| |
| tm.hour = time >> 11; |
| tm.min = (time >> 5) & 63; |
| tm.sec = (time & 31) << 1; |
| tm.year = 80 + (date >> 9); |
| tm.mon = ((date >> 5) & 15) - 1; |
| tm.mday = date & 31; |
| tm.zone[0] = '\0'; |
| tm.yday = 0; |
| |
| return tm2sec(&tm); |
| } |
| |
| static void* |
| emalloc(u32int n) |
| { |
| void *p; |
| |
| p = malloc(n); |
| if(p == nil) |
| sysfatal("out of memory"); |
| return p; |
| } |
| |
| static void |
| error(char *fmt, ...) |
| { |
| va_list arg; |
| |
| fprint(2, "unzip: "); |
| va_start(arg, fmt); |
| vfprint(2, fmt, arg); |
| va_end(arg); |
| fprint(2, "\n"); |
| |
| if(delfile != nil){ |
| fprint(2, "unzip: removing output file %s\n", delfile); |
| remove(delfile); |
| delfile = nil; |
| } |
| |
| longjmp(zjmp, 1); |
| } |