From 5c7df1f3dbde33a145774b4e5c804186f159f2f4 Mon Sep 17 00:00:00 2001 From: SheetJS Date: Tue, 24 Jun 2014 00:00:39 -0400 Subject: [PATCH] version bump 0.10.0: performance - reworked IIFE - avoid Buffer read helpers (see https://github.com/joyent/node/issues/7809) - avoid nested functions in parse - eliminate consecutive nulls in test files (libreoffice) - travis + coveralls support - jscs linting --- .gitignore | 6 + .jscs.json | 6 + .travis.yml | 6 + Makefile | 26 +- README.md | 6 +- bits/08_blob.js | 125 +++----- bits/30_cfbheader.js | 3 +- bits/31_version.js | 2 +- bits/40_parse.js | 98 +++++++ bits/40_parseheader.js | 3 - bits/41_mver.js | 33 +++ bits/42_cfbheader.js | 99 ------- bits/42_sectorify.js | 9 + bits/43_rbtree.js | 41 +++ bits/43_readfat.js | 51 ---- bits/44_findpath.js | 17 ++ bits/44_readdir.js | 59 ---- bits/45_rbtree.js | 42 --- bits/45_readfat.js | 40 +++ bits/46_findpath.js | 13 - bits/46_readdir.js | 59 ++++ bits/48_parsefooter.js | 11 - bits/49_readutils.js | 15 +- bits/75_consts.js | 25 ++ bits/78_cfbexports.js | 14 +- bits/80_consts.js | 18 -- bits/90_utils.js | 7 - bits/98_exports.js | 8 +- cfb.js | 640 ++++++++++++++++++++--------------------- misc/prof.js | 5 + misc/spin.sh | 14 + package.json | 21 +- test.js | 12 +- 33 files changed, 784 insertions(+), 750 deletions(-) create mode 100644 .gitignore create mode 100644 .jscs.json create mode 100644 bits/40_parse.js delete mode 100644 bits/40_parseheader.js create mode 100644 bits/41_mver.js delete mode 100644 bits/42_cfbheader.js create mode 100644 bits/42_sectorify.js create mode 100644 bits/43_rbtree.js delete mode 100644 bits/43_readfat.js create mode 100644 bits/44_findpath.js delete mode 100644 bits/44_readdir.js delete mode 100644 bits/45_rbtree.js create mode 100644 bits/45_readfat.js delete mode 100644 bits/46_findpath.js create mode 100644 bits/46_readdir.js delete mode 100644 bits/48_parsefooter.js create mode 100644 bits/75_consts.js delete mode 100644 bits/80_consts.js delete mode 100644 bits/90_utils.js create mode 100644 misc/prof.js create mode 100755 misc/spin.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ddf47a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +test_files +node_modules/ +*.xls +misc/coverage.html +prof.js +v8.log diff --git a/.jscs.json b/.jscs.json new file mode 100644 index 0000000..5965004 --- /dev/null +++ b/.jscs.json @@ -0,0 +1,6 @@ +{ + "requireCommaBeforeLineBreak": true, + "disallowTrailingWhitespace": true, + "disallowTrailingComma": true +} + diff --git a/.travis.yml b/.travis.yml index 52d7eba..ed1b3d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,14 @@ language: node_js node_js: + - "0.11" - "0.10" - "0.8" before_install: - "npm install -g mocha" + - "npm install blanket" + - "npm install xlsjs" + - "npm install coveralls mocha-lcov-reporter" before_script: - "make init" +after_success: + - "make coveralls-spin" diff --git a/Makefile b/Makefile index 4e3f7d8..022b93a 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,17 @@ LIB=cfb -DEPS=$(wildcard bits/*.js) +DEPS=$(sort $(wildcard bits/*.js)) TARGET=$(LIB).js $(TARGET): $(DEPS) - cat $^ > $@ + cat $^ | tr -d '\15\32' > $@ bits/31_version.js: package.json - echo "this.version = '"`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`"';" > $@ + echo "exports.version = '"`grep version package.json | awk '{gsub(/[^0-9a-z\.-]/,"",$$2); print $$2}'`"';" > $@ .PHONY: clean clean: - rm -rf $(TARGET) ./test_files/ + rm -f $(TARGET) + rm -rf ./test_files/ .PHONY: init init: @@ -21,20 +22,31 @@ init: test mocha: test.js mocha -R spec +.PHONY: prof +prof: + cat misc/prof.js test.js > prof.js + node --prof prof.js + .PHONY: lint lint: $(TARGET) jshint --show-non-errors $(TARGET) + jscs $(TARGET) -.PHONY: cov +.PHONY: cov cov-spin cov: misc/coverage.html +cov-spin: + make cov & bash misc/spin.sh $$! misc/coverage.html: $(TARGET) test.js - mocha --require blanket -R html-cov > misc/coverage.html + mocha --require blanket -R html-cov > $@ -.PHONY: coveralls +.PHONY: coveralls coveralls-spin coveralls: mocha --require blanket --reporter mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js +coveralls-spin: + make coveralls & bash misc/spin.sh $$! + .PHONY: dist dist: $(TARGET) cp $(TARGET) dist/ diff --git a/README.md b/README.md index 257c6fe..f236e27 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ The entry objects are available from `FullPathDir` and `FileIndex` elements of t container object. - `.name` is the (case sensitive) internal name -- `.type` is the type (`stream` for files, `storage` for dirs, `root` for root) +- `.type` is the type (`2 (stream)` for files, `1 (storage)` for dirs, `5 (root)` for root) - `.content` is a Buffer/Array with the raw content - `.ct`/`.mt` are the creation and modification time (if provided in file) @@ -90,5 +90,9 @@ Writing is not supported. It is in the works, but it has not yet been released. This implementation is covered under Apache 2.0 license. It complies with the [Open Specifications Promise](http://www.microsoft.com/openspecifications/) +[![Build Status](https://travis-ci.org/SheetJS/js-cfb.svg?branch=master)](https://travis-ci.org/SheetJS/js-cfb) + +[![Coverage Status](https://coveralls.io/repos/SheetJS/js-cfb/badge.png?branch=master)](https://coveralls.io/r/SheetJS/js-cfb?branch=master) + [![githalytics.com alpha](https://cruel-carlota.pagodabox.com/88c2e1fd637653cd780b3c6d3dcd70ad "githalytics.com")](http://githalytics.com/SheetJS/js-cfb) diff --git a/bits/08_blob.js b/bits/08_blob.js index ef05d9c..a3523b6 100644 --- a/bits/08_blob.js +++ b/bits/08_blob.js @@ -1,114 +1,81 @@ var Base64 = (function(){ var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; return { - /* (will need this for writing) encode: function(input, utf8) { - var o = ""; - var c1, c2, c3, e1, e2, e3, e4; - for(var i = 0; i < input.length; ) { - c1 = input.charCodeAt(i++); - c2 = input.charCodeAt(i++); - c3 = input.charCodeAt(i++); - e1 = c1 >> 2; - e2 = (c1 & 3) << 4 | c2 >> 4; - e3 = (c2 & 15) << 2 | c3 >> 6; - e4 = c3 & 63; - if (isNaN(c2)) { e3 = e4 = 64; } - else if (isNaN(c3)) { e4 = 64; } - o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4); - } - return o; - },*/ - decode: function(input, utf8) { + decode: function(input) { var o = ""; var c1, c2, c3; var e1, e2, e3, e4; - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + input = input.replace(/[^\w\+\/\=]/g, ""); for(var i = 0; i < input.length;) { e1 = map.indexOf(input.charAt(i++)); e2 = map.indexOf(input.charAt(i++)); - e3 = map.indexOf(input.charAt(i++)); - e4 = map.indexOf(input.charAt(i++)); - c1 = e1 << 2 | e2 >> 4; - c2 = (e2 & 15) << 4 | e3 >> 2; - c3 = (e3 & 3) << 6 | e4; + c1 = (e1 << 2) | (e2 >> 4); o += String.fromCharCode(c1); - if (e3 != 64) { o += String.fromCharCode(c2); } - if (e4 != 64) { o += String.fromCharCode(c3); } + + e3 = map.indexOf(input.charAt(i++)); + c2 = ((e2 & 15) << 4) | (e3 >> 2); + if (e3 !== 64) { o += String.fromCharCode(c2); } + + e4 = map.indexOf(input.charAt(i++)); + c3 = ((e3 & 3) << 6) | e4; + if (e4 !== 64) { o += String.fromCharCode(c3); } } return o; } }; })(); -function s2a(s) { - if(typeof Buffer !== 'undefined') return new Buffer(s, "binary"); - var w = s.split("").map(function(x){ return x.charCodeAt(0) & 0xff; }); - return w; -} +var chr0 = /\u0000/g, chr1 = /[\u0001-\u0006]/; + +var s2a, _s2a; +s2a = _s2a = function _s2a(s) { return s.split("").map(function(x){ return x.charCodeAt(0) & 0xff; }); }; +var __toBuffer, ___toBuffer; +__toBuffer = ___toBuffer = function(bufs) { var x = []; for(var i = 0; i < bufs[0].length; ++i) { x.push.apply(x, bufs[0][i]); } return x; }; +var __utf16le, ___utf16le; +__utf16le = ___utf16le = function(b,s,e) { var ss=[]; for(var i=s; i 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0]) : ___toBuffer(bufs);}; + s2a = function(s) { return Buffer(s, "binary"); }; + bconcat = function(bufs) { return Buffer.isBuffer(bufs[0]) ? Buffer.concat(bufs) : [].concat.apply([], bufs); }; } -var __readUInt8 = function(b, idx) { return b.readUInt8 ? b.readUInt8(idx) : b[idx]; }; -var __readUInt16LE = function(b, idx) { return b.readUInt16LE ? b.readUInt16LE(idx) : b[idx+1]*(1<<8)+b[idx]; }; -var __readInt16LE = function(b, idx) { var u = __readUInt16LE(b,idx); if(!(u & 0x8000)) return u; return (0xffff - u + 1) * -1; }; -var __readUInt32LE = function(b, idx) { return b.readUInt32LE ? b.readUInt32LE(idx) : b[idx+3]*(1<<24)+b[idx+2]*(1<<16)+b[idx+1]*(1<<8)+b[idx]; }; -var __readInt32LE = function(b, idx) { if(b.readInt32LE) return b.readInt32LE(idx); var u = __readUInt32LE(b,idx); if(!(u & 0x80000000)) return u; return (0xffffffff - u + 1) * -1; }; -var __hexlify = function(b) { return b.map(function(x){return (x<16?"0":"") + x.toString(16);}).join(""); }; +var __readUInt8 = function(b, idx) { return b[idx]; }; +var __readUInt16LE = function(b, idx) { return b[idx+1]*(1<<8)+b[idx]; }; +var __readInt16LE = function(b, idx) { var u = b[idx+1]*(1<<8)+b[idx]; return (u < 0x8000) ? u : (0xffff - u + 1) * -1; }; +var __readUInt32LE = function(b, idx) { return b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; }; +var __readInt32LE = function(b, idx) { return (b[idx+3]<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; }; -var __utf16le = function(b,s,e) { if(b.utf16le) return b.utf16le(s,e); var ss=[]; for(var i=s; i>1); + if(m !== hexstr) throw fld + 'Expected ' + hexstr + ' saw ' + m; + this.l += hexstr.length>>1; } function prep_blob(blob, pos) { - blob.read_shift = ReadShift.bind(blob); + blob.l = pos; + blob.read_shift = ReadShift; blob.chk = CheckField; - blob.l = pos || 0; - var read = ReadShift.bind(blob), chk = CheckField.bind(blob); - return [read, chk]; } diff --git a/bits/30_cfbheader.js b/bits/30_cfbheader.js index 31265b1..d145964 100644 --- a/bits/30_cfbheader.js +++ b/bits/30_cfbheader.js @@ -1,2 +1,3 @@ /* [MS-CFB] v20130118 */ -var CFB = (function(){ +var CFB = (function _CFB(){ +var exports = {}; diff --git a/bits/31_version.js b/bits/31_version.js index 5d7864c..74b3d52 100644 --- a/bits/31_version.js +++ b/bits/31_version.js @@ -1 +1 @@ -this.version = '0.9.1'; +exports.version = '0.10.0'; diff --git a/bits/40_parse.js b/bits/40_parse.js new file mode 100644 index 0000000..f04b812 --- /dev/null +++ b/bits/40_parse.js @@ -0,0 +1,98 @@ +function parse(file) { +var mver = 3; // major version +var ssz = 512; // sector size +var nmfs = 0; // number of mini FAT sectors +var ndfs = 0; // number of DIFAT sectors +var dir_start = 0; // first directory sector location +var minifat_start = 0; // first mini FAT sector location +var difat_start = 0; // first mini FAT sector location + +var fat_addrs = []; // locations of FAT sectors + +/* [MS-CFB] 2.2 Compound File Header */ +var blob = file.slice(0,512); +prep_blob(blob, 0); + +/* major version */ +mver = check_get_mver(blob); +switch(mver) { + case 3: ssz = 512; break; case 4: ssz = 4096; break; + default: throw "Major Version: Expected 3 or 4 saw " + mver; +} + +/* reprocess header */ +if(ssz !== 512) { blob = file.slice(0,ssz); prep_blob(blob, 28 /* blob.l */); } +/* Save header for final object */ +var header = file.slice(0,ssz); + +check_shifts(blob, mver); + +// Number of Directory Sectors +var nds = blob.read_shift(4, 'i'); +if(mver === 3 && nds !== 0) throw '# Directory Sectors: Expected 0 saw ' + nds; + +// Number of FAT Sectors +//var nfs = blob.read_shift(4, 'i'); +blob.l += 4; + +// First Directory Sector Location +dir_start = blob.read_shift(4, 'i'); + +// Transaction Signature +blob.l += 4; + +// Mini Stream Cutoff Size +blob.chk('00100000', 'Mini Stream Cutoff Size: '); + +// First Mini FAT Sector Location +minifat_start = blob.read_shift(4, 'i'); + +// Number of Mini FAT Sectors +nmfs = blob.read_shift(4, 'i'); + +// First DIFAT sector location +difat_start = blob.read_shift(4, 'i'); + +// Number of DIFAT Sectors +ndfs = blob.read_shift(4, 'i'); + +// Grab FAT Sector Locations +for(var q, j = 0; j < 109; ++j) { /* 109 = (512 - blob.l)>>>2; */ + q = blob.read_shift(4, 'i'); + if(q<0) break; + fat_addrs[j] = q; +} + +/** Break the file up into sectors */ +var sectors = sectorify(file, ssz); + +sleuth_fat(difat_start, ndfs, sectors, ssz, fat_addrs); + +/** Chains */ +var sector_list = make_sector_list(sectors, dir_start, fat_addrs, ssz); + +sector_list[dir_start].name = "!Directory"; +if(nmfs > 0 && minifat_start !== ENDOFCHAIN) sector_list[minifat_start].name = "!MiniFAT"; +sector_list[fat_addrs[0]].name = "!FAT"; + +/* [MS-CFB] 2.6.1 Compound File Directory Entry */ +var files = {}, Paths = [], FileIndex = [], FullPaths = [], FullPathDir = {}; +read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex); + +build_full_paths(FileIndex, FullPathDir, FullPaths, Paths); + +var root_name = Paths.shift(); +Paths.root = root_name; + +/* [MS-CFB] 2.6.4 (Unicode 3.0.1 case conversion) */ +var find_path = make_find_path(FullPaths, Paths, FileIndex, files, root_name); + +return { + raw: {header: header, sectors: sectors}, + FileIndex: FileIndex, + FullPaths: FullPaths, + FullPathDir: FullPathDir, + find: find_path +}; +} // parse + diff --git a/bits/40_parseheader.js b/bits/40_parseheader.js deleted file mode 100644 index c326006..0000000 --- a/bits/40_parseheader.js +++ /dev/null @@ -1,3 +0,0 @@ -function parse(file) { - - diff --git a/bits/41_mver.js b/bits/41_mver.js new file mode 100644 index 0000000..e67db91 --- /dev/null +++ b/bits/41_mver.js @@ -0,0 +1,33 @@ +/* [MS-CFB] 2.2 Compound File Header -- read up to major version */ +function check_get_mver(blob) { + // header signature 8 + blob.chk(HEADER_SIGNATURE, 'Header Signature: '); + + // clsid 16 + blob.chk(HEADER_CLSID, 'CLSID: '); + + // minor version 2 + blob.l += 2; + + return blob.read_shift(2,'u'); +} +function check_shifts(blob, mver) { + var shift = 0x09; + + // Byte Order + blob.chk('feff', 'Byte Order: '); + + // Sector Shift + switch((shift = blob.read_shift(2))) { + case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; + case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; + default: throw 'Sector Shift: Expected 9 or 12 saw ' + shift; + } + + // Mini Sector Shift + blob.chk('0600', 'Mini Sector Shift: '); + + // Reserved + blob.chk('000000000000', 'Reserved: '); +} + diff --git a/bits/42_cfbheader.js b/bits/42_cfbheader.js deleted file mode 100644 index 3dfc807..0000000 --- a/bits/42_cfbheader.js +++ /dev/null @@ -1,99 +0,0 @@ -var mver = 3; // major version -var ssz = 512; // sector size -var mssz = 64; // mini sector size -var nds = 0; // number of directory sectors -var nfs = 0; // number of FAT sectors -var nmfs = 0; // number of mini FAT sectors -var ndfs = 0; // number of DIFAT sectors -var dir_start = 0; // first directory sector location -var minifat_start = 0; // first mini FAT sector location -var difat_start = 0; // first mini FAT sector location - -var ms_cutoff_size = 4096; // mini stream cutoff size -var minifat_store = 0; // first sector with minifat data -var minifat_size = 0; // size of minifat data - -var fat_addrs = []; // locations of FAT sectors - -/* [MS-CFB] 2.2 Compound File Header */ -var blob = file.slice(0,512); -prep_blob(blob); -var read = ReadShift.bind(blob), chk = CheckField.bind(blob); -var j = 0, q; - -// header signature 8 -chk(HEADER_SIGNATURE, 'Header Signature: '); - -// clsid 16 -chk(HEADER_CLSID, 'CLSID: '); - -// minor version 2 -read(2); - -// major version 3 -mver = read(2); -switch(mver) { - case 3: ssz = 512; break; - case 4: ssz = 4096; break; - default: throw "Major Version: Expected 3 or 4 saw " + mver; -} - -// reprocess header -var pos = blob.l; -blob = file.slice(0,ssz); -prep_blob(blob,pos); -read = ReadShift.bind(blob); -chk = CheckField.bind(blob); -var header = file.slice(0,ssz); - -// Byte Order TODO -chk('feff', 'Byte Order: '); - -// Sector Shift -switch((q = read(2))) { - case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; - case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; - default: throw 'Sector Shift: Expected 9 or 12 saw ' + q; -} - -// Mini Sector Shift -chk('0600', 'Mini Sector Shift: '); - -// Reserved -chk('000000000000', 'Mini Sector Shift: '); - -// Number of Directory Sectors -nds = read(4); -if(mver === 3 && nds !== 0) throw '# Directory Sectors: Expected 0 saw ' + nds; - -// Number of FAT Sectors -nfs = read(4); - -// First Directory Sector Location -dir_start = read(4); - -// Transaction Signature TODO -read(4); - -// Mini Stream Cutoff Size TODO -chk('00100000', 'Mini Stream Cutoff Size: '); - -// First Mini FAT Sector Location -minifat_start = read(4); - -// Number of Mini FAT Sectors -nmfs = read(4); - -// First DIFAT sector location -difat_start = read(4); - -// Number of DIFAT Sectors -ndfs = read(4); - -// Grab FAT Sector Locations -for(j = 0; blob.l != 512; ) { - if((q = read(4))>=MAXREGSECT) break; - fat_addrs[j++] = q; -} - - diff --git a/bits/42_sectorify.js b/bits/42_sectorify.js new file mode 100644 index 0000000..b778af7 --- /dev/null +++ b/bits/42_sectorify.js @@ -0,0 +1,9 @@ +/** Break the file up into sectors */ +function sectorify(file, ssz) { + var nsectors = Math.ceil(file.length/ssz)-1; + var sectors = new Array(nsectors); + for(var i=1; i < nsectors; ++i) sectors[i-1] = file.slice(i*ssz,(i+1)*ssz); + sectors[nsectors-1] = file.slice(nsectors*ssz); + return sectors; +} + diff --git a/bits/43_rbtree.js b/bits/43_rbtree.js new file mode 100644 index 0000000..7354d46 --- /dev/null +++ b/bits/43_rbtree.js @@ -0,0 +1,41 @@ +/* [MS-CFB] 2.6.4 Red-Black Tree */ +function build_full_paths(FI, FPD, FP, Paths) { + var i = 0, L = 0, R = 0, C = 0, j = 0, pl = Paths.length; + var dad = new Array(pl), q = new Array(pl); + + for(; i < pl; ++i) { dad[i]=q[i]=i; FP[i]=Paths[i]; } + + for(; j < q.length; ++j) { + i = q[j]; + L = FI[i].L; R = FI[i].R; C = FI[i].C; + if(dad[i] === i) { + if(L !== -1 /*NOSTREAM*/ && dad[L] !== L) dad[i] = dad[L]; + if(R !== -1 && dad[R] !== R) dad[i] = dad[R]; + } + if(C !== -1 /*NOSTREAM*/) dad[C] = i; + if(L !== -1) { dad[L] = dad[i]; q.push(L); } + if(R !== -1) { dad[R] = dad[i]; q.push(R); } + } + for(i=1; i !== pl; ++i) if(dad[i] === i) { + if(R !== -1 /*NOSTREAM*/ && dad[R] !== R) dad[i] = dad[R]; + else if(L !== -1 && dad[L] !== L) dad[i] = dad[L]; + } + + for(i=1; i < pl; ++i) { + if(FI[i].type === 0 /* unknown */) continue; + j = dad[i]; + if(j === 0) FP[i] = FP[0] + "/" + FP[i]; + else while(j !== 0) { + FP[i] = FP[j] + "/" + FP[i]; + j = dad[j]; + } + dad[i] = 0; + } + + FP[0] += "/"; + for(i=1; i < pl; ++i) { + if(FI[i].type !== 2 /* stream */) FP[i] += "/"; + FPD[FP[i]] = FI[i]; + } +} + diff --git a/bits/43_readfat.js b/bits/43_readfat.js deleted file mode 100644 index d6dbe96..0000000 --- a/bits/43_readfat.js +++ /dev/null @@ -1,51 +0,0 @@ -/** Break the file up into sectors */ -var nsectors = Math.ceil((file.length - ssz)/ssz); -var sectors = []; -for(var i=1; i != nsectors; ++i) sectors[i-1] = file.slice(i*ssz,(i+1)*ssz); -sectors[nsectors-1] = file.slice(nsectors*ssz); - -/** Chase down the rest of the DIFAT chain to build a comprehensive list - DIFAT chains by storing the next sector number as the last 32 bytes */ -function sleuth_fat(idx, cnt) { - if(idx === ENDOFCHAIN) { - if(cnt !== 0) throw "DIFAT chain shorter than expected"; - return; - } - if(idx !== FREESECT) { - var sector = sectors[idx]; - for(var i = 0; i != ssz/4-1; ++i) { - if((q = __readUInt32LE(sector,i*4)) === ENDOFCHAIN) break; - fat_addrs.push(q); - } - sleuth_fat(__readUInt32LE(sector,ssz-4),cnt - 1); - } -} -sleuth_fat(difat_start, ndfs); - -/** DONT CAT THE FAT! Just calculate where we need to go */ -function get_buffer(byte_addr, bytes) { - var addr = fat_addrs[Math.floor(byte_addr*4/ssz)]; - if(ssz - (byte_addr*4 % ssz) < (bytes || 0)) throw "FAT boundary crossed: " + byte_addr + " "+bytes+" "+ssz; - return sectors[addr].slice((byte_addr*4 % ssz)); -} - -function get_buffer_u32(byte_addr) { - return __readUInt32LE(get_buffer(byte_addr,4), 0); -} - -function get_next_sector(idx) { return get_buffer_u32(idx); } - -/** Chains */ -var chkd = new Array(sectors.length), sector_list = []; -var get_sector = function get_sector(k) { return sectors[k]; }; -for(i=0; i != sectors.length; ++i) { - var buf = [], k = (i + dir_start) % sectors.length; - if(chkd[k]) continue; - for(j=k; j<=MAXREGSECT; buf.push(j),j=get_next_sector(j)) chkd[j] = true; - sector_list[k] = {nodes: buf}; - sector_list[k].data = __toBuffer(Array(buf.map(get_sector))); -} -sector_list[dir_start].name = "!Directory"; -if(nmfs > 0 && minifat_start !== ENDOFCHAIN) sector_list[minifat_start].name = "!MiniFAT"; -sector_list[fat_addrs[0]].name = "!FAT"; - diff --git a/bits/44_findpath.js b/bits/44_findpath.js new file mode 100644 index 0000000..6575379 --- /dev/null +++ b/bits/44_findpath.js @@ -0,0 +1,17 @@ +/* [MS-CFB] 2.6.4 */ +function make_find_path(FullPaths, Paths, FileIndex, files, root_name) { + var UCFullPaths = new Array(FullPaths.length); + var UCPaths = new Array(Paths.length), i; + for(i = 0; i < FullPaths.length; ++i) UCFullPaths[i] = FullPaths[i].toUpperCase(); + for(i = 0; i < Paths.length; ++i) UCPaths[i] = Paths[i].toUpperCase(); + return function find_path(path) { + var k; + if(path.charCodeAt(0) === 47 /* "/" */) { k=true; path = root_name + path; } + else k = path.indexOf("/") !== -1; + var UCPath = path.toUpperCase(); + var w = k === true ? UCFullPaths.indexOf(UCPath) : UCPaths.indexOf(UCPath); + if(w === -1) return null; + return k === true ? FileIndex[w] : files[Paths[w]]; + }; +} + diff --git a/bits/44_readdir.js b/bits/44_readdir.js deleted file mode 100644 index e900a55..0000000 --- a/bits/44_readdir.js +++ /dev/null @@ -1,59 +0,0 @@ -/* [MS-CFB] 2.6.1 Compound File Directory Entry */ -var files = {}, Paths = [], FileIndex = [], FullPaths = [], FullPathDir = {}; -function read_directory(idx) { - var blob, read, w; - var sector = sector_list[idx].data; - for(var i = 0; i != sector.length; i+= 128) { - blob = sector.slice(i, i+128); - prep_blob(blob, 64); - read = ReadShift.bind(blob); - var namelen = read(2); - if(namelen === 0) return; - var name = __utf16le(blob,0,namelen-(Paths.length?2:0)); // OLE - Paths.push(name); - var o = { name: name }; - o.type = EntryTypes[read(1)]; - o.color = read(1); - o.left = read(4); if(o.left === NOSTREAM) delete o.left; - o.right = read(4); if(o.right === NOSTREAM) delete o.right; - o.child = read(4); if(o.child === NOSTREAM) delete o.child; - o.clsid = read(16); - o.state = read(4); - var ctime = read(8); if(ctime != "0000000000000000") o.ctime = ctime; - var mtime = read(8); if(mtime != "0000000000000000") o.mtime = mtime; - o.start = read(4); - o.size = read(4); - if(o.type === 'root') { //root entry - minifat_store = o.start; - if(nmfs > 0 && minifat_store !== ENDOFCHAIN) sector_list[minifat_store].name = "!StreamData"; - minifat_size = o.size; - } else if(o.size >= ms_cutoff_size) { - o.storage = 'fat'; - if(!sector_list[o.start] && dir_start > 0) o.start = (o.start + dir_start) % sectors.length; - sector_list[o.start].name = o.name; - o.content = sector_list[o.start].data.slice(0,o.size); - prep_blob(o.content); - } else { - o.storage = 'minifat'; - w = o.start * mssz; - if(minifat_store !== ENDOFCHAIN && o.start !== ENDOFCHAIN) { - o.content = sector_list[minifat_store].data.slice(w,w+o.size); - prep_blob(o.content); - } - } - if(o.ctime) { - var ct = blob.slice(blob.l-24, blob.l-16); - var c2 = (__readUInt32LE(ct,4)/1e7)*Math.pow(2,32)+__readUInt32LE(ct,0)/1e7; - o.ct = new Date((c2 - 11644473600)*1000); - } - if(o.mtime) { - var mt = blob.slice(blob.l-16, blob.l-8); - var m2 = (__readUInt32LE(mt,4)/1e7)*Math.pow(2,32)+__readUInt32LE(mt,0)/1e7; - o.mt = new Date((m2 - 11644473600)*1000); - } - files[name] = o; - FileIndex.push(o); - } -} -read_directory(dir_start); - diff --git a/bits/45_rbtree.js b/bits/45_rbtree.js deleted file mode 100644 index 0fc7b65..0000000 --- a/bits/45_rbtree.js +++ /dev/null @@ -1,42 +0,0 @@ -/* [MS-CFB] 2.6.4 Red-Black Tree */ -function build_full_paths(Dir, pathobj, paths, patharr) { - var i; - var dad = new Array(patharr.length); - - var q = new Array(patharr.length); - - for(i=0; i != dad.length; ++i) { dad[i]=q[i]=i; paths[i]=patharr[i]; } - - while(q.length > 0) { - for(i = q[0]; typeof i !== "undefined"; i = q.shift()) { - if(dad[i] === i) { - if(Dir[i].left && dad[Dir[i].left] != Dir[i].left) dad[i] = dad[Dir[i].left]; - if(Dir[i].right && dad[Dir[i].right] != Dir[i].right) dad[i] = dad[Dir[i].right]; - } - if(Dir[i].child) dad[Dir[i].child] = i; - if(Dir[i].left) { dad[Dir[i].left] = dad[i]; q.push(Dir[i].left); } - if(Dir[i].right) { dad[Dir[i].right] = dad[i]; q.push(Dir[i].right); } - } - for(i=1; i != dad.length; ++i) if(dad[i] === i) { - if(Dir[i].right && dad[Dir[i].right] != Dir[i].right) dad[i] = dad[Dir[i].right]; - else if(Dir[i].left && dad[Dir[i].left] != Dir[i].left) dad[i] = dad[Dir[i].left]; - } - } - - for(i=1; i !== paths.length; ++i) { - if(Dir[i].type === "unknown") continue; - var j = dad[i]; - if(j === 0) paths[i] = paths[0] + "/" + paths[i]; - else while(j !== 0) { - paths[i] = paths[j] + "/" + paths[i]; - j = dad[j]; - } - dad[i] = 0; - } - - paths[0] += "/"; - for(i=1; i !== paths.length; ++i) if(Dir[i].type !== 'stream') paths[i] += "/"; - for(i=0; i !== paths.length; ++i) pathobj[paths[i]] = FileIndex[i]; -} -build_full_paths(FileIndex, FullPathDir, FullPaths, Paths); - diff --git a/bits/45_readfat.js b/bits/45_readfat.js new file mode 100644 index 0000000..2c205d6 --- /dev/null +++ b/bits/45_readfat.js @@ -0,0 +1,40 @@ +/** Chase down the rest of the DIFAT chain to build a comprehensive list + DIFAT chains by storing the next sector number as the last 32 bytes */ +function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) { + var q; + if(idx === ENDOFCHAIN) { + if(cnt !== 0) throw "DIFAT chain shorter than expected"; + } else if(idx !== -1 /*FREESECT*/) { + var sector = sectors[idx], m = (ssz>>>2)-1; + for(var i = 0; i < m; ++i) { + if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break; + fat_addrs.push(q); + } + sleuth_fat(__readInt32LE(sector,ssz-4),cnt - 1, sectors, ssz, fat_addrs); + } +} + +/** Chase down the sector linked lists */ +function make_sector_list(sectors, dir_start, fat_addrs, ssz) { + var sl = sectors.length, sector_list = new Array(sl); + var chkd = new Array(sl), buf, buf_chain; + var modulus = ssz - 1, i, j, k, jj; + for(i=0; i < sl; ++i) { + buf = []; + k = (i + dir_start); if(k >= sl) k-=sl; + if(chkd[k] === true) continue; + buf_chain = []; + for(j=k; j>=0;) { + chkd[j] = true; + buf[buf.length] = j; + buf_chain.push(sectors[j]); + var addr = fat_addrs[Math.floor(j*4/ssz)]; + jj = ((j*4) & modulus); + if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + j = __readInt32LE(sectors[addr], jj); + } + sector_list[k] = {nodes: buf, data:__toBuffer([buf_chain])}; + } + return sector_list; +} + diff --git a/bits/46_findpath.js b/bits/46_findpath.js deleted file mode 100644 index d712c1c..0000000 --- a/bits/46_findpath.js +++ /dev/null @@ -1,13 +0,0 @@ -var root_name = Paths.shift(); -Paths.root = root_name; - -/* [MS-CFB] 2.6.4 (Unicode 3.0.1 case conversion) */ -function find_path(path) { - if(path[0] === "/") path = root_name + path; - var UCNames = (path.indexOf("/") !== -1 ? FullPaths : Paths).map(function(x) { return x.toUpperCase(); }); - var UCPath = path.toUpperCase(); - var w = UCNames.indexOf(UCPath); - if(w === -1) return null; - return path.indexOf("/") !== -1 ? FileIndex[w] : files[Paths[w]]; -} - diff --git a/bits/46_readdir.js b/bits/46_readdir.js new file mode 100644 index 0000000..0551f16 --- /dev/null +++ b/bits/46_readdir.js @@ -0,0 +1,59 @@ +/* [MS-CFB] 2.6.1 Compound File Directory Entry */ +function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex) { + var blob; + var minifat_store = 0, pl = (Paths.length?2:0); + var sector = sector_list[dir_start].data; + var i = 0, namelen = 0, name, o, ctime, mtime; + for(; i < sector.length; i+= 128) { + blob = sector.slice(i, i+128); + prep_blob(blob, 64); + namelen = blob.read_shift(2); + if(namelen === 0) continue; + name = __utf16le(blob,0,namelen-pl); + Paths.push(name); + o = { + name: name, + type: blob.read_shift(1), + color: blob.read_shift(1), + L: blob.read_shift(4, 'i'), + R: blob.read_shift(4, 'i'), + C: blob.read_shift(4, 'i'), + clsid: blob.read_shift(16), + state: blob.read_shift(4, 'i') + }; + ctime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2); + if(ctime !== 0) { + o.ctime = ctime; o.ct = read_date(blob, blob.l-8); + } + mtime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2); + if(mtime !== 0) { + o.mtime = mtime; o.mt = read_date(blob, blob.l-8); + } + o.start = blob.read_shift(4, 'i'); + o.size = blob.read_shift(4, 'i'); + if(o.type === 5) { /* root */ + minifat_store = o.start; + if(nmfs > 0 && minifat_store !== ENDOFCHAIN) sector_list[minifat_store].name = "!StreamData"; + /*minifat_size = o.size;*/ + } else if(o.size >= 4096 /* MSCSZ */) { + o.storage = 'fat'; + if(sector_list[o.start] === undefined) if((o.start+=dir_start)>=sectors.length) o.start-=sectors.length; + sector_list[o.start].name = o.name; + o.content = sector_list[o.start].data.slice(0,o.size); + prep_blob(o.content, 0); + } else { + o.storage = 'minifat'; + if(minifat_store !== ENDOFCHAIN && o.start !== ENDOFCHAIN) { + o.content = sector_list[minifat_store].data.slice(o.start*MSSZ,o.start*MSSZ+o.size); + prep_blob(o.content, 0); + } + } + files[name] = o; + FileIndex.push(o); + } +} + +function read_date(blob, offset) { + return new Date(( ( (__readUInt32LE(blob,offset+4)/1e7)*Math.pow(2,32)+__readUInt32LE(blob,offset)/1e7 ) - 11644473600)*1000); +} + diff --git a/bits/48_parsefooter.js b/bits/48_parsefooter.js deleted file mode 100644 index 1bc70cb..0000000 --- a/bits/48_parsefooter.js +++ /dev/null @@ -1,11 +0,0 @@ -var rval = { - raw: {header: header, sectors: sectors}, - FileIndex: FileIndex, - FullPaths: FullPaths, - FullPathDir: FullPathDir, - find: find_path -}; - -return rval; -} // parse - diff --git a/bits/49_readutils.js b/bits/49_readutils.js index fc4a359..7a85529 100644 --- a/bits/49_readutils.js +++ b/bits/49_readutils.js @@ -1,17 +1,14 @@ - +var fs; function readFileSync(filename) { - var fs = require('fs'); - var file = fs.readFileSync(filename); - return parse(file); + if(fs === undefined) fs = require('fs'); + return parse(fs.readFileSync(filename)); } function readSync(blob, options) { - var o = options || {}; - switch((o.type || "base64")) { + switch(options !== undefined && options.type !== undefined ? options.type : "base64") { case "file": return readFileSync(blob); - case "base64": blob = Base64.decode(blob); - /* falls through */ - case "binary": blob = s2a(blob); break; + case "base64": return parse(s2a(Base64.decode(blob))); + case "binary": return parse(s2a(blob)); } return parse(blob); } diff --git a/bits/75_consts.js b/bits/75_consts.js new file mode 100644 index 0000000..6b7429c --- /dev/null +++ b/bits/75_consts.js @@ -0,0 +1,25 @@ +/** CFB Constants */ +var MSSZ = 64; /* Mini Sector Size = 1<<6 */ +//var MSCSZ = 4096; /* Mini Stream Cutoff Size */ +/* 2.1 Compound File Sector Numbers and Types */ +var ENDOFCHAIN = -2; +/* 2.2 Compound File Header */ +var HEADER_SIGNATURE = 'd0cf11e0a1b11ae1'; +var HEADER_CLSID = '00000000000000000000000000000000'; +var consts = { + /* 2.1 Compund File Sector Numbers and Types */ + MAXREGSECT: -6, + DIFSECT: -4, + FATSECT: -3, + ENDOFCHAIN: ENDOFCHAIN, + FREESECT: -1, + /* 2.2 Compound File Header */ + HEADER_SIGNATURE: HEADER_SIGNATURE, + HEADER_MINOR_VERSION: '3e00', + MAXREGSID: -6, + NOSTREAM: -1, + HEADER_CLSID: HEADER_CLSID, + /* 2.6.1 Compound File Directory Entry */ + EntryTypes: ['unknown','storage','stream','lockbytes','property','root'] +}; + diff --git a/bits/78_cfbexports.js b/bits/78_cfbexports.js index c20d69f..d2da979 100644 --- a/bits/78_cfbexports.js +++ b/bits/78_cfbexports.js @@ -1,3 +1,11 @@ -this.read = readSync; -this.parse = parse; -return this; +exports.read = readSync; +exports.parse = parse; +exports.utils = { + ReadShift: ReadShift, + CheckField: CheckField, + prep_blob: prep_blob, + bconcat: bconcat, + consts: consts +}; + +return exports; diff --git a/bits/80_consts.js b/bits/80_consts.js deleted file mode 100644 index 45eb609..0000000 --- a/bits/80_consts.js +++ /dev/null @@ -1,18 +0,0 @@ -/** CFB Constants */ -{ - /* 2.1 Compund File Sector Numbers and Types */ - var MAXREGSECT = 0xFFFFFFFA; - var DIFSECT = 0xFFFFFFFC; - var FATSECT = 0xFFFFFFFD; - var ENDOFCHAIN = 0xFFFFFFFE; - var FREESECT = 0xFFFFFFFF; - /* 2.2 Compound File Header */ - var HEADER_SIGNATURE = 'd0cf11e0a1b11ae1'; - var HEADER_MINOR_VERSION = '3e00'; - var MAXREGSID = 0xFFFFFFFA; - var NOSTREAM = 0xFFFFFFFF; - var HEADER_CLSID = '00000000000000000000000000000000'; - /* 2.6.1 Compound File Directory Entry */ - var EntryTypes = ['unknown','storage','stream','lockbytes','property','root']; -} - diff --git a/bits/90_utils.js b/bits/90_utils.js deleted file mode 100644 index b79dde8..0000000 --- a/bits/90_utils.js +++ /dev/null @@ -1,7 +0,0 @@ -var CFB_utils = { - ReadShift: ReadShift, - CheckField: CheckField, - prep_blob: prep_blob, - bconcat: bconcat -}; - diff --git a/bits/98_exports.js b/bits/98_exports.js index e9b7ed0..9234345 100644 --- a/bits/98_exports.js +++ b/bits/98_exports.js @@ -1,7 +1 @@ -if(typeof require !== 'undefined' && typeof exports !== 'undefined') { - var fs = require('fs'); - exports.read = CFB.read; - exports.parse = CFB.parse; - exports.utils = CFB_utils; - exports.version = CFB.version; -} +if(typeof require !== 'undefined' && typeof module !== 'undefined' && typeof DO_NOT_EXPORT_CFB === 'undefined') { module.exports = CFB; } diff --git a/cfb.js b/cfb.js index f3bbf5a..0e864c0 100644 --- a/cfb.js +++ b/cfb.js @@ -5,450 +5,436 @@ var Base64 = (function(){ var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; return { - /* (will need this for writing) encode: function(input, utf8) { - var o = ""; - var c1, c2, c3, e1, e2, e3, e4; - for(var i = 0; i < input.length; ) { - c1 = input.charCodeAt(i++); - c2 = input.charCodeAt(i++); - c3 = input.charCodeAt(i++); - e1 = c1 >> 2; - e2 = (c1 & 3) << 4 | c2 >> 4; - e3 = (c2 & 15) << 2 | c3 >> 6; - e4 = c3 & 63; - if (isNaN(c2)) { e3 = e4 = 64; } - else if (isNaN(c3)) { e4 = 64; } - o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4); - } - return o; - },*/ - decode: function(input, utf8) { + decode: function(input) { var o = ""; var c1, c2, c3; var e1, e2, e3, e4; - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + input = input.replace(/[^\w\+\/\=]/g, ""); for(var i = 0; i < input.length;) { e1 = map.indexOf(input.charAt(i++)); e2 = map.indexOf(input.charAt(i++)); - e3 = map.indexOf(input.charAt(i++)); - e4 = map.indexOf(input.charAt(i++)); - c1 = e1 << 2 | e2 >> 4; - c2 = (e2 & 15) << 4 | e3 >> 2; - c3 = (e3 & 3) << 6 | e4; + c1 = (e1 << 2) | (e2 >> 4); o += String.fromCharCode(c1); - if (e3 != 64) { o += String.fromCharCode(c2); } - if (e4 != 64) { o += String.fromCharCode(c3); } + + e3 = map.indexOf(input.charAt(i++)); + c2 = ((e2 & 15) << 4) | (e3 >> 2); + if (e3 !== 64) { o += String.fromCharCode(c2); } + + e4 = map.indexOf(input.charAt(i++)); + c3 = ((e3 & 3) << 6) | e4; + if (e4 !== 64) { o += String.fromCharCode(c3); } } return o; } }; })(); -function s2a(s) { - if(typeof Buffer !== 'undefined') return new Buffer(s, "binary"); - var w = s.split("").map(function(x){ return x.charCodeAt(0) & 0xff; }); - return w; -} +var chr0 = /\u0000/g, chr1 = /[\u0001-\u0006]/; + +var s2a, _s2a; +s2a = _s2a = function _s2a(s) { return s.split("").map(function(x){ return x.charCodeAt(0) & 0xff; }); }; +var __toBuffer, ___toBuffer; +__toBuffer = ___toBuffer = function(bufs) { var x = []; for(var i = 0; i < bufs[0].length; ++i) { x.push.apply(x, bufs[0][i]); } return x; }; +var __utf16le, ___utf16le; +__utf16le = ___utf16le = function(b,s,e) { var ss=[]; for(var i=s; i 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0]) : ___toBuffer(bufs);}; + s2a = function(s) { return Buffer(s, "binary"); }; + bconcat = function(bufs) { return Buffer.isBuffer(bufs[0]) ? Buffer.concat(bufs) : [].concat.apply([], bufs); }; } -var __readUInt8 = function(b, idx) { return b.readUInt8 ? b.readUInt8(idx) : b[idx]; }; -var __readUInt16LE = function(b, idx) { return b.readUInt16LE ? b.readUInt16LE(idx) : b[idx+1]*(1<<8)+b[idx]; }; -var __readInt16LE = function(b, idx) { var u = __readUInt16LE(b,idx); if(!(u & 0x8000)) return u; return (0xffff - u + 1) * -1; }; -var __readUInt32LE = function(b, idx) { return b.readUInt32LE ? b.readUInt32LE(idx) : b[idx+3]*(1<<24)+b[idx+2]*(1<<16)+b[idx+1]*(1<<8)+b[idx]; }; -var __readInt32LE = function(b, idx) { if(b.readInt32LE) return b.readInt32LE(idx); var u = __readUInt32LE(b,idx); if(!(u & 0x80000000)) return u; return (0xffffffff - u + 1) * -1; }; -var __hexlify = function(b) { return b.map(function(x){return (x<16?"0":"") + x.toString(16);}).join(""); }; +var __readUInt8 = function(b, idx) { return b[idx]; }; +var __readUInt16LE = function(b, idx) { return b[idx+1]*(1<<8)+b[idx]; }; +var __readInt16LE = function(b, idx) { var u = b[idx+1]*(1<<8)+b[idx]; return (u < 0x8000) ? u : (0xffff - u + 1) * -1; }; +var __readUInt32LE = function(b, idx) { return b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; }; +var __readInt32LE = function(b, idx) { return (b[idx+3]<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; }; -var __utf16le = function(b,s,e) { if(b.utf16le) return b.utf16le(s,e); var ss=[]; for(var i=s; i>1); + if(m !== hexstr) throw fld + 'Expected ' + hexstr + ' saw ' + m; + this.l += hexstr.length>>1; } function prep_blob(blob, pos) { - blob.read_shift = ReadShift.bind(blob); + blob.l = pos; + blob.read_shift = ReadShift; blob.chk = CheckField; - blob.l = pos || 0; - var read = ReadShift.bind(blob), chk = CheckField.bind(blob); - return [read, chk]; } /* [MS-CFB] v20130118 */ -var CFB = (function(){ -this.version = '0.9.1'; +var CFB = (function _CFB(){ +var exports = {}; +exports.version = '0.10.0'; function parse(file) { - - var mver = 3; // major version var ssz = 512; // sector size -var mssz = 64; // mini sector size -var nds = 0; // number of directory sectors -var nfs = 0; // number of FAT sectors var nmfs = 0; // number of mini FAT sectors var ndfs = 0; // number of DIFAT sectors var dir_start = 0; // first directory sector location var minifat_start = 0; // first mini FAT sector location var difat_start = 0; // first mini FAT sector location -var ms_cutoff_size = 4096; // mini stream cutoff size -var minifat_store = 0; // first sector with minifat data -var minifat_size = 0; // size of minifat data - var fat_addrs = []; // locations of FAT sectors /* [MS-CFB] 2.2 Compound File Header */ var blob = file.slice(0,512); -prep_blob(blob); -var read = ReadShift.bind(blob), chk = CheckField.bind(blob); -var j = 0, q; +prep_blob(blob, 0); -// header signature 8 -chk(HEADER_SIGNATURE, 'Header Signature: '); - -// clsid 16 -chk(HEADER_CLSID, 'CLSID: '); - -// minor version 2 -read(2); - -// major version 3 -mver = read(2); +/* major version */ +mver = check_get_mver(blob); switch(mver) { - case 3: ssz = 512; break; - case 4: ssz = 4096; break; + case 3: ssz = 512; break; case 4: ssz = 4096; break; default: throw "Major Version: Expected 3 or 4 saw " + mver; } -// reprocess header -var pos = blob.l; -blob = file.slice(0,ssz); -prep_blob(blob,pos); -read = ReadShift.bind(blob); -chk = CheckField.bind(blob); +/* reprocess header */ +if(ssz !== 512) { blob = file.slice(0,ssz); prep_blob(blob, 28 /* blob.l */); } +/* Save header for final object */ var header = file.slice(0,ssz); -// Byte Order TODO -chk('feff', 'Byte Order: '); - -// Sector Shift -switch((q = read(2))) { - case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; - case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; - default: throw 'Sector Shift: Expected 9 or 12 saw ' + q; -} - -// Mini Sector Shift -chk('0600', 'Mini Sector Shift: '); - -// Reserved -chk('000000000000', 'Mini Sector Shift: '); +check_shifts(blob, mver); // Number of Directory Sectors -nds = read(4); +var nds = blob.read_shift(4, 'i'); if(mver === 3 && nds !== 0) throw '# Directory Sectors: Expected 0 saw ' + nds; // Number of FAT Sectors -nfs = read(4); +//var nfs = blob.read_shift(4, 'i'); +blob.l += 4; // First Directory Sector Location -dir_start = read(4); +dir_start = blob.read_shift(4, 'i'); -// Transaction Signature TODO -read(4); +// Transaction Signature +blob.l += 4; -// Mini Stream Cutoff Size TODO -chk('00100000', 'Mini Stream Cutoff Size: '); +// Mini Stream Cutoff Size +blob.chk('00100000', 'Mini Stream Cutoff Size: '); // First Mini FAT Sector Location -minifat_start = read(4); +minifat_start = blob.read_shift(4, 'i'); // Number of Mini FAT Sectors -nmfs = read(4); +nmfs = blob.read_shift(4, 'i'); // First DIFAT sector location -difat_start = read(4); +difat_start = blob.read_shift(4, 'i'); // Number of DIFAT Sectors -ndfs = read(4); +ndfs = blob.read_shift(4, 'i'); // Grab FAT Sector Locations -for(j = 0; blob.l != 512; ) { - if((q = read(4))>=MAXREGSECT) break; - fat_addrs[j++] = q; +for(var q, j = 0; j < 109; ++j) { /* 109 = (512 - blob.l)>>>2; */ + q = blob.read_shift(4, 'i'); + if(q<0) break; + fat_addrs[j] = q; } - /** Break the file up into sectors */ -var nsectors = Math.ceil((file.length - ssz)/ssz); -var sectors = []; -for(var i=1; i != nsectors; ++i) sectors[i-1] = file.slice(i*ssz,(i+1)*ssz); -sectors[nsectors-1] = file.slice(nsectors*ssz); +var sectors = sectorify(file, ssz); -/** Chase down the rest of the DIFAT chain to build a comprehensive list - DIFAT chains by storing the next sector number as the last 32 bytes */ -function sleuth_fat(idx, cnt) { - if(idx === ENDOFCHAIN) { - if(cnt !== 0) throw "DIFAT chain shorter than expected"; - return; - } - if(idx !== FREESECT) { - var sector = sectors[idx]; - for(var i = 0; i != ssz/4-1; ++i) { - if((q = __readUInt32LE(sector,i*4)) === ENDOFCHAIN) break; - fat_addrs.push(q); - } - sleuth_fat(__readUInt32LE(sector,ssz-4),cnt - 1); - } -} -sleuth_fat(difat_start, ndfs); - -/** DONT CAT THE FAT! Just calculate where we need to go */ -function get_buffer(byte_addr, bytes) { - var addr = fat_addrs[Math.floor(byte_addr*4/ssz)]; - if(ssz - (byte_addr*4 % ssz) < (bytes || 0)) throw "FAT boundary crossed: " + byte_addr + " "+bytes+" "+ssz; - return sectors[addr].slice((byte_addr*4 % ssz)); -} - -function get_buffer_u32(byte_addr) { - return __readUInt32LE(get_buffer(byte_addr,4), 0); -} - -function get_next_sector(idx) { return get_buffer_u32(idx); } +sleuth_fat(difat_start, ndfs, sectors, ssz, fat_addrs); /** Chains */ -var chkd = new Array(sectors.length), sector_list = []; -var get_sector = function get_sector(k) { return sectors[k]; }; -for(i=0; i != sectors.length; ++i) { - var buf = [], k = (i + dir_start) % sectors.length; - if(chkd[k]) continue; - for(j=k; j<=MAXREGSECT; buf.push(j),j=get_next_sector(j)) chkd[j] = true; - sector_list[k] = {nodes: buf}; - sector_list[k].data = __toBuffer(Array(buf.map(get_sector))); -} +var sector_list = make_sector_list(sectors, dir_start, fat_addrs, ssz); + sector_list[dir_start].name = "!Directory"; if(nmfs > 0 && minifat_start !== ENDOFCHAIN) sector_list[minifat_start].name = "!MiniFAT"; sector_list[fat_addrs[0]].name = "!FAT"; /* [MS-CFB] 2.6.1 Compound File Directory Entry */ var files = {}, Paths = [], FileIndex = [], FullPaths = [], FullPathDir = {}; -function read_directory(idx) { - var blob, read, w; - var sector = sector_list[idx].data; - for(var i = 0; i != sector.length; i+= 128) { - blob = sector.slice(i, i+128); - prep_blob(blob, 64); - read = ReadShift.bind(blob); - var namelen = read(2); - if(namelen === 0) return; - var name = __utf16le(blob,0,namelen-(Paths.length?2:0)); // OLE - Paths.push(name); - var o = { name: name }; - o.type = EntryTypes[read(1)]; - o.color = read(1); - o.left = read(4); if(o.left === NOSTREAM) delete o.left; - o.right = read(4); if(o.right === NOSTREAM) delete o.right; - o.child = read(4); if(o.child === NOSTREAM) delete o.child; - o.clsid = read(16); - o.state = read(4); - var ctime = read(8); if(ctime != "0000000000000000") o.ctime = ctime; - var mtime = read(8); if(mtime != "0000000000000000") o.mtime = mtime; - o.start = read(4); - o.size = read(4); - if(o.type === 'root') { //root entry - minifat_store = o.start; - if(nmfs > 0 && minifat_store !== ENDOFCHAIN) sector_list[minifat_store].name = "!StreamData"; - minifat_size = o.size; - } else if(o.size >= ms_cutoff_size) { - o.storage = 'fat'; - if(!sector_list[o.start] && dir_start > 0) o.start = (o.start + dir_start) % sectors.length; - sector_list[o.start].name = o.name; - o.content = sector_list[o.start].data.slice(0,o.size); - prep_blob(o.content); - } else { - o.storage = 'minifat'; - w = o.start * mssz; - if(minifat_store !== ENDOFCHAIN && o.start !== ENDOFCHAIN) { - o.content = sector_list[minifat_store].data.slice(w,w+o.size); - prep_blob(o.content); - } - } - if(o.ctime) { - var ct = blob.slice(blob.l-24, blob.l-16); - var c2 = (__readUInt32LE(ct,4)/1e7)*Math.pow(2,32)+__readUInt32LE(ct,0)/1e7; - o.ct = new Date((c2 - 11644473600)*1000); - } - if(o.mtime) { - var mt = blob.slice(blob.l-16, blob.l-8); - var m2 = (__readUInt32LE(mt,4)/1e7)*Math.pow(2,32)+__readUInt32LE(mt,0)/1e7; - o.mt = new Date((m2 - 11644473600)*1000); - } - files[name] = o; - FileIndex.push(o); - } -} -read_directory(dir_start); +read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex); -/* [MS-CFB] 2.6.4 Red-Black Tree */ -function build_full_paths(Dir, pathobj, paths, patharr) { - var i; - var dad = new Array(patharr.length); - - var q = new Array(patharr.length); - - for(i=0; i != dad.length; ++i) { dad[i]=q[i]=i; paths[i]=patharr[i]; } - - while(q.length > 0) { - for(i = q[0]; typeof i !== "undefined"; i = q.shift()) { - if(dad[i] === i) { - if(Dir[i].left && dad[Dir[i].left] != Dir[i].left) dad[i] = dad[Dir[i].left]; - if(Dir[i].right && dad[Dir[i].right] != Dir[i].right) dad[i] = dad[Dir[i].right]; - } - if(Dir[i].child) dad[Dir[i].child] = i; - if(Dir[i].left) { dad[Dir[i].left] = dad[i]; q.push(Dir[i].left); } - if(Dir[i].right) { dad[Dir[i].right] = dad[i]; q.push(Dir[i].right); } - } - for(i=1; i != dad.length; ++i) if(dad[i] === i) { - if(Dir[i].right && dad[Dir[i].right] != Dir[i].right) dad[i] = dad[Dir[i].right]; - else if(Dir[i].left && dad[Dir[i].left] != Dir[i].left) dad[i] = dad[Dir[i].left]; - } - } - - for(i=1; i !== paths.length; ++i) { - if(Dir[i].type === "unknown") continue; - var j = dad[i]; - if(j === 0) paths[i] = paths[0] + "/" + paths[i]; - else while(j !== 0) { - paths[i] = paths[j] + "/" + paths[i]; - j = dad[j]; - } - dad[i] = 0; - } - - paths[0] += "/"; - for(i=1; i !== paths.length; ++i) if(Dir[i].type !== 'stream') paths[i] += "/"; - for(i=0; i !== paths.length; ++i) pathobj[paths[i]] = FileIndex[i]; -} build_full_paths(FileIndex, FullPathDir, FullPaths, Paths); var root_name = Paths.shift(); Paths.root = root_name; /* [MS-CFB] 2.6.4 (Unicode 3.0.1 case conversion) */ -function find_path(path) { - if(path[0] === "/") path = root_name + path; - var UCNames = (path.indexOf("/") !== -1 ? FullPaths : Paths).map(function(x) { return x.toUpperCase(); }); - var UCPath = path.toUpperCase(); - var w = UCNames.indexOf(UCPath); - if(w === -1) return null; - return path.indexOf("/") !== -1 ? FileIndex[w] : files[Paths[w]]; -} +var find_path = make_find_path(FullPaths, Paths, FileIndex, files, root_name); -var rval = { +return { raw: {header: header, sectors: sectors}, FileIndex: FileIndex, FullPaths: FullPaths, FullPathDir: FullPathDir, find: find_path }; - -return rval; } // parse +/* [MS-CFB] 2.2 Compound File Header -- read up to major version */ +function check_get_mver(blob) { + // header signature 8 + blob.chk(HEADER_SIGNATURE, 'Header Signature: '); + // clsid 16 + blob.chk(HEADER_CLSID, 'CLSID: '); + + // minor version 2 + blob.l += 2; + + return blob.read_shift(2,'u'); +} +function check_shifts(blob, mver) { + var shift = 0x09; + + // Byte Order + blob.chk('feff', 'Byte Order: '); + + // Sector Shift + switch((shift = blob.read_shift(2))) { + case 0x09: if(mver !== 3) throw 'MajorVersion/SectorShift Mismatch'; break; + case 0x0c: if(mver !== 4) throw 'MajorVersion/SectorShift Mismatch'; break; + default: throw 'Sector Shift: Expected 9 or 12 saw ' + shift; + } + + // Mini Sector Shift + blob.chk('0600', 'Mini Sector Shift: '); + + // Reserved + blob.chk('000000000000', 'Reserved: '); +} + +/** Break the file up into sectors */ +function sectorify(file, ssz) { + var nsectors = Math.ceil(file.length/ssz)-1; + var sectors = new Array(nsectors); + for(var i=1; i < nsectors; ++i) sectors[i-1] = file.slice(i*ssz,(i+1)*ssz); + sectors[nsectors-1] = file.slice(nsectors*ssz); + return sectors; +} + +/* [MS-CFB] 2.6.4 Red-Black Tree */ +function build_full_paths(FI, FPD, FP, Paths) { + var i = 0, L = 0, R = 0, C = 0, j = 0, pl = Paths.length; + var dad = new Array(pl), q = new Array(pl); + + for(; i < pl; ++i) { dad[i]=q[i]=i; FP[i]=Paths[i]; } + + for(; j < q.length; ++j) { + i = q[j]; + L = FI[i].L; R = FI[i].R; C = FI[i].C; + if(dad[i] === i) { + if(L !== -1 /*NOSTREAM*/ && dad[L] !== L) dad[i] = dad[L]; + if(R !== -1 && dad[R] !== R) dad[i] = dad[R]; + } + if(C !== -1 /*NOSTREAM*/) dad[C] = i; + if(L !== -1) { dad[L] = dad[i]; q.push(L); } + if(R !== -1) { dad[R] = dad[i]; q.push(R); } + } + for(i=1; i !== pl; ++i) if(dad[i] === i) { + if(R !== -1 /*NOSTREAM*/ && dad[R] !== R) dad[i] = dad[R]; + else if(L !== -1 && dad[L] !== L) dad[i] = dad[L]; + } + + for(i=1; i < pl; ++i) { + if(FI[i].type === 0 /* unknown */) continue; + j = dad[i]; + if(j === 0) FP[i] = FP[0] + "/" + FP[i]; + else while(j !== 0) { + FP[i] = FP[j] + "/" + FP[i]; + j = dad[j]; + } + dad[i] = 0; + } + + FP[0] += "/"; + for(i=1; i < pl; ++i) { + if(FI[i].type !== 2 /* stream */) FP[i] += "/"; + FPD[FP[i]] = FI[i]; + } +} + +/* [MS-CFB] 2.6.4 */ +function make_find_path(FullPaths, Paths, FileIndex, files, root_name) { + var UCFullPaths = new Array(FullPaths.length); + var UCPaths = new Array(Paths.length), i; + for(i = 0; i < FullPaths.length; ++i) UCFullPaths[i] = FullPaths[i].toUpperCase(); + for(i = 0; i < Paths.length; ++i) UCPaths[i] = Paths[i].toUpperCase(); + return function find_path(path) { + var k; + if(path.charCodeAt(0) === 47 /* "/" */) { k=true; path = root_name + path; } + else k = path.indexOf("/") !== -1; + var UCPath = path.toUpperCase(); + var w = k === true ? UCFullPaths.indexOf(UCPath) : UCPaths.indexOf(UCPath); + if(w === -1) return null; + return k === true ? FileIndex[w] : files[Paths[w]]; + }; +} + +/** Chase down the rest of the DIFAT chain to build a comprehensive list + DIFAT chains by storing the next sector number as the last 32 bytes */ +function sleuth_fat(idx, cnt, sectors, ssz, fat_addrs) { + var q; + if(idx === ENDOFCHAIN) { + if(cnt !== 0) throw "DIFAT chain shorter than expected"; + } else if(idx !== -1 /*FREESECT*/) { + var sector = sectors[idx], m = (ssz>>>2)-1; + for(var i = 0; i < m; ++i) { + if((q = __readInt32LE(sector,i*4)) === ENDOFCHAIN) break; + fat_addrs.push(q); + } + sleuth_fat(__readInt32LE(sector,ssz-4),cnt - 1, sectors, ssz, fat_addrs); + } +} + +/** Chase down the sector linked lists */ +function make_sector_list(sectors, dir_start, fat_addrs, ssz) { + var sl = sectors.length, sector_list = new Array(sl); + var chkd = new Array(sl), buf, buf_chain; + var modulus = ssz - 1, i, j, k, jj; + for(i=0; i < sl; ++i) { + buf = []; + k = (i + dir_start); if(k >= sl) k-=sl; + if(chkd[k] === true) continue; + buf_chain = []; + for(j=k; j>=0;) { + chkd[j] = true; + buf[buf.length] = j; + buf_chain.push(sectors[j]); + var addr = fat_addrs[Math.floor(j*4/ssz)]; + jj = ((j*4) & modulus); + if(ssz < 4 + jj) throw "FAT boundary crossed: " + j + " 4 "+ssz; + j = __readInt32LE(sectors[addr], jj); + } + sector_list[k] = {nodes: buf, data:__toBuffer([buf_chain])}; + } + return sector_list; +} + +/* [MS-CFB] 2.6.1 Compound File Directory Entry */ +function read_directory(dir_start, sector_list, sectors, Paths, nmfs, files, FileIndex) { + var blob; + var minifat_store = 0, pl = (Paths.length?2:0); + var sector = sector_list[dir_start].data; + var i = 0, namelen = 0, name, o, ctime, mtime; + for(; i < sector.length; i+= 128) { + blob = sector.slice(i, i+128); + prep_blob(blob, 64); + namelen = blob.read_shift(2); + if(namelen === 0) continue; + name = __utf16le(blob,0,namelen-pl); + Paths.push(name); + o = { + name: name, + type: blob.read_shift(1), + color: blob.read_shift(1), + L: blob.read_shift(4, 'i'), + R: blob.read_shift(4, 'i'), + C: blob.read_shift(4, 'i'), + clsid: blob.read_shift(16), + state: blob.read_shift(4, 'i') + }; + ctime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2); + if(ctime !== 0) { + o.ctime = ctime; o.ct = read_date(blob, blob.l-8); + } + mtime = blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2) + blob.read_shift(2); + if(mtime !== 0) { + o.mtime = mtime; o.mt = read_date(blob, blob.l-8); + } + o.start = blob.read_shift(4, 'i'); + o.size = blob.read_shift(4, 'i'); + if(o.type === 5) { /* root */ + minifat_store = o.start; + if(nmfs > 0 && minifat_store !== ENDOFCHAIN) sector_list[minifat_store].name = "!StreamData"; + /*minifat_size = o.size;*/ + } else if(o.size >= 4096 /* MSCSZ */) { + o.storage = 'fat'; + if(sector_list[o.start] === undefined) if((o.start+=dir_start)>=sectors.length) o.start-=sectors.length; + sector_list[o.start].name = o.name; + o.content = sector_list[o.start].data.slice(0,o.size); + prep_blob(o.content, 0); + } else { + o.storage = 'minifat'; + if(minifat_store !== ENDOFCHAIN && o.start !== ENDOFCHAIN) { + o.content = sector_list[minifat_store].data.slice(o.start*MSSZ,o.start*MSSZ+o.size); + prep_blob(o.content, 0); + } + } + files[name] = o; + FileIndex.push(o); + } +} + +function read_date(blob, offset) { + return new Date(( ( (__readUInt32LE(blob,offset+4)/1e7)*Math.pow(2,32)+__readUInt32LE(blob,offset)/1e7 ) - 11644473600)*1000); +} + +var fs; function readFileSync(filename) { - var fs = require('fs'); - var file = fs.readFileSync(filename); - return parse(file); + if(fs === undefined) fs = require('fs'); + return parse(fs.readFileSync(filename)); } function readSync(blob, options) { - var o = options || {}; - switch((o.type || "base64")) { + switch(options !== undefined && options.type !== undefined ? options.type : "base64") { case "file": return readFileSync(blob); - case "base64": blob = Base64.decode(blob); - /* falls through */ - case "binary": blob = s2a(blob); break; + case "base64": return parse(s2a(Base64.decode(blob))); + case "binary": return parse(s2a(blob)); } return parse(blob); } -this.read = readSync; -this.parse = parse; -return this; -})(); - /** CFB Constants */ -{ +var MSSZ = 64; /* Mini Sector Size = 1<<6 */ +//var MSCSZ = 4096; /* Mini Stream Cutoff Size */ +/* 2.1 Compound File Sector Numbers and Types */ +var ENDOFCHAIN = -2; +/* 2.2 Compound File Header */ +var HEADER_SIGNATURE = 'd0cf11e0a1b11ae1'; +var HEADER_CLSID = '00000000000000000000000000000000'; +var consts = { /* 2.1 Compund File Sector Numbers and Types */ - var MAXREGSECT = 0xFFFFFFFA; - var DIFSECT = 0xFFFFFFFC; - var FATSECT = 0xFFFFFFFD; - var ENDOFCHAIN = 0xFFFFFFFE; - var FREESECT = 0xFFFFFFFF; + MAXREGSECT: -6, + DIFSECT: -4, + FATSECT: -3, + ENDOFCHAIN: ENDOFCHAIN, + FREESECT: -1, /* 2.2 Compound File Header */ - var HEADER_SIGNATURE = 'd0cf11e0a1b11ae1'; - var HEADER_MINOR_VERSION = '3e00'; - var MAXREGSID = 0xFFFFFFFA; - var NOSTREAM = 0xFFFFFFFF; - var HEADER_CLSID = '00000000000000000000000000000000'; + HEADER_SIGNATURE: HEADER_SIGNATURE, + HEADER_MINOR_VERSION: '3e00', + MAXREGSID: -6, + NOSTREAM: -1, + HEADER_CLSID: HEADER_CLSID, /* 2.6.1 Compound File Directory Entry */ - var EntryTypes = ['unknown','storage','stream','lockbytes','property','root']; -} + EntryTypes: ['unknown','storage','stream','lockbytes','property','root'] +}; -var CFB_utils = { +exports.read = readSync; +exports.parse = parse; +exports.utils = { ReadShift: ReadShift, CheckField: CheckField, prep_blob: prep_blob, - bconcat: bconcat + bconcat: bconcat, + consts: consts }; -if(typeof require !== 'undefined' && typeof exports !== 'undefined') { - var fs = require('fs'); - exports.read = CFB.read; - exports.parse = CFB.parse; - exports.utils = CFB_utils; - exports.version = CFB.version; -} +return exports; +})(); + +if(typeof require !== 'undefined' && typeof module !== 'undefined' && typeof DO_NOT_EXPORT_CFB === 'undefined') { module.exports = CFB; } diff --git a/misc/prof.js b/misc/prof.js new file mode 100644 index 0000000..97f0e43 --- /dev/null +++ b/misc/prof.js @@ -0,0 +1,5 @@ +var describe = function(m,cb){if(cb) cb();}; +describe.skip = function(m,cb){}; +var it = function(m,cb){if(cb) cb();}; +it.skip = function(m,cb){}; +var before = function(cb){if(cb) cb();}; diff --git a/misc/spin.sh b/misc/spin.sh new file mode 100755 index 0000000..9951a57 --- /dev/null +++ b/misc/spin.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# spin.sh -- show a spinner (for coverage test) +# Copyright (C) 2014 SheetJS + +wpid=$1 +delay=1 +str="|/-\\" +while [ $(ps -a|awk '$1=='$wpid' {print $1}') ]; do + t=${str#?} + printf " [%c]" "$str" + str=$t${str%"$t"} + sleep $delay + printf "\b\b\b\b" +done diff --git a/package.json b/package.json index 433bfc2..7a50a98 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,32 @@ { "name": "cfb", - "version": "0.9.1", - "author": "SheetJS", + "version": "0.10.0", + "author": "sheetjs", "description": "Compound File Binary File Format extractor", "keywords": [ "cfb", "compression", "office" ], "bin": { "cfb": "./bin/cfb" }, "main": "./cfb", - "repository": { "type":"git", "url":"git://github.com/SheetJS/js-cfb.git" }, - "scripts": { - "test": "make test" - }, "dependencies": { "commander":"" }, + "devDependencies": { + "mocha":"", + "xlsjs":"", + "uglify-js":"" + }, + "repository": { "type":"git", "url":"git://github.com/SheetJS/js-cfb.git" }, + "scripts": { + "pretest": "make init", + "test": "make test" + }, "config": { "blanket": { "pattern": "cfb.js" } }, "bugs": { "url": "https://github.com/SheetJS/js-cfb/issues" }, - "license": "Apache 2.0" + "license": "Apache-2.0", + "engines": { "node": ">=0.8" } } diff --git a/test.js b/test.js index 7bf1b36..ba104f7 100644 --- a/test.js +++ b/test.js @@ -3,10 +3,11 @@ var CFB; var fs = require('fs'); describe('source', function() { it('should load', function() { CFB = require('./'); }); }); +var ffunc = function(x){return x.substr(-4)==".xls" && fails.indexOf(x) === -1;}; var fails = fs.existsSync('./fails.lst') ? fs.readFileSync('./fails.lst', 'utf-8').split("\n") : []; -var files = fs.readdirSync('test_files').filter(function(x){return x.substr(-4)==".xls" && fails.indexOf(x) === -1;}); -var f2011 = fs.readdirSync('test_files/2011').filter(function(x){return x.substr(-4)==".xls" && fails.indexOf(x) === -1;}); -var f2013 = fs.readdirSync('test_files/2013').filter(function(x){return x.substr(-4)==".xls" && fails.indexOf(x) === -1;}); +var files = fs.readdirSync('test_files').filter(ffunc); +var f2011 = fs.readdirSync('test_files/2011').filter(ffunc); +var f2013 = fs.readdirSync('test_files/2013').filter(ffunc); var dir = "./test_files/"; @@ -19,8 +20,6 @@ function parsetest(x, cfb) { if(!cfb.find('/Workbook') && !cfb.find('/Book')) throw new Error("Cannot find workbook for " + x); }); }); - describe(x + ' should ', function() { - }); } describe('should parse test files', function() { @@ -53,4 +52,7 @@ describe('input formats', function() { it('should read base64 strings', function() { CFB.read(fs.readFileSync(dir + '/' + cp, 'base64'), {type: 'base64'}); }); + it('should read buffers', function() { + CFB.read(fs.readFileSync(dir + '/' + cp), {type: 'buffer'}); + }); });