/* pptfs.c © Steve Simon 2007 */ #include #include #include #include #include #include #include #include <9p.h> enum { PPTMAGIC = 0xe391c05f, Isdir = 0x0f }; typedef struct { vlong base; // start of record in file int cont; // is 0x0f if its a container int opts; // record options int type; // record type int len; // length of current record int got; // remaining record data } Hdr; typedef struct { int num; // slide number ? vlong off; // file offset } Blk; typedef struct { int bullet; // true if lines have bullets char allign; // l=left, r=right, c=center } Para; typedef struct { char *type; int len; int off; } Img; typedef struct { int size; void *table; int alloc; int used; } Tab; typedef struct { int slide; int blip; } Xref; Tab Imgtab = { sizeof(Img) }; // Blip Images (in order found) Tab Blktab = { sizeof(Blk) }; // edited block of slides (sorted by slide ID) Tab Xreftab = { sizeof(Xref) }; // maps blips to slides int Slideno = -1; // current slide number int Margin = 1; // am at the margin int Txtype = -1; // type of current text (title, body etc) vlong Start; // first edited slide chunk Fmt *Fp; int Debug = 0; Biobuf *Bi = nil; int parse(vlong off, int len, int indent); typedef struct { char *data; int len; } Vfile; struct { char *s; int n; } Types[] = { { "Unknown", 0 }, { "Document", 1000 }, { "Document Atom", 1001 }, { "End Document", 1002 }, { "Slide", 1006 }, { "Slide Atom", 1007 }, { "Notes", 1008 }, { "Notes Atom", 1009 }, { "Environment", 1010 }, { "Slide Persist Atom", 1011 }, { "S Slide Layout Atom", 1015 }, { "Main Master", 1016 }, { "SS Slide Info Atom", 1017 }, { "Slide View Info", 1018 }, { "Guide Atom", 1019 }, { "View Info", 1020 }, { "View Info Atom", 1021 }, { "Slide View Info Atom", 1022 }, { "VBA Info", 1023 }, { "VBA Info Atom", 1024 }, { "SS Doc Info Atom", 1025 }, { "Summary", 1026 }, { "Doc Routing Slip", 1030 }, { "Outline View Info", 1031 }, { "Sorter View Info", 1032 }, { "Ex Obj List", 1033 }, { "Ex Obj List Atom", 1034 }, { "PP Drawing Group", 1035 }, { "PP Drawing", 1036 }, // Escher container { "Named Shows", 1040 }, { "Named Show", 1041 }, { "Named Show Slides", 1042 }, { "List", 2000 }, { "Font Collection", 2005 }, { "Bookmark Collection", 2019 }, { "Sound Coll Atom", 2021 }, { "Sound", 2022 }, { "Sound Data", 2023 }, { "Bookmark Seed Atom", 2025 }, { "Color Scheme Atom", 2032 }, { "Ex Obj Xref Atom", 3009 }, { "OE Shape Atom", 3009 }, { "OE Placeholder Atom", 3011 }, { "G Point Atom", 3024 }, { "G Ratio Atom", 3031 }, { "Outline Text Xref Atom", 3998 }, { "Text Header Atom", 3999 }, { "Text Chars Atom", 4000 }, { "Style Text Prop Atom", 4001 }, { "Base Text Prop Atom", 4002 }, { "Tx Master Style Atom", 4003 }, { "Tx CF Style Atom", 4004 }, { "Tx PF Style Atom", 4005 }, { "Text Ruler Atom", 4006 }, { "Text Bookmark Atom", 4007 }, { "Text Bytes Atom", 4008 }, { "Tx SI Style Atom", 4009 }, { "Text Spec Info Atom", 4010 }, { "Default Ruler Atom", 4011 }, { "Font Entity Atom", 4023 }, { "Font Embedded Data", 4024 }, { "C String", 4026 }, { "Meta File", 4033 }, { "Ex Ole Obj Atom", 4035 }, { "Sr Kinsoku", 4040 }, { "Hand Out", 4041 }, { "Ex Embed", 4044 }, { "Ex Embed Atom", 4045 }, { "Ex Link", 4046 }, { "Bookmark Entity Atom", 4048 }, { "Ex Link Atom", 4049 }, { "Sr Kinsoku Atom", 4050 }, { "Ex Hyperlink Atom", 4051 }, { "Ex Hyperlink", 4055 }, { "Slide Number MC Atom", 4056 }, { "Headers Footers", 4057 }, { "Headers Footers Atom", 4058 }, { "Tx Interactive Info Atom", 4063 }, { "Char Format Atom", 4066 }, { "Para Format Atom", 4067 }, { "Recolor Info Atom", 4071 }, { "Ex Quick Time Movie", 4074 }, { "Ex Quick Time Movie Data", 4075 }, { "Ex Control", 4078 }, { "Slide List With Text", 4080 }, { "Interactive Info", 4082 }, { "Interactive Info Atom", 4083 }, { "User Edit Atom", 4085 }, { "Current User Atom", 4086 }, { "Date Time MC Atom", 4087 }, { "Generic Date MC Atom", 4088 }, { "Footer MC Atom", 4090 }, { "Ex Control Atom", 4091 }, { "Ex Media Atom", 4100 }, { "Ex Video", 4101 }, { "Ex Avi Movie", 4102 }, { "Ex MCI Movie", 4103 }, { "Ex MIDI Audio", 4109 }, { "Ex CD Audio", 4110 }, { "Ex WAV Audio Embedded", 4111 }, { "Ex WAV Audio Link", 4112 }, { "Ex Ole Obj Stg", 4113 }, { "Ex CD Audio Atom", 4114 }, { "Ex WAV Audio Embedded Atom", 4115 }, { "Animation Info Atom", 4116 }, { "RTF Date Time MC Atom", 4117 }, { "Prog Tags", 5000 }, { "Prog String Tag", 5001 }, { "Prog Binary Tag", 5002 }, { "Binary Tag Data", 5003 }, { "Print Options", 6000 }, { "Persist Ptr Full Block", 6001 }, { "Persist Ptr Inc Block", 6002 }, { "G Scaling Atom", 10001 }, { "GR Color Atom", 10002 }, { "Escher Dgg Container", 0xf000 }, { "Escher Dgg", 0xf006 }, { "Escher CLSID", 0xf016 }, { "Escher OPT", 0xf00b }, { "Escher BStore Container", 0xf001 }, { "Escher BSE", 0xf007 }, { "Escher Blip first", 0xf018 }, { "Escher Blip unknown", 0xf019 }, { "Escher Blip EMF", 0xf01a }, { "Escher Blip WMF", 0xf01b }, { "Escher Blip PICT", 0xf01c }, { "Escher Blip JPEG", 0xf01d }, { "Escher Blip PNG", 0xf01e }, { "Escher Blip DIB", 0xf01f }, { "Escher Blip last", 0xf117 }, { "Escher Dg Container", 0xf002 }, { "Escher Dg", 0xf008 }, { "Escher Regroup Items", 0xf118 }, { "Escher Color Scheme", 0xf120 }, /* bug in docs */ { "Escher Spgr Container", 0xf003 }, { "Escher Sp Container", 0xf004 }, { "Escher Spgr", 0xf009 }, { "Escher Sp", 0xf00a }, { "Escher Textbox", 0xf00c }, { "Escher Client Textbox", 0xf00d }, { "Escher Anchor", 0xf00e }, { "Escher Child Anchor", 0xf00f }, { "Escher Client Anchor", 0xf010 }, { "Escher Client Data", 0xf011 }, { "Escher Solver Container", 0xf005 }, { "Escher Connector Rule", 0xf012 } , /* bug in docs */ { "Escher Align Rule", 0xf013 }, { "Escher Arc Rule", 0xf014 }, { "Escher Client Rule", 0xf015 }, { "Escher Callout Rule", 0xf017 }, { "Escher Selection", 0xf119 }, { "Escher Color MRU", 0xf11a }, { "Escher Deleted Pspl", 0xf11d } , /* bug in docs */ { "Escher Split Menu Colors", 0xf11e }, { "Escher Ole Object", 0xf11f }, { "Escher User Defined", 0xf122 }, }; void fsread(Req *); Srv Fs = { .read = fsread, }; void pr(char *fmt, ...) { va_list args; va_start(args, fmt); if (Debug) vfprint(1, fmt, args); fmtvprint(Fp, fmt, args); va_end(args); } void * growtab(Tab *t) { if(t->alloc >= t->used){ t->alloc += 8; t->table = realloc(t->table, t->alloc * t->size); if(t->table == nil) sysfatal("no memory - %r"); } return (char *)t->table + t->used++ * t->size; } void * indextab(Tab *t, int idx) { if (idx < 0 || idx >= t->used) return nil; return (char *)t->table + idx * t->size; } int mkfile(char *name, char *data, int len) { int seq; char *p; Vfile *vf; File *f, *nf; char s[128]; vf = emalloc9p(sizeof(Vfile)); vf->data = data; vf->len = len; f = Fs.tree->root; incref(f); while(f && (p = strchr(name, '/'))) { *p = '\0'; if(strcmp(name, "") != 0 && strcmp(name, ".") != 0){ /* this would be a race if we were multithreaded */ incref(f); /* so walk doesn't kill it immediately on failure */ if((nf = walkfile(f, name)) == nil) nf = createfile(f, name, "bill", DMDIR|0755, nil); decref(f); f = nf; } *p = '/'; name = p+1; } if(f == nil){ free(vf->data); free(vf); return -1; } incref(f); seq = 0; snprint(s, sizeof(s), "%s", name); while((nf = walkfile(f, s)) != nil){ f = walkfile(nf, ".."); snprint(s, sizeof(s), "%s.%d", name, seq++); } nf = createfile(f, s, "bill", 0644, vf); decref(f); nf->length = len; return 0; } char * rectype(int type) { int i; static char buf[32]; for (i = 0; i < nelem(Types); i++) if(type == Types[i].n) return Types[i].s; snprint(buf, nelem(buf), "0x%x", type); return buf; } void sheet(int num, vlong off) { int i; Blk *bk; for(i = 0; (bk = indextab(&Blktab, i)); i++) if(bk->num == num) return; bk = growtab(&Blktab); bk->num = num; bk->off = off; } int cmp(void *a, void *b) { return ((Blk *)a)->num - ((Blk *)b)->num; } void xd(Hdr *h) { vlong off; uchar buf[16]; int addr, got, n, i, j; addr = 0; off = Boffset(Bi); while (addr < h->len){ n = (h->len >= sizeof(buf))? sizeof(buf): h->len; got = Bread(Bi, buf, n); print(" %6d ", addr); addr += n; for (i = 0; i < got; i++) print("%02x ", buf[i]); for (j = i; j < 16; j++) print(" "); print(" "); for (i = 0; i < got; i++) print("%c", isprint(buf[i])? buf[i]: '.'); print("\n"); } Bseek(Bi, off, 0); } vlong gint(Hdr *h, int n) { int i, c; uvlong vl, rc; assert(n <= h->got); rc = 0; for (i = 0; i < n; i++){ if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->got--; vl = c; rc |= vl << (8*i); } return rc; } static int getrec(Hdr *h, int indent) { int c; h->base = Boffset(Bi); if ((c = Bgetc(Bi)) == -1) return -1; // real EOF h->cont = c & 0x0f; h->opts = (c >> 4) & 0x0f; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->opts |= c << 4; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->type = c; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->type |= c << 8; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->got = c; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->got |= c << 8; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->got |= c << 16; if ((c = Bgetc(Bi)) == -1) sysfatal("unexpected EOF - %r\n"); h->got |= c << 24; h->len = h->got +8; if (Debug > 1){ print("%*.3s%s off=%-6lld len=%-6d %d=0x%x=%s\n", indent, "", (h->cont == Isdir)? "cont": "atom", h->base, h->len+8, h->type, h->type, rectype(h->type)); if (Debug > 2 && h->cont != Isdir) xd(h); } return 0; } void skip(Hdr *h, vlong n) { assert(n <= h->got); if (Bseek(Bi, n, 1) == -1) sysfatal("seek failed - %r\n"); h->got -= n; } void putrune(Rune r, int bullet) { static int em = 0; if (Margin && bullet && r != L'\r' && r != L'\n'){ pr("\t• "); Margin = 0; } switch(r){ case L'\n': case L'\r': pr("\n"); Margin = 1; break; case L'': em = 1; break; case L'': // FIXME: guesswork pr(" "); break; case L'': pr("``"); break; case L'': pr("''"); break; case L'�': pr("£"); break; case L' ': if (Debug) print("\t"); fmtprint(Fp, "\t"); break; case L' ': if (em) pr("'"); else pr(" "); em = 0; break; case 0: // ignore embedded nulls break; default: pr("%C", r); break; } } void gmem(Hdr *h, void *v, int n) { assert(n <= h->got); if (Bread(Bi, v, n) != n) sysfatal("unexpected EOF - %r\n"); h->got -= n; } /**********************************************/ void textcharsatom(Hdr *h, int bullet) { Rune r; Margin = 1; do { r = Bgetrune(Bi); h->got -= runelen(r); putrune(r, bullet); } while(h->got); pr("\n"); } void textbytesatom(Hdr *h, int bullet) { int i; Rune r; char buf[UTFmax]; Margin = 1; do { i = Bgetc(Bi); buf[0] = i; h->got--; chartorune(&r, buf); putrune(r, bullet); } while(h->got); pr("\n"); } void cstring(Hdr *h, int bullet) { Rune r; Margin = 1; do { r = Bgetrune(Bi); h->got -= runelen(r); putrune(r, bullet); } while(h->got); pr("\n"); } void persistincrement(Hdr *h) { uint x; int i, num, off, first; do { x = gint(h, 4); first = x & 0xfffff; num = (x >> 20)&0xfff; for(i = 0; i < num; i++){ off = gint(h, 4); sheet(first+i, off); } } while(h->got); } void useredit(Hdr *h, int indent) { vlong was; int lastedit, persistdir; skip(h, 4); // last slide viewed skip(h, 4); // major ver of app lastedit = gint(h, 4); // previous user edit persistdir = gint(h, 4); // directory skip(h, h->got); was = Boffset(Bi); if(lastedit) parse(lastedit, 0, indent+1); if(persistdir) parse(persistdir, 0, indent+1); Bseek(Bi, was, 0); } /* * This record is special, it occurs only * in the CurrentUser OLE file where it is the sole record. * It gives detail of the offset to _start_ walking the UserEdit * records from in the main PowerPointDocument OLE file. */ void currentuser(Hdr *h) { int n; char *buf; skip(h, 4); // size of this rec if(gint(h, 4) != PPTMAGIC) sysfatal("bad magic number - not a powerpoint file\n"); Start = gint(h, 4); n = gint(h, 2); // usr name len buf = malloc(n+2); skip(h, 4); // file version skip(h, 1); // app major version skip(h, 1); // app minor version gmem(h, buf, n); // user who edited buf[n] = '\n'; buf[n+1] = 0; mkfile("/ppt/author", buf, n+1); skip(h, h->got); } void textheaderatom(Hdr *h, int *type) { *type = gint(h, 4); // text block type if(Debug) print("#text-type: %d\n", *type); } void blipstoreentry(Hdr *h) { Img *i; int m, w; char *types[] = { "err", "unk", "emf.gz", "wmf.gz", "pict.gz", "jpeg", "png", "dbi" }; i = growtab(&Imgtab); w = gint(h, 1); m = gint(h, 1); if (w > 1 && w < nelem(types)) i->type = types[w]; else if (m >= 0 && m < nelem(types)) i->type = types[m]; else i->type = "bad"; skip(h, 16); // md4 of blip skip(h, 2); // tag (unused) i->len = gint(h, 4); gint(h, 4); // ref count (unused ?) i->off = gint(h, 4); skip(h, h->got); // don't care about the rest } void slide(Hdr *h) { char places[8]; int flags, geo, master, id; geo = gint(h, 4); // geometry gmem(h, places, sizeof(places)); master = gint(h, 4); // master of this slide of 0 if is a master id = gint(h, 4); // id of this slide flags = gint(h, 2); // master follow flags if (Debug) print("#slide: master-id=%d geo=%d flags=%x id=%d/%x\n", master, geo, flags, id, id); skip(h, h->got); // don't care about the rest Slideno = id; } void slidepersist(Hdr *h) { int ref, shapes, ntexts, id; ref = gint(h, 4); // slide ref shapes = gint(h, 4); // has shapes ntexts = gint(h, 4); // num texts id = gint(h, 4); // slide ID if (Debug) print("#slidepersist: ref=%d shapes=%d ntexts=%d id=%d/%x\n", ref, shapes, ntexts, id, id); skip(h, h->got); // don't care about the rest Slideno = id; } void notes(Hdr *h) { int flags, id; id = gint(h, 4); // slide ID these notes relate to flags = gint(h, 2); // master follow flags if (Debug) print("#notes: flags=%x id=%d/%x\n", flags, id, id); skip(h, h->got); // don't care about the rest Slideno = id; } void escheropt(Hdr *h) { Xref *rf; int x, blip, off, val, id; off = 0; blip = -1; do{ x = gint(h, 2); id = x & 0x3fff; val = gint(h, 4); if(id == 260) blip = val; if((x & 0x8000) != 0) // complex value off += val; }while(h->got >= off+6); skip(h, h->got); // don't care about the rest if (blip == -1) // only uninteresting info return; rf = growtab(&Xreftab); rf->blip = blip; rf->slide = Slideno; if (Debug) print("#escheropt: blip=%d slide=%d\n", blip-1, Slideno); } void pushimage(Hdr *h, char *ext) { int n; char *p, buf[64]; static int blipno = 1; skip(h, 16); // md4 sum of image skip(h, 1); // tag (seems always to be 0xff) n = h->got; p = emalloc9p(n); gmem(h, p, n); if (Slideno == 0) snprint(buf, sizeof(buf), "/ppt/master/image.%s", ext); else snprint(buf, sizeof(buf), "/ppt/slide%d/image.%s", Slideno, ext); mkfile(buf, p, n); if(Debug) print("#blip type=%s len=%d\n", ext, n); } void pushtext(int type) { int n; char *p, buf[64]; char *types[] = { "title", "body", "notes", "unused", "other", "centered-body", "centered-title", "half-body", "quater-body" }; p = fmtstrflush(Fp); if (p == nil || *p == 0 || type < 0 || type >= nelem(types) || Slideno < 0){ free(p); fmtstrinit(Fp); return; } n = strlen(p); if (Slideno == 0) snprint(buf, sizeof(buf), "/ppt/master/%s", types[type]); else snprint(buf, sizeof(buf), "/ppt/slide%d/%s", Slideno, types[type]); mkfile(buf, p, n); fmtstrinit(Fp); if(Debug) print("#text name=%s len=%d\n#%s\n", buf, n, p); } int decode(Hdr *h, int indent) { int bullet; static int type; /* - Slide ID is really in the Slide Persist Atom unless the slide is from the Template - The slide sequence is listed under the latest 1000 record under 4080 and the sequence of 1011 shows the list slides ... There is no real Slide number that I can find. */ bullet = (type != 0 && type != 6); switch (h->type){ case 1011: slidepersist(h); break; case 1007: slide(h); break; case 1009: notes(h); break; case 3999: textheaderatom(h, &type); break; case 4000: textcharsatom(h, bullet); pushtext(type); break; case 4008: textbytesatom(h, bullet); pushtext(type); break; case 4026: cstring(h, bullet); pushtext(type); break; case 4085: useredit(h, indent+1); break; case 4086: currentuser(h); break; case 6002: persistincrement(h); break; case 0xf00b: escheropt(h); break; case 0xf01a: pushimage(h, "emf.gz"); break; case 0xf01b: pushimage(h, "wmf.gz"); break; case 0xf01c: pushimage(h, "pict.gz"); break; case 0xf01d: pushimage(h, "jpg"); break; case 0xf01e: pushimage(h, "png"); break; case 0xf007: blipstoreentry(h); break; default: if (h->cont == Isdir) return parse(-1, h->len, indent+1) +8; skip(h, h->got); break; } return h->len; } int parse(vlong off, int len, int indent) { Hdr h; int got; if (off != -1 && Bseek(Bi, off, 0) != off) sysfatal("seek failed"); got = 0; do { if(getrec(&h, indent) == -1) return -1; got += decode(&h, indent); } while((len > 0 && got < len-9) || (len == 0 && got < h.got)); return got; } void fsread(Req *r) { Vfile *vf; vlong offset; long count; vf = r->fid->file->aux; offset = r->ifcall.offset; count = r->ifcall.count; if(offset >= vf->len){ r->ofcall.count = 0; respond(r, nil); return; } if(offset+count >= vf->len) count = vf->len - offset; memmove(r->ofcall.data, vf->data+offset, count); r->ofcall.count = count; respond(r, nil); } void usage(void) { fmtprint(Fp, "usage: %s [-ba] [-Dd] [-s srvname] [-m mtpt] [/mnt/doc/Current␣User " "/mnt/doc/PowerPoint␣Document /mnt/doc/Pictures]\n", argv0); exits("usage"); } void main(int argc, char **argv) { Qid q; Img *im; Blk *bk; Xref *rf; Fmt fmt; int i, j, flags; char *current, *document, *pictures, *srvname, *mtpt; Fs.tree = alloctree("bill", "trog", DMDIR|0755, nil); q = Fs.tree->root->qid; Fp = &fmt; mtpt = nil; flags = 0; srvname = nil; current="/mnt/doc/Current␣User"; document="/mnt/doc/PowerPoint␣Document"; pictures="/mnt/doc/Pictures"; ARGBEGIN{ case 'D': chatty9p++; break; case 'd': Debug++; break; case 'b': flags |= MBEFORE; break; case 'a': flags |= MAFTER; break; case 's': srvname = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; default: usage(); }ARGEND; if (argc != 0 && argc != 3) usage(); fmtstrinit(Fp); if (Debug) print("\nfile=%s\n", current); if ((Bi = Bopen(current, OREAD)) == nil) sysfatal("%s cannot open\n", current); parse(0, 0, 0); Bterm(Bi); if (Debug) print("\nfile=%s\n", document); if ((Bi = Bopen(document, OREAD)) == nil) sysfatal("%s cannot open\n", document); parse(Start, 0, 0); qsort(Blktab.table, Blktab.used, Blktab.size, cmp); for(i = 0; (bk = indextab(&Blktab, i)); i++) parse(bk->off, 0, 0); Bterm(Bi); if (Debug) print("\nfile=%s\n", pictures); if ((Bi = Bopen(pictures, OREAD)) != nil){ for(i = 0; (im = indextab(&Imgtab, i)) != nil; i++) for(j = 0; (rf = indextab(&Xreftab, j)) != nil; j++){ if (rf->blip -1 == i){ Slideno = rf->slide; parse(im->off, 0, 0); } } Bterm(Bi); } if (mtpt == nil) mtpt = "/mnt/doc/"; if (flags == 0) flags = MBEFORE; postmountsrv(&Fs, srvname, mtpt, flags); exits(0); }