Unified · Split
| @@ -0,0 +1,121 @@ | ||
| 1 | +#include <u.h> | |
| 2 | +#include <libc.h> | |
| 3 | +#include <bio.h> | |
| 4 | +#include "pdf.h" | |
| 5 | + | |
| 6 | +static int | |
| 7 | +bufgrow(Buffer *b, int sz) | |
| 8 | +{ | |
| 9 | + uchar *r; | |
| 10 | + int maxsz; | |
| 11 | + | |
| 12 | + if(b->maxsz < 1){ | |
| 13 | + if((b->b = mallocz(128, 1)) == nil) | |
| 14 | + return -1; | |
| 15 | + b->maxsz = 128; | |
| 16 | + } | |
| 17 | + for(maxsz = b->maxsz; b->sz+sz > maxsz; maxsz *= 2); | |
| 18 | + if(maxsz != b->maxsz){ | |
| 19 | + if((r = realloc(b->b, maxsz)) == nil) | |
| 20 | + return -1; | |
| 21 | + memset(r+b->maxsz, 0, maxsz-b->maxsz); | |
| 22 | + b->b = r; | |
| 23 | + b->maxsz = maxsz; | |
| 24 | + } | |
| 25 | + | |
| 26 | + return 0; | |
| 27 | +} | |
| 28 | + | |
| 29 | +void | |
| 30 | +bufinit(Buffer *b, uchar *d, int sz) | |
| 31 | +{ | |
| 32 | + memset(b, 0, sizeof(*b)); | |
| 33 | + if(d != nil){ | |
| 34 | + b->b = d; | |
| 35 | + b->sz = sz; | |
| 36 | + b->ro = 1; | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +void | |
| 41 | +buffree(Buffer *b) | |
| 42 | +{ | |
| 43 | + if(b->ro == 0) | |
| 44 | + free(b->b); | |
| 45 | +} | |
| 46 | + | |
| 47 | +int | |
| 48 | +bufeof(Buffer *b) | |
| 49 | +{ | |
| 50 | + return b->off == b->sz; | |
| 51 | +} | |
| 52 | + | |
| 53 | +uchar * | |
| 54 | +bufdata(Buffer *b, int *sz) | |
| 55 | +{ | |
| 56 | + *sz = b->sz; | |
| 57 | + return b->b; | |
| 58 | +} | |
| 59 | + | |
| 60 | +int | |
| 61 | +bufreadn(Buffer *b, void *bio, int sz) | |
| 62 | +{ | |
| 63 | + int n, end; | |
| 64 | + | |
| 65 | + if(bufgrow(b, sz) != 0) | |
| 66 | + return -1; | |
| 67 | + for(end = b->sz+sz; b->sz < end; b->sz += n){ | |
| 68 | + if((n = Bread(bio, b->b+b->sz, sz)) < 1) | |
| 69 | + return -1; | |
| 70 | + sz -= n; | |
| 71 | + } | |
| 72 | + return 0; | |
| 73 | +} | |
| 74 | + | |
| 75 | +int | |
| 76 | +bufput(Buffer *b, uchar *d, int sz) | |
| 77 | +{ | |
| 78 | + if(b->ro) | |
| 79 | + sysfatal("bufferput on readonly buffer"); | |
| 80 | + if(bufgrow(b, sz) != 0) | |
| 81 | + return -1; | |
| 82 | + | |
| 83 | + memmove(b->b+b->sz, d, sz); | |
| 84 | + b->sz += sz; | |
| 85 | + | |
| 86 | + return sz; | |
| 87 | +} | |
| 88 | + | |
| 89 | +int | |
| 90 | +bufget(Buffer *b, uchar *d, int sz) | |
| 91 | +{ | |
| 92 | + if(sz == 0) | |
| 93 | + return 0; | |
| 94 | + | |
| 95 | + if(b->off > b->sz) | |
| 96 | + sysfatal("buffer: off(%d) > sz(%d)", b->off, b->sz); | |
| 97 | + if(sz > b->sz - b->off) | |
| 98 | + sz = b->sz - b->off; | |
| 99 | + memmove(d, b->b+b->off, sz); | |
| 100 | + b->off += sz; | |
| 101 | + b->eof = sz == 0; | |
| 102 | + | |
| 103 | + return sz; | |
| 104 | +} | |
| 105 | + | |
| 106 | +void | |
| 107 | +bufdump(Buffer *b) | |
| 108 | +{ | |
| 109 | + Biobuf bio; | |
| 110 | + int i, j; | |
| 111 | + | |
| 112 | + Binit(&bio, 2, OWRITE); | |
| 113 | + Bprint(&bio, "%d bytes:\n", b->sz); | |
| 114 | + for(i = 0; i < b->sz;){ | |
| 115 | + Bprint(&bio, "%04x\t", i); | |
| 116 | + for(j = 0; i < b->sz && j < 16; j++, i++) | |
| 117 | + Bprint(&bio, "%02x%s", b->b[i], (j+1)&7 ? " " : " "); | |
| 118 | + Bprint(&bio, "\n"); | |
| 119 | + } | |
| 120 | + Bterm(&bio); | |
| 121 | +} | |
| @@ -69,7 +69,7 @@ pdfdictget(Object *o, char *name) · 68 unmodified lines | ||
| 69 | 69 | { |
| 70 | 70 | int i; |
| 71 | 71 | |
| 72 | − if(o == nil || o->type != Odict || name == nil) | |
| 72 | + if(o == nil || (o->type != Ostream && o->type != Odict) || name == nil) | |
| 73 | 73 | return nil; |
| 74 | 74 | for(i = 0; i < o->dict.nkv && strcmp(name, o->dict.kv[i].key) != 0; i++); |
| 75 | 75 | |
| @@ -0,0 +1,38 @@ | ||
| 1 | +#include <u.h> | |
| 2 | +#include <libc.h> | |
| 3 | +#include <bio.h> | |
| 4 | +#include <flate.h> | |
| 5 | +#include "pdf.h" | |
| 6 | + | |
| 7 | +static int | |
| 8 | +bw(void *aux, void *d, int n) | |
| 9 | +{ | |
| 10 | + return bufput(aux, d, n); | |
| 11 | +} | |
| 12 | + | |
| 13 | +static int | |
| 14 | +bget(void *aux) | |
| 15 | +{ | |
| 16 | + uchar c; | |
| 17 | + | |
| 18 | + return bufget(aux, &c, 1) == 1 ? c : -1; | |
| 19 | +} | |
| 20 | + | |
| 21 | +int | |
| 22 | +fFlate(void *aux, Buffer *bi, Buffer *bo) | |
| 23 | +{ | |
| 24 | + int r; | |
| 25 | + | |
| 26 | + USED(aux); | |
| 27 | + | |
| 28 | + do{ | |
| 29 | + r = inflatezlib(bo, bw, bi, bget); | |
| 30 | + }while(r == FlateOk && !bufeof(bi)); | |
| 31 | + | |
| 32 | + if(r != FlateOk){ | |
| 33 | + werrstr("%s", flateerr(r)); | |
| 34 | + return -1; | |
| 35 | + } | |
| 36 | + | |
| 37 | + return 0; | |
| 38 | +} | |
| @@ -3,17 +3,72 @@ · 2 unmodified lines | ||
| 3 | 3 | #include <bio.h> |
| 4 | 4 | #include "pdf.h" |
| 5 | 5 | |
| 6 | −/* | |
| 7 | −7.4 | |
| 8 | − | |
| 9 | −ASCIIHex | |
| 10 | −ASCII85 | |
| 11 | −LZW | |
| 12 | −Flate | |
| 13 | −RunLength | |
| 14 | −CCITTFax | |
| 15 | −JBIG2 | |
| 16 | −DCT | |
| 17 | −JPX | |
| 18 | −Crypt | |
| 19 | −*/ | |
| 6 | +/* 7.4 Filters */ | |
| 7 | + | |
| 8 | +struct Filter { | |
| 9 | + char *name; | |
| 10 | + int (*readall)(void *aux, Buffer *bi, Buffer *bo); | |
| 11 | + int (*open)(Filter *f, Object *o); | |
| 12 | + void (*close)(Filter *f); | |
| 13 | + void *aux; | |
| 14 | +}; | |
| 15 | + | |
| 16 | +int fFlate(void *aux, Buffer *bi, Buffer *bo); | |
| 17 | + | |
| 18 | +static Filter filters[] = { | |
| 19 | + {"ASCII85Decode", nil, nil, nil}, | |
| 20 | + {"ASCIIHexDecode", nil, nil, nil}, | |
| 21 | + {"CCITTFaxDecode", nil, nil, nil}, | |
| 22 | + {"CryptDecode", nil, nil, nil}, | |
| 23 | + {"DCTDecode", nil, nil, nil}, | |
| 24 | + {"FlateDecode", fFlate}, | |
| 25 | + {"JBIG2Decode", nil, nil, nil}, | |
| 26 | + {"JPXDecode", nil, nil, nil}, | |
| 27 | + {"LZWDecode", nil, nil, nil}, | |
| 28 | + {"RunLengthDecode", nil, nil, nil}, | |
| 29 | +}; | |
| 30 | + | |
| 31 | +Filter * | |
| 32 | +filteropen(char *name, Object *o) | |
| 33 | +{ | |
| 34 | + int i; | |
| 35 | + Filter *f; | |
| 36 | + | |
| 37 | + for(i = 0; i < nelem(filters) && strcmp(filters[i].name, name) != 0; i++); | |
| 38 | + if(i >= nelem(filters)){ | |
| 39 | + werrstr("no such filter %q", name); | |
| 40 | + return nil; | |
| 41 | + } | |
| 42 | + if(filters[i].readall == nil){ | |
| 43 | + werrstr("filter %q not implemented", name); | |
| 44 | + return nil; | |
| 45 | + } | |
| 46 | + if((f = malloc(sizeof(*f))) == nil) | |
| 47 | + return nil; | |
| 48 | + memmove(f, &filters[i], sizeof(*f)); | |
| 49 | + if(f->open != nil && f->open(f, o) != 0){ | |
| 50 | + free(f); | |
| 51 | + return nil; | |
| 52 | + } | |
| 53 | + | |
| 54 | + return f; | |
| 55 | +} | |
| 56 | + | |
| 57 | +int | |
| 58 | +filterrun(Filter *f, Buffer *bi, Buffer *bo) | |
| 59 | +{ | |
| 60 | + if(f->readall(f->aux, bi, bo) != 0){ | |
| 61 | + werrstr("filter[%s]: %r", f->name); | |
| 62 | + return -1; | |
| 63 | + } | |
| 64 | + fprint(2, "filter[%s]: %d → %d %d\n", f->name, bi->sz, bo->sz, bo->off); | |
| 65 | + return 0; | |
| 66 | +} | |
| 67 | + | |
| 68 | +void | |
| 69 | +filterclose(Filter *f) | |
| 70 | +{ | |
| 71 | + if(f->close != nil) | |
| 72 | + f->close(f); | |
| 73 | + free(f); | |
| 74 | +} | |
| @@ -22,7 +22,7 @@ threadmain(int argc, char **argv) · 21 unmodified lines | ||
| 22 | 22 | Object *v; |
| 23 | 23 | |
| 24 | 24 | quotefmtinstall(); |
| 25 | − deflateinit(); | |
| 25 | + inflateinit(); | |
| 26 | 26 | |
| 27 | 27 | ARGBEGIN{ |
| 28 | 28 | default: |
| @@ -5,8 +5,10 @@ TARG=pdfs · 4 unmodified lines | ||
| 5 | 5 | |
| 6 | 6 | OFILES=\ |
| 7 | 7 | array.$O\ |
| 8 | + buffer.$O\ | |
| 8 | 9 | dict.$O\ |
| 9 | 10 | eval.$O\ |
| 11 | + f_flate.$O\ | |
| 10 | 12 | filter.$O\ |
| 11 | 13 | main.$O\ |
| 12 | 14 | misc.$O\ |
| @@ -14,6 +16,7 @@ OFILES=\ · 1 unmodified line | ||
| 14 | 16 | object.$O\ |
| 15 | 17 | pdf.$O\ |
| 16 | 18 | pdfs.$O\ |
| 19 | + stream.$O\ | |
| 17 | 20 | string.$O\ |
| 18 | 21 | |
| 19 | 22 | HFILES=\ |
| @@ -13,6 +13,31 @@ static Object null = { · 12 unmodified lines | ||
| 13 | 13 | .type = Onull, |
| 14 | 14 | }; |
| 15 | 15 | |
| 16 | +static char *otypes[] = { | |
| 17 | + [Obool] = "bool", | |
| 18 | + [Onum] = "num", | |
| 19 | + [Ostr] = "str", | |
| 20 | + [Oname] = "name", | |
| 21 | + [Oarray] = "array", | |
| 22 | + [Odict] = "dict", | |
| 23 | + [Ostream] = "stream", | |
| 24 | + [Onull] = "null", | |
| 25 | + [Oindir] = "indir", | |
| 26 | +}; | |
| 27 | + | |
| 28 | +int | |
| 29 | +Tfmt(Fmt *f) | |
| 30 | +{ | |
| 31 | + Object *o; | |
| 32 | + | |
| 33 | + o = va_arg(f->args, Object*); | |
| 34 | + if(o == nil || o == &null) | |
| 35 | + return fmtprint(f, "null"); | |
| 36 | + if(o->type < 0 || o->type >= nelem(otypes)) | |
| 37 | + return fmtprint(f, "????"); | |
| 38 | + return fmtprint(f, "%s", otypes[o->type]); | |
| 39 | +} | |
| 40 | + | |
| 16 | 41 | /* General function to parse an object of any type. */ |
| 17 | 42 | Object * |
| 18 | 43 | pdfobj(Pdf *pdf, void *b) |
| @@ -49,8 +74,8 @@ pdfobj(Pdf *pdf, void *b) · 30 unmodified lines | ||
| 49 | 74 | goto err; |
| 50 | 75 | } |
| 51 | 76 | o->type = Ostream; |
| 52 | − o->stream.length = m->num; | |
| 53 | − o->stream.offset = Boffset(b); | |
| 77 | + o->stream.len = m->num; | |
| 78 | + o->stream.off = Boffset(b); | |
| 54 | 79 | return o; |
| 55 | 80 | } |
| 56 | 81 | Bseek(b, off, 0); |
| @@ -75,9 +75,7 @@ err: · 74 unmodified lines | ||
| 75 | 75 | static int |
| 76 | 76 | trailerread(Pdf *pdf) |
| 77 | 77 | { |
| 78 | − int i; | |
| 79 | 78 | Object *o; |
| 80 | − KeyValue *kv; | |
| 81 | 79 | |
| 82 | 80 | if((o = pdfobj(pdf, pdf->bio)) == nil) |
| 83 | 81 | goto err; |
| @@ -87,12 +85,8 @@ trailerread(Pdf *pdf) · 3 unmodified lines | ||
| 87 | 85 | goto err; |
| 88 | 86 | } |
| 89 | 87 | |
| 90 | − for(i = 0, kv = o->dict.kv; i < o->dict.nkv; i++, kv++){ | |
| 91 | − if(strcmp(kv->key, "Root") == 0) | |
| 92 | − pdf->root = kv->value; | |
| 93 | − else if(strcmp(kv->key, "Info") == 0) | |
| 94 | − pdf->info = kv->value; | |
| 95 | − } | |
| 88 | + pdf->root = pdfdictget(o, "Root"); | |
| 89 | + pdf->info = pdfdictget(o, "Info"); | |
| 96 | 90 | pdfobjfree(o); |
| 97 | 91 | o = nil; |
| 98 | 92 | |
| @@ -101,8 +95,6 @@ trailerread(Pdf *pdf) · 2 unmodified lines | ||
| 101 | 95 | werrstr("no root"); |
| 102 | 96 | goto err; |
| 103 | 97 | } |
| 104 | − if(pdfeval(pdf, pdf->root) != 0 || pdfeval(pdf, pdf->info) != 0) | |
| 105 | − goto err; | |
| 106 | 98 | |
| 107 | 99 | return 0; |
| 108 | 100 | err: |
| @@ -121,6 +113,9 @@ pdfopen(int fd) · 12 unmodified lines | ||
| 121 | 113 | int nxref; /* 7.5.4 xref subsection number of objects */ |
| 122 | 114 | int xreftb; /* 7.5.4 xref table offset from the beginning of the file */ |
| 123 | 115 | int i, n, off; |
| 116 | + Stream *stream; | |
| 117 | + | |
| 118 | + fmtinstall('T', Tfmt); | |
| 124 | 119 | |
| 125 | 120 | b = nil; |
| 126 | 121 | o = nil; |
| @@ -196,10 +191,16 @@ badxref: · 69 unmodified lines | ||
| 196 | 191 | } |
| 197 | 192 | }else if(isdigit(tmp[0])){ /* could be 7.5.8 xref stream (since PDF 1.5) */ |
| 198 | 193 | Bseek(b, xreftb, 0); |
| 199 | − if((o = pdfobj(pdf, b)) == nil || pdfeval(pdf, o) != 0) | |
| 200 | − goto err; | |
| 201 | − | |
| 194 | + if((o = pdfobj(pdf, b)) == nil || (stream = streamopen(pdf, o)) == nil) | |
| 195 | + goto badxref; | |
| 196 | + streamclose(stream); | |
| 197 | + pdf->root = pdfdictget(o, "Root"); | |
| 198 | + pdf->info = pdfdictget(o, "Info"); | |
| 202 | 199 | } |
| 200 | + if(pdfeval(pdf, pdf->root) != 0 || pdfeval(pdf, pdf->info) != 0) | |
| 201 | + goto err; | |
| 202 | + fprint(2, "root %T\n", pdf->root); | |
| 203 | + fprint(2, "info %T\n", pdf->info); | |
| 203 | 204 | |
| 204 | 205 | return pdf; |
| 205 | 206 | err: |
| @@ -10,11 +10,23 @@ enum { · 9 unmodified lines | ||
| 10 | 10 | Oindir, /* 7.3.10 */ |
| 11 | 11 | }; |
| 12 | 12 | |
| 13 | +typedef struct Buffer Buffer; | |
| 14 | +typedef struct Filter Filter; | |
| 13 | 15 | typedef struct KeyValue KeyValue; |
| 14 | 16 | typedef struct Object Object; |
| 15 | 17 | typedef struct Pdf Pdf; |
| 16 | −typedef struct Xref Xref; | |
| 17 | 18 | typedef struct Stream Stream; |
| 19 | +typedef struct Xref Xref; | |
| 20 | +#pragma incomplete Filter | |
| 21 | + | |
| 22 | +struct Buffer { | |
| 23 | + uchar *b; | |
| 24 | + int ro; | |
| 25 | + int maxsz; | |
| 26 | + int sz; | |
| 27 | + int off; | |
| 28 | + int eof; | |
| 29 | +}; | |
| 18 | 30 | |
| 19 | 31 | struct Object { |
| 20 | 32 | int type; |
| @@ -45,8 +57,8 @@ struct Object { · 24 unmodified lines | ||
| 45 | 57 | struct { |
| 46 | 58 | KeyValue *kv; |
| 47 | 59 | int nkv; |
| 48 | − u32int length; /* packed */ | |
| 49 | − u32int offset; | |
| 60 | + u32int len; /* packed */ | |
| 61 | + u32int off; | |
| 50 | 62 | }stream; |
| 51 | 63 | }; |
| 52 | 64 | }; |
| @@ -72,9 +84,8 @@ struct Xref { · 19 unmodified lines | ||
| 72 | 84 | }; |
| 73 | 85 | |
| 74 | 86 | struct Stream { |
| 75 | − Biobuf; | |
| 76 | − Object *o; | |
| 77 | − u32int offset; | |
| 87 | + Buffer buf; | |
| 88 | + void *bio; | |
| 78 | 89 | }; |
| 79 | 90 | |
| 80 | 91 | Pdf *pdfopen(int fd); |
| @@ -96,3 +107,19 @@ Object *pdfdictget(Object *o, char *name); · 15 unmodified lines | ||
| 96 | 107 | |
| 97 | 108 | Stream *streamopen(Pdf *pdf, Object *o); |
| 98 | 109 | void streamclose(Stream *s); |
| 110 | + | |
| 111 | +Filter *filteropen(char *name, Object *o); | |
| 112 | +int filterrun(Filter *f, Buffer *bi, Buffer *bo); | |
| 113 | +void filterclose(Filter *f); | |
| 114 | + | |
| 115 | +void bufinit(Buffer *b, uchar *d, int sz); | |
| 116 | +void buffree(Buffer *b); | |
| 117 | +int bufeof(Buffer *b); | |
| 118 | +uchar *bufdata(Buffer *b, int *sz); | |
| 119 | +int bufreadn(Buffer *b, void *bio, int sz); | |
| 120 | +int bufput(Buffer *b, uchar *d, int sz); | |
| 121 | +int bufget(Buffer *b, uchar *d, int sz); | |
| 122 | +void bufdump(Buffer *b); | |
| 123 | + | |
| 124 | +#pragma varargck type "T" Object * | |
| 125 | +int Tfmt(Fmt *f); | |
| @@ -0,0 +1,93 @@ | ||
| 1 | +#include <u.h> | |
| 2 | +#include <libc.h> | |
| 3 | +#include <bio.h> | |
| 4 | +#include "pdf.h" | |
| 5 | + | |
| 6 | +static int | |
| 7 | +bufiof(Biobufhdr *b, void *data, long n) | |
| 8 | +{ | |
| 9 | + Stream *s; | |
| 10 | + | |
| 11 | + s = (void*)((char*)b - sizeof(*s)); | |
| 12 | + | |
| 13 | + return bufget(&s->buf, data, n); | |
| 14 | +} | |
| 15 | + | |
| 16 | +Stream * | |
| 17 | +streamopen(Pdf *pdf, Object *o) | |
| 18 | +{ | |
| 19 | + Stream *s; | |
| 20 | + Buffer b, x; | |
| 21 | + Object *of, **flts; | |
| 22 | + Filter *f; | |
| 23 | + int i, nflts; | |
| 24 | + | |
| 25 | + s = nil; | |
| 26 | + if(pdfeval(pdf, o) != 0 || o == nil || o->type != Ostream) /* FIXME open a string object as a stream as well? */ | |
| 27 | + return nil; | |
| 28 | + | |
| 29 | + bufinit(&b, nil, 0); | |
| 30 | + if(Bseek(pdf->bio, o->stream.off, 0) != o->stream.off) | |
| 31 | + return nil; | |
| 32 | + if(bufreadn(&b, pdf->bio, o->stream.len) < 0) | |
| 33 | + goto err; | |
| 34 | + bufdump(&b); | |
| 35 | + | |
| 36 | + /* see if there are any filters */ | |
| 37 | + if((of = pdfdictget(o, "Filter")) != nil){ | |
| 38 | + if(pdfeval(pdf, of) != 0) | |
| 39 | + goto err; | |
| 40 | + if(of->type == Oname){ /* one filter */ | |
| 41 | + flts = &of; | |
| 42 | + nflts = 1; | |
| 43 | + }else if(of->type == Oarray){ /* array of filters */ | |
| 44 | + flts = of->array.e; | |
| 45 | + nflts = of->array.ne; | |
| 46 | + }else{ | |
| 47 | + werrstr("filters type invalid (%T)", of); | |
| 48 | + goto err; | |
| 49 | + } | |
| 50 | + | |
| 51 | + for(i = nflts-1; i >= 0; i--){ | |
| 52 | + if(flts[i]->type != Oname){ | |
| 53 | + werrstr("filter type invalid (%T)", flts[i]); | |
| 54 | + goto err; | |
| 55 | + } | |
| 56 | + if((f = filteropen(flts[i]->name, o)) == nil) | |
| 57 | + goto err; | |
| 58 | + bufinit(&x, nil, 0); | |
| 59 | + if(filterrun(f, &b, &x) != 0){ | |
| 60 | + buffree(&x); | |
| 61 | + goto err; | |
| 62 | + } | |
| 63 | + buffree(&b); | |
| 64 | + b = x; | |
| 65 | + } | |
| 66 | + } | |
| 67 | + | |
| 68 | + if((s = calloc(1, sizeof(*s)+sizeof(Biobuf))) == nil){ | |
| 69 | + buffree(&b); | |
| 70 | + return nil; | |
| 71 | + } | |
| 72 | + s->bio = (uchar*)(s+1); | |
| 73 | + s->buf = b; | |
| 74 | + Binit(s->bio, Bfildes(pdf->bio), OREAD); | |
| 75 | + Biofn(s->bio, bufiof); | |
| 76 | + | |
| 77 | + bufdump(&s->buf); | |
| 78 | + | |
| 79 | + return s; | |
| 80 | +err: | |
| 81 | + werrstr("stream: %r"); | |
| 82 | + buffree(&b); | |
| 83 | + free(s); | |
| 84 | + return nil; | |
| 85 | +} | |
| 86 | + | |
| 87 | +void | |
| 88 | +streamclose(Stream *s) | |
| 89 | +{ | |
| 90 | + buffree(&s->buf); | |
| 91 | + Bterm(s->bio); | |
| 92 | + free(s); | |
| 93 | +} | |