diff --git a/.gitignore b/.gitignore index 296e217..ad682f7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ test_files_pres *.[eE][tT][hH] *.[zZ][iI][pP] *.[mM][sS][iIgG] +*.[mM][hH][tT] *.123 *.htm *.html diff --git a/.travis.yml b/.travis.yml index 1ca55c3..b5b4997 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: node_js +dist: xenial node_js: + - "14" + - "13" - "12" - "11" - "10" @@ -13,12 +16,15 @@ node_js: - "0.10" - "0.8" before_install: - - "npm install -g npm@4.3.0" + - "npm config set strict-ssl false" + - "./misc/node_version.sh" - "npm install -g mocha@2.x voc" - "npm install blanket" - - "npm install xlsjs crc-32" + - "npm install word crc-32" - "npm install coveralls mocha-lcov-reporter" before_script: - "make init" +install: + - npm install after_success: - "make coveralls-spin" diff --git a/CHANGELOG.md b/CHANGELOG.md index c65a3a3..2ff5950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ This log is intended to keep track of backwards-incompatible changes, including but not limited to API changes and file location changes. Minor behavioral changes may not be included if they are not expected to break existing code. +## 1.2.0 (2020-07-09) + +* Support for MAD file format (MIME aggregate document) +* Spun off the CLI tool to the `cfb-cli` module + ## 1.1.0 (2018-09-04) * Support for ZIP file format diff --git a/Makefile b/Makefile index 4b778de..068b4b0 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ FMT=xls doc ppt misc full REQS= ADDONS= AUXTARGETS=xlscfb.js -CMDS=bin/cfb.njs +CMDS=packages/cfb-cli/bin/cfb.njs HTMLLINT=index.html ULIB=$(shell echo $(LIB) | tr a-z A-Z) diff --git a/README.md b/README.md index 8255369..3967f4a 100644 --- a/README.md +++ b/README.md @@ -44,28 +44,8 @@ var data = workbook.content; ## Command-Line Utility Usage -It is preferable to install the library globally with npm: - -```bash -$ npm install -g cfb -``` - -The global installation adds a command `cfb` which can work with files: - -- `cfb file [names...]` extracts the contents of the file. If additional names - are supplied, only the listed files will be extracted. - -- `cfb -l file` lists the contained files (following `unzip -l` "short format") - -- `cfb -r file` attempts to repair by reading and re-writing the file. - This fixes some issues with files generated by non-standard tools. - -- `cfb -c file [files...]` creates a new file containing the listed files. - The default root entry name is `Root Entry`. - -- `cfb -a file [files...]` adds the listed files to the original file. - -- `cfb -d file [files...]` deletes the listed files from the original file. +The [`cfb-cli`](https://www.npmjs.com/package/cfb-cli) module ships with a CLI +tool for manipulating and inspecting supported files. ## JS API @@ -113,10 +93,11 @@ name, if there are no slashes) and returns an entry object or null if not found. `opts.fileType` controls the output file type: -| `fileType` | output | -|:-------------------|:--------------| -| `'cfb'` (default) | CFB container | -| `'zip'` | ZIP file | +| `fileType` | output | +|:-------------------|:------------------------| +| `'cfb'` (default) | CFB container | +| `'zip'` | ZIP file | +| `'mad'` | MIME aggregate document | `opts.compression` enables DEFLATE compression for ZIP file type. @@ -157,6 +138,7 @@ interface CFBEntry { content: Buffer | number[] | Uint8Array; /** Raw Content */ ct?: Date; /** Creation Time */ mt?: Date; /** Modification Time */ + ctype?: String; /** Content-Type (for MAD) */ } ``` @@ -172,4 +154,6 @@ granted by the Apache 2.0 License are reserved by the Original Author. - `MS-CFB`: Compound File Binary File Format - ZIP `APPNOTE.TXT`: .ZIP File Format Specification - RFC1951: https://www.ietf.org/rfc/rfc1951.txt + - RFC2045: https://www.ietf.org/rfc/rfc2045.txt + - RFC2557: https://www.ietf.org/rfc/rfc2557.txt diff --git a/bits/05_buf.js b/bits/05_buf.js index d0a0fd3..217e5ac 100644 --- a/bits/05_buf.js +++ b/bits/05_buf.js @@ -24,7 +24,7 @@ function new_unsafe_buf(len/*:number*/) { /* jshint +W056 */ } -var s2a = function s2a(s/*:string*/)/*:any*/ { +var s2a = function s2a(s/*:string*/)/*:RawBytes*/ { if(has_buf) return Buffer_from(s, "binary"); return s.split("").map(function(x/*:string*/)/*:number*/{ return x.charCodeAt(0) & 0xff; }); }; diff --git a/bits/31_version.js b/bits/31_version.js index 96a8b06..79633f6 100644 --- a/bits/31_version.js +++ b/bits/31_version.js @@ -1 +1 @@ -exports.version = '1.1.4'; +exports.version = '1.2.0'; diff --git a/bits/40_parse.js b/bits/40_parse.js index 633e2b3..3631e24 100644 --- a/bits/40_parse.js +++ b/bits/40_parse.js @@ -1,5 +1,6 @@ function parse(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options); +if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options); if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512"); var mver = 3; var ssz = 512; diff --git a/bits/60_writehead.js b/bits/60_writehead.js index 47e5bc8..1ba952e 100644 --- a/bits/60_writehead.js +++ b/bits/60_writehead.js @@ -1,4 +1,9 @@ -function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ { +function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var _opts = options || {}; + /* MAD is order-sensitive, skip rebuild and sort */ + if(_opts.fileType == 'mad') return write_mad(cfb, _opts); rebuild_cfb(cfb); - if(_opts.fileType == 'zip') return write_zip(cfb, _opts); + switch(_opts.fileType) { + case 'zip': return write_zip(cfb, _opts); + //case 'mad': return write_mad(cfb, _opts); + } diff --git a/bits/77_writeutils.js b/bits/77_writeutils.js index 9527cda..3ebb9d2 100644 --- a/bits/77_writeutils.js +++ b/bits/77_writeutils.js @@ -13,10 +13,13 @@ function a2s(o/*:RawBytes*/)/*:string*/ { function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } diff --git a/bits/84_mht.js b/bits/84_mht.js new file mode 100644 index 0000000..8edb6db --- /dev/null +++ b/bits/84_mht.js @@ -0,0 +1,206 @@ +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}/*:any*/); + +function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr/*:string*/)/*:string*/ { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +*/ = [], split = encoded.split("\r\n"); + for(var si = 0; si < split.length; ++si) { + var str = split[si]; + if(str.length == 0) { o.push(""); continue; } + for(var i = 0; i < str.length;) { + var end = 76; + var tmp = str.slice(i, i + end); + if(tmp.charAt(end - 1) == "=") end --; + else if(tmp.charAt(end - 2) == "=") end -= 2; + else if(tmp.charAt(end - 3) == "=") end -= 3; + tmp = str.slice(i, i + end); + i += end; + if(i < str.length) tmp += "="; + o.push(tmp); + } + } + + return o.join("\r\n"); +} +function parse_quoted_printable(data/*:Array*/)/*:RawBytes*/ { + var o = []; + + /* unify long lines */ + for(var di = 0; di < data.length; ++di) { + var line = data[di]; + while(di <= data.length && line.charAt(line.length - 1) == "=") line = line.slice(0, line.length - 1) + data[++di]; + o.push(line); + } + + /* decode */ + for(var oi = 0; oi < o.length; ++oi) o[oi] = o[oi].replace(/=[0-9A-Fa-f]{2}/g, function($$) { return String.fromCharCode(parseInt($$.slice(1), 16)); }); + return s2a(o.join("\r\n")); +} + + +function parse_mime(cfb/*:CFBContainer*/, data/*:Array*/, root/*:string*/)/*:void*/ { + var fname = "", cte = "", ctype = "", fdata; + var di = 0; + for(;di < 10; ++di) { + var line = data[di]; + if(!line || line.match(/^\s*$/)) break; + var m = line.match(/^(.*?):\s*([^\s].*)$/); + if(m) switch(m[1].toLowerCase()) { + case "content-location": fname = m[2].trim(); break; + case "content-type": ctype = m[2].trim(); break; + case "content-transfer-encoding": cte = m[2].trim(); break; + } + } + ++di; + switch(cte.toLowerCase()) { + case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break; + case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break; + default: throw new Error("Unsupported Content-Transfer-Encoding " + cte); + } + var file = cfb_add(cfb, fname.slice(root.length), fdata, {unsafe: true}); + if(ctype) file.ctype = ctype; +} + +function parse_mad(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { + if(a2s(file.slice(0,13)).toLowerCase() != "mime-version:") throw new Error("Unsupported MAD header"); + var root = (options && options.root || ""); + // $FlowIgnore + var data = (has_buf && Buffer.isBuffer(file) ? file.toString("binary") : a2s(file)).split("\r\n"); + var di = 0, row = ""; + + /* if root is not specified, scan for the common prefix */ + for(di = 0; di < data.length; ++di) { + row = data[di]; + if(!/^Content-Location:/i.test(row)) continue; + row = row.slice(row.indexOf("file")); + if(!root) root = row.slice(0, row.lastIndexOf("/") + 1); + if(row.slice(0, root.length) == root) continue; + while(root.length > 0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array*/ = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:string*/ { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +} \ No newline at end of file diff --git a/cfb.flow.js b/cfb.flow.js index 5eaa3e9..b755124 100644 --- a/cfb.flow.js +++ b/cfb.flow.js @@ -74,7 +74,7 @@ function new_unsafe_buf(len/*:number*/) { /* jshint +W056 */ } -var s2a = function s2a(s/*:string*/)/*:any*/ { +var s2a = function s2a(s/*:string*/)/*:RawBytes*/ { if(has_buf) return Buffer_from(s, "binary"); return s.split("").map(function(x/*:string*/)/*:number*/{ return x.charCodeAt(0) & 0xff; }); }; @@ -295,7 +295,7 @@ CRC32.str = crc32_str; /* [MS-CFB] v20171201 */ var CFB = (function _CFB(){ var exports/*:CFBModule*/ = /*::(*/{}/*:: :any)*/; -exports.version = '1.1.4'; +exports.version = '1.2.0'; /* [MS-CFB] 2.6.4 */ function namecmp(l/*:string*/, r/*:string*/)/*:number*/ { var L = l.split("/"), R = r.split("/"); @@ -385,6 +385,7 @@ var fs/*:: = require('fs'); */; function get_fs() { return fs || (fs = require('fs')); } function parse(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options); +if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options); if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512"); var mver = 3; var ssz = 512; @@ -803,10 +804,15 @@ function rebuild_cfb(cfb/*:CFBContainer*/, f/*:?boolean*/)/*:void*/ { } -function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ { +function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var _opts = options || {}; + /* MAD is order-sensitive, skip rebuild and sort */ + if(_opts.fileType == 'mad') return write_mad(cfb, _opts); rebuild_cfb(cfb); - if(_opts.fileType == 'zip') return write_zip(cfb, _opts); + switch(_opts.fileType) { + case 'zip': return write_zip(cfb, _opts); + //case 'mad': return write_mad(cfb, _opts); + } var L = (function(cfb/*:CFBContainer*/)/*:Array*/{ var mini_size = 0, fat_size = 0; for(var i = 0; i < cfb.FileIndex.length; ++i) { @@ -1000,10 +1006,13 @@ function a2s(o/*:RawBytes*/)/*:string*/ { function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } @@ -1552,7 +1561,212 @@ function write_zip(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ return bconcat(([bconcat((out/*:any*/)), bconcat(cdirs), o]/*:any*/)); } -function cfb_new(opts/*:?any*/)/*:CFBContainer*/ { +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}/*:any*/); + +function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr/*:string*/)/*:string*/ { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +
*/ = [], split = encoded.split("\r\n"); + for(var si = 0; si < split.length; ++si) { + var str = split[si]; + if(str.length == 0) { o.push(""); continue; } + for(var i = 0; i < str.length;) { + var end = 76; + var tmp = str.slice(i, i + end); + if(tmp.charAt(end - 1) == "=") end --; + else if(tmp.charAt(end - 2) == "=") end -= 2; + else if(tmp.charAt(end - 3) == "=") end -= 3; + tmp = str.slice(i, i + end); + i += end; + if(i < str.length) tmp += "="; + o.push(tmp); + } + } + + return o.join("\r\n"); +} +function parse_quoted_printable(data/*:Array*/)/*:RawBytes*/ { + var o = []; + + /* unify long lines */ + for(var di = 0; di < data.length; ++di) { + var line = data[di]; + while(di <= data.length && line.charAt(line.length - 1) == "=") line = line.slice(0, line.length - 1) + data[++di]; + o.push(line); + } + + /* decode */ + for(var oi = 0; oi < o.length; ++oi) o[oi] = o[oi].replace(/=[0-9A-Fa-f]{2}/g, function($$) { return String.fromCharCode(parseInt($$.slice(1), 16)); }); + return s2a(o.join("\r\n")); +} + + +function parse_mime(cfb/*:CFBContainer*/, data/*:Array*/, root/*:string*/)/*:void*/ { + var fname = "", cte = "", ctype = "", fdata; + var di = 0; + for(;di < 10; ++di) { + var line = data[di]; + if(!line || line.match(/^\s*$/)) break; + var m = line.match(/^(.*?):\s*([^\s].*)$/); + if(m) switch(m[1].toLowerCase()) { + case "content-location": fname = m[2].trim(); break; + case "content-type": ctype = m[2].trim(); break; + case "content-transfer-encoding": cte = m[2].trim(); break; + } + } + ++di; + switch(cte.toLowerCase()) { + case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break; + case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break; + default: throw new Error("Unsupported Content-Transfer-Encoding " + cte); + } + var file = cfb_add(cfb, fname.slice(root.length), fdata, {unsafe: true}); + if(ctype) file.ctype = ctype; +} + +function parse_mad(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { + if(a2s(file.slice(0,13)).toLowerCase() != "mime-version:") throw new Error("Unsupported MAD header"); + var root = (options && options.root || ""); + // $FlowIgnore + var data = (has_buf && Buffer.isBuffer(file) ? file.toString("binary") : a2s(file)).split("\r\n"); + var di = 0, row = ""; + + /* if root is not specified, scan for the common prefix */ + for(di = 0; di < data.length; ++di) { + row = data[di]; + if(!/^Content-Location:/i.test(row)) continue; + row = row.slice(row.indexOf("file")); + if(!root) root = row.slice(0, row.lastIndexOf("/") + 1); + if(row.slice(0, root.length) == root) continue; + while(root.length > 0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array*/ = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:string*/ { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +}function cfb_new(opts/*:?any*/)/*:CFBContainer*/ { var o/*:CFBContainer*/ = ({}/*:any*/); init_cfb(o, opts); return o; diff --git a/cfb.js b/cfb.js index 5999dbf..7882e20 100644 --- a/cfb.js +++ b/cfb.js @@ -277,7 +277,7 @@ CRC32.str = crc32_str; /* [MS-CFB] v20171201 */ var CFB = (function _CFB(){ var exports = {}; -exports.version = '1.1.4'; +exports.version = '1.2.0'; /* [MS-CFB] 2.6.4 */ function namecmp(l, r) { var L = l.split("/"), R = r.split("/"); @@ -367,6 +367,7 @@ var fs; function get_fs() { return fs || (fs = require('fs')); } function parse(file, options) { if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options); +if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options); if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512"); var mver = 3; var ssz = 512; @@ -787,8 +788,13 @@ function rebuild_cfb(cfb, f) { function _write(cfb, options) { var _opts = options || {}; + /* MAD is order-sensitive, skip rebuild and sort */ + if(_opts.fileType == 'mad') return write_mad(cfb, _opts); rebuild_cfb(cfb); - if(_opts.fileType == 'zip') return write_zip(cfb, _opts); + switch(_opts.fileType) { + case 'zip': return write_zip(cfb, _opts); + //case 'mad': return write_mad(cfb, _opts); + } var L = (function(cfb){ var mini_size = 0, fat_size = 0; for(var i = 0; i < cfb.FileIndex.length; ++i) { @@ -976,10 +982,13 @@ function a2s(o) { function write(cfb, options) { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } @@ -1528,7 +1537,212 @@ function write_zip(cfb, options) { return bconcat(([bconcat((out)), bconcat(cdirs), o])); } -function cfb_new(opts) { +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}); + +function get_content_type(fi, fp) { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr) { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +
0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex = [], FullPaths = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb, options) { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +}function cfb_new(opts) { var o = ({}); init_cfb(o, opts); return o; diff --git a/dist/cfb.js b/dist/cfb.js index 5999dbf..7882e20 100644 --- a/dist/cfb.js +++ b/dist/cfb.js @@ -277,7 +277,7 @@ CRC32.str = crc32_str; /* [MS-CFB] v20171201 */ var CFB = (function _CFB(){ var exports = {}; -exports.version = '1.1.4'; +exports.version = '1.2.0'; /* [MS-CFB] 2.6.4 */ function namecmp(l, r) { var L = l.split("/"), R = r.split("/"); @@ -367,6 +367,7 @@ var fs; function get_fs() { return fs || (fs = require('fs')); } function parse(file, options) { if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options); +if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options); if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512"); var mver = 3; var ssz = 512; @@ -787,8 +788,13 @@ function rebuild_cfb(cfb, f) { function _write(cfb, options) { var _opts = options || {}; + /* MAD is order-sensitive, skip rebuild and sort */ + if(_opts.fileType == 'mad') return write_mad(cfb, _opts); rebuild_cfb(cfb); - if(_opts.fileType == 'zip') return write_zip(cfb, _opts); + switch(_opts.fileType) { + case 'zip': return write_zip(cfb, _opts); + //case 'mad': return write_mad(cfb, _opts); + } var L = (function(cfb){ var mini_size = 0, fat_size = 0; for(var i = 0; i < cfb.FileIndex.length; ++i) { @@ -976,10 +982,13 @@ function a2s(o) { function write(cfb, options) { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } @@ -1528,7 +1537,212 @@ function write_zip(cfb, options) { return bconcat(([bconcat((out)), bconcat(cdirs), o])); } -function cfb_new(opts) { +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}); + +function get_content_type(fi, fp) { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr) { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +
0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex = [], FullPaths = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb, options) { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +}function cfb_new(opts) { var o = ({}); init_cfb(o, opts); return o; diff --git a/dist/cfb.min.js b/dist/cfb.min.js index e24123d..1c2e21f 100644 --- a/dist/cfb.min.js +++ b/dist/cfb.min.js @@ -1,2 +1,3 @@ /* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */ -var Base64=function r(){var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return{encode:function(e){var t="";var i=0,n=0,f=0,a=0,s=0,h=0,l=0;for(var o=0;o>2;n=e.charCodeAt(o++);s=(i&3)<<4|n>>4;f=e.charCodeAt(o++);h=(n&15)<<2|f>>6;l=f&63;if(isNaN(n)){h=l=64}else if(isNaN(f)){l=64}t+=r.charAt(a)+r.charAt(s)+r.charAt(h)+r.charAt(l)}return t},decode:function e(t){var i="";var n=0,f=0,a=0,s=0,h=0,l=0,o=0;t=t.replace(/[^\w\+\/\=]/g,"");for(var u=0;u>4;i+=String.fromCharCode(n);l=r.indexOf(t.charAt(u++));f=(h&15)<<4|l>>2;if(l!==64){i+=String.fromCharCode(f)}o=r.indexOf(t.charAt(u++));a=(l&3)<<6|o;if(o!==64){i+=String.fromCharCode(a)}}return i}}}();var has_buf=typeof Buffer!=="undefined"&&typeof process!=="undefined"&&typeof process.versions!=="undefined"&&process.versions.node;var Buffer_from=function(){};if(typeof Buffer!=="undefined"){var nbfs=!Buffer.from;if(!nbfs)try{Buffer.from("foo","utf8")}catch(e){nbfs=true}Buffer_from=nbfs?function(r,e){return e?new Buffer(r,e):new Buffer(r)}:Buffer.from.bind(Buffer);if(!Buffer.alloc)Buffer.alloc=function(r){return new Buffer(r)};if(!Buffer.allocUnsafe)Buffer.allocUnsafe=function(r){return new Buffer(r)}}function new_raw_buf(r){return has_buf?Buffer.alloc(r):new Array(r)}function new_unsafe_buf(r){return has_buf?Buffer.allocUnsafe(r):new Array(r)}var s2a=function e(r){if(has_buf)return Buffer_from(r,"binary");return r.split("").map(function(r){return r.charCodeAt(0)&255})};var chr0=/\u0000/g,chr1=/[\u0001-\u0006]/g;var __toBuffer=function(r){var e=[];for(var t=0;t0&&Buffer.isBuffer(r[0][0])?Buffer.concat(r[0]):___toBuffer(r)};s2a=function(r){return Buffer_from(r,"binary")};bconcat=function(r){return Buffer.isBuffer(r[0])?Buffer.concat(r):__bconcat(r)}}var __readUInt8=function(r,e){return r[e]};var __readUInt16LE=function(r,e){return r[e+1]*(1<<8)+r[e]};var __readInt16LE=function(r,e){var t=r[e+1]*(1<<8)+r[e];return t<32768?t:(65535-t+1)*-1};var __readUInt32LE=function(r,e){return r[e+3]*(1<<24)+(r[e+2]<<16)+(r[e+1]<<8)+r[e]};var __readInt32LE=function(r,e){return(r[e+3]<<24)+(r[e+2]<<16)+(r[e+1]<<8)+r[e]};function ReadShift(r,e){var t,i,n=0;switch(r){case 1:t=__readUInt8(this,this.l);break;case 2:t=(e!=="i"?__readUInt16LE:__readInt16LE)(this,this.l);break;case 4:t=__readInt32LE(this,this.l);break;case 16:n=2;i=__hexlify(this,this.l,r);}this.l+=r;if(n===0)return t;return i}var __writeUInt32LE=function(r,e,t){r[t]=e&255;r[t+1]=e>>>8&255;r[t+2]=e>>>16&255;r[t+3]=e>>>24&255};var __writeInt32LE=function(r,e,t){r[t]=e&255;r[t+1]=e>>8&255;r[t+2]=e>>16&255;r[t+3]=e>>24&255};function WriteShift(r,e,t){var i=0,n=0;switch(t){case"hex":for(;n>8}while(this.l>>=8;this[this.l+1]=e&255;break;case 4:i=4;__writeUInt32LE(this,e,this.l);break;case-4:i=4;__writeInt32LE(this,e,this.l);break;}this.l+=i;return this}function CheckField(r,e){var t=__hexlify(this,this.l,r.length>>1);if(t!==r)throw new Error(e+"Expected "+r+" saw "+t);this.l+=r.length>>1}function prep_blob(r,e){r.l=e;r.read_shift=ReadShift;r.chk=CheckField;r.write_shift=WriteShift}function new_buf(r){var e=new_raw_buf(r);prep_blob(e,0);return e}var CRC32;(function(r){r(CRC32={})})(function(r){r.version="1.2.0";function e(){var r=0,e=new Array(256);for(var t=0;t!=256;++t){r=t;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;r=r&1?-306674912^r>>>1:r>>>1;e[t]=r}return typeof Int32Array!=="undefined"?new Int32Array(e):e}var t=e();function i(r,e){var i=e^-1,n=r.length-1;for(var f=0;f>>8^t[(i^r.charCodeAt(f++))&255];i=i>>>8^t[(i^r.charCodeAt(f++))&255]}if(f===n)i=i>>>8^t[(i^r.charCodeAt(f))&255];return i^-1}function n(r,e){if(r.length>1e4)return f(r,e);var i=e^-1,n=r.length-3;for(var a=0;a>>8^t[(i^r[a++])&255];i=i>>>8^t[(i^r[a++])&255];i=i>>>8^t[(i^r[a++])&255];i=i>>>8^t[(i^r[a++])&255]}while(a>>8^t[(i^r[a++])&255];return i^-1}function f(r,e){var i=e^-1,n=r.length-7;for(var f=0;f>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255];i=i>>>8^t[(i^r[f++])&255]}while(f>>8^t[(i^r[f++])&255];return i^-1}function a(r,e){var i=e^-1;for(var n=0,f=r.length,a,s;n>>8^t[(i^a)&255]}else if(a<2048){i=i>>>8^t[(i^(192|a>>6&31))&255];i=i>>>8^t[(i^(128|a&63))&255]}else if(a>=55296&&a<57344){a=(a&1023)+64;s=r.charCodeAt(n++)&1023;i=i>>>8^t[(i^(240|a>>8&7))&255];i=i>>>8^t[(i^(128|a>>2&63))&255];i=i>>>8^t[(i^(128|s>>6&15|(a&3)<<4))&255];i=i>>>8^t[(i^(128|s&63))&255]}else{i=i>>>8^t[(i^(224|a>>12&15))&255];i=i>>>8^t[(i^(128|a>>6&63))&255];i=i>>>8^t[(i^(128|a&63))&255]}}return i^-1}r.table=t;r.bstr=i;r.buf=n;r.str=a});var CFB=function t(){var r={};r.version="1.1.4";function e(r,e){var t=r.split("/"),i=e.split("/");for(var n=0,f=0,a=Math.min(t.length,i.length);n>>1;r.write_shift(2,t);var i=e.getFullYear()-1980;i=i<<4|e.getMonth()+1;i=i<<5|e.getDate();r.write_shift(2,i)}function f(r){var e=r.read_shift(2)&65535;var t=r.read_shift(2)&65535;var i=new Date;var n=t&31;t>>>=5;var f=t&15;t>>>=4;i.setMilliseconds(0);i.setFullYear(t+1980);i.setMonth(f-1);i.setDate(n);var a=e&31;e>>>=5;var s=e&63;e>>>=6;i.setHours(e);i.setMinutes(s);i.setSeconds(a<<1);return i}function a(r){prep_blob(r,0);var e={};var t=0;while(r.l<=r.length-4){var i=r.read_shift(2);var n=r.read_shift(2),f=r.l+n;var a={};switch(i){case 21589:{t=r.read_shift(1);if(t&1)a.mtime=r.read_shift(4);if(n>5){if(t&2)a.atime=r.read_shift(4);if(t&4)a.ctime=r.read_shift(4)}if(a.mtime)a.mt=new Date(a.mtime*1e3)}break;}r.l=f;e[i]=a}return e}var s;function h(){return s||(s=require("fs"))}function l(r,e){if(r[0]==80&&r[1]==75)return wr(r,e);if(r.length<512)throw new Error("CFB file size "+r.length+" < 512");var t=3;var i=512;var n=0;var f=0;var a=0;var s=0;var h=0;var l=[];var v=r.slice(0,512);prep_blob(v,0);var w=o(v);t=w[0];switch(t){case 3:i=512;break;case 4:i=4096;break;case 0:if(w[1]==0)return wr(r,e);default:throw new Error("Major Version: Expected 3 or 4 saw "+t);}if(i!==512){v=r.slice(0,i);prep_blob(v,28)}var b=r.slice(0,i);u(v,t);var F=v.read_shift(4,"i");if(t===3&&F!==0)throw new Error("# Directory Sectors: Expected 0 saw "+F);v.l+=4;a=v.read_shift(4,"i");v.l+=4;v.chk("00100000","Mini Stream Cutoff Size: ");s=v.read_shift(4,"i");n=v.read_shift(4,"i");h=v.read_shift(4,"i");f=v.read_shift(4,"i");for(var y=-1,I=0;I<109;++I){y=v.read_shift(4,"i");if(y<0)break;l[I]=y}var C=c(r,i);d(h,f,C,i,l);var x=p(C,a,l,i);x[a].name="!Directory";if(n>0&&s!==B)x[s].name="!MiniFAT";x[l[0]].name="!FAT";x.fat_addrs=l;x.ssz=i;var m={},E=[],A=[],S=[];g(a,x,C,E,n,m,A,s);_(A,S,E);E.shift();var k={FileIndex:A,FullPaths:S};if(e&&e.raw)k.raw={header:b,sectors:C};return k}function o(r){if(r[r.l]==80&&r[r.l+1]==75)return[0,0];r.chk(S,"Header Signature: ");r.l+=16;var e=r.read_shift(2,"u");return[r.read_shift(2,"u"),e]}function u(r,e){var t=9;r.l+=2;switch(t=r.read_shift(2)){case 9:if(e!=3)throw new Error("Sector Shift: Expected 9 saw "+t);break;case 12:if(e!=4)throw new Error("Sector Shift: Expected 12 saw "+t);break;default:throw new Error("Sector Shift: Expected 9 or 12 saw "+t);}r.chk("0600","Mini Sector Shift: ");r.chk("000000000000","Reserved: ")}function c(r,e){var t=Math.ceil(r.length/e)-1;var i=[];for(var n=1;n0&&a>=0){f.push(e.slice(a*A,a*A+A));n-=A;a=__readInt32LE(t,a*4)}if(f.length===0)return new_buf(0);return bconcat(f).slice(0,r.size)}function d(r,e,t,i,n){var f=B;if(r===B){if(e!==0)throw new Error("DIFAT chain shorter than expected")}else if(r!==-1){var a=t[r],s=(i>>>2)-1;if(!a)return;for(var h=0;h=0;){n[h]=true;f[f.length]=h;a.push(r[h]);var o=t[Math.floor(h*4/i)];l=h*4&s;if(i<4+l)throw new Error("FAT boundary crossed: "+h+" 4 "+i);if(!r[o])break;h=__readInt32LE(r[o],l)}return{nodes:f,data:__toBuffer([a])}}function p(r,e,t,i){var n=r.length,f=[];var a=[],s=[],h=[];var l=i-1,o=0,u=0,c=0,_=0;for(o=0;o=n)c-=n;if(a[c])continue;h=[];var v=[];for(u=c;u>=0;){v[u]=true;a[u]=true;s[s.length]=u;h.push(r[u]);var d=t[Math.floor(u*4/i)];_=u*4&l;if(i<4+_)throw new Error("FAT boundary crossed: "+u+" 4 "+i);if(!r[d])break;u=__readInt32LE(r[d],_);if(v[u])break}f[c]={nodes:s,data:__toBuffer([h])}}return f}function g(r,e,t,i,n,f,a,s){var h=0,l=i.length?2:0;var o=e[r].data;var u=0,c=0,_;for(;u0&&h!==B)e[h].name="!StreamData"}else if(p.size>=4096){p.storage="fat";if(e[p.start]===undefined)e[p.start]=w(t,p.start,e.fat_addrs,e.ssz);e[p.start].name=p.name;p.content=e[p.start].data.slice(0,p.size)}else{p.storage="minifat";if(p.size<0)p.size=0;else if(h!==B&&p.start!==B&&e[h]){p.content=v(p,e[h].data,(e[s]||{}).data)}}if(p.content)prep_blob(p.content,0);f[_]=p;a.push(p)}}function b(r,e){return new Date((__readUInt32LE(r,e+4)/1e7*Math.pow(2,32)+__readUInt32LE(r,e)/1e7-11644473600)*1e3)}function F(r,e){h();return l(s.readFileSync(r),e)}function y(r,e){switch(e&&e.type||"base64"){case"file":return F(r,e);case"base64":return l(s2a(Base64.decode(r)),e);case"binary":return l(s2a(r),e);}return l(r,e)}function I(r,e){var t=e||{},i=t.root||"Root Entry";if(!r.FullPaths)r.FullPaths=[];if(!r.FileIndex)r.FileIndex=[];if(r.FullPaths.length!==r.FileIndex.length)throw new Error("inconsistent CFB structure");if(r.FullPaths.length===0){r.FullPaths[0]=i+"/";r.FileIndex[0]={name:i,type:5}}if(t.CLSID)r.FileIndex[0].clsid=t.CLSID;C(r)}function C(r){var e="Sh33tJ5";if(CFB.find(r,"/"+e))return;var t=new_buf(4);t[0]=55;t[1]=t[3]=50;t[2]=54;r.FileIndex.push({name:e,type:2,content:t,size:4,L:69,R:69,C:69});r.FullPaths.push(r.FullPaths[0]+e);x(r)}function x(r,n){I(r);var f=false,a=false;for(var s=r.FullPaths.length-1;s>=0;--s){var h=r.FileIndex[s];switch(h.type){case 0:if(a)f=true;else{r.FileIndex.pop();r.FullPaths.pop()}break;case 1:;case 2:;case 5:a=true;if(isNaN(h.R*h.L*h.C))f=true;if(h.R>-1&&h.L>-1&&h.R==h.L)f=true;break;default:f=true;break;}}if(!f&&!n)return;var l=new Date(1987,1,19),o=0;var u=[];for(s=0;s1?1:-1;_.size=0;_.type=5}else if(v.slice(-1)=="/"){for(o=s+1;o=u.length?-1:o;for(o=s+1;o=u.length?-1:o;_.type=1}else{if(t(r.FullPaths[s+1]||"")==t(v))_.R=s+1;_.type=2}}}function m(r,e){var t=e||{};x(r);if(t.fileType=="zip")return gr(r,t);var i=function(r){var e=0,t=0;for(var i=0;i0){if(f<4096)e+=f+63>>6;else t+=f+511>>9}}var a=r.FullPaths.length+3>>2;var s=e+7>>3;var h=e+127>>7;var l=s+t+a+h;var o=l+127>>7;var u=o<=109?0:Math.ceil((o-109)/127);while(l+o+u+127>>7>o)u=++o<=109?0:Math.ceil((o-109)/127);var c=[1,u,o,h,a,t,e,0];r.FileIndex[0].size=e<<6;c[7]=(r.FileIndex[0].start=c[0]+c[1]+c[2]+c[3]+c[4]+c[5])+(c[6]+7>>3);return c}(r);var n=new_buf(i[7]<<9);var f=0,a=0;{for(f=0;f<8;++f)n.write_shift(1,k[f]);for(f=0;f<8;++f)n.write_shift(2,0);n.write_shift(2,62);n.write_shift(2,3);n.write_shift(2,65534);n.write_shift(2,9);n.write_shift(2,6);for(f=0;f<3;++f)n.write_shift(2,0);n.write_shift(4,0);n.write_shift(4,i[2]);n.write_shift(4,i[0]+i[1]+i[2]+i[3]-1);n.write_shift(4,0);n.write_shift(4,1<<12);n.write_shift(4,i[3]?i[0]+i[1]+i[2]-1:B);n.write_shift(4,i[3]);n.write_shift(-4,i[1]?i[0]-1:B);n.write_shift(4,i[1]);for(f=0;f<109;++f)n.write_shift(-4,f>9)}s(i[6]+7>>3);while(n.l&511)n.write_shift(-4,z.ENDOFCHAIN);a=f=0;for(h=0;h=4096)continue;o.start=a;s(l+63>>6)}while(n.l&511)n.write_shift(-4,z.ENDOFCHAIN);for(f=0;f=4096){n.l=o.start+1<<9;for(h=0;h0&&o.size<4096){for(h=0;h>16|e>>8|e)&255}var X=typeof Uint8Array!=="undefined";var q=X?new Uint8Array(1<<8):[];for(var J=0;J<1<<8;++J)q[J]=G(J);function V(r,e){var t=q[r&255];if(e<=8)return t>>>8-e;t=t<<8|q[r>>8&255];if(e<=16)return t>>>16-e;t=t<<8|q[r>>16&255];return t>>>24-e}function W(r,e){var t=e&7,i=e>>>3;return(r[i]|(t<=6?0:r[i+1]<<8))>>>t&3}function Y(r,e){var t=e&7,i=e>>>3;return(r[i]|(t<=5?0:r[i+1]<<8))>>>t&7}function Z(r,e){var t=e&7,i=e>>>3;return(r[i]|(t<=4?0:r[i+1]<<8))>>>t&15}function K(r,e){var t=e&7,i=e>>>3;return(r[i]|(t<=3?0:r[i+1]<<8))>>>t&31}function Q(r,e){var t=e&7,i=e>>>3;return(r[i]|(t<=1?0:r[i+1]<<8))>>>t&127}function $(r,e,t){var i=e&7,n=e>>>3,f=(1<>>i;if(t<8-i)return a&f;a|=r[n+1]<<8-i;if(t<16-i)return a&f;a|=r[n+2]<<16-i;if(t<24-i)return a&f;a|=r[n+3]<<24-i;return a&f}function rr(r,e){var t=r.length,i=2*t>e?2*t:e+5,n=0;if(t>=e)return r;if(has_buf){var f=new_unsafe_buf(i);if(r.copy)r.copy(f);else for(;n0)t[t.l++]=e[i++]}return t.l}}();return function(e){var t=new_buf(50+Math.floor(e.length*1.1));var i=r(e,t);return t.slice(0,i)}}();function ir(r,e,t){var i=1,n=0,f=0,a=0,s=0,h=r.length;var l=X?new Uint16Array(32):er(32);for(f=0;f<32;++f)l[f]=0;for(f=h;f>i-u;for(a=(1<=0;--a)e[s|a<>8-v;for(var d=(1<<7-v)-1;d>=0;--d)lr[_|d<>>=3){case 16:f=3+W(r,e);e+=2;_=w[w.length-1];while(f-- >0)w.push(_);break;case 17:f=3+Y(r,e);e+=3;while(f-- >0)w.push(0);break;case 18:f=11+Q(r,e);e+=7;while(f-- >0)w.push(0);break;default:w.push(_);if(h<_)h=_;break;}}var p=w.slice(0,t),g=w.slice(t);for(c=t;c<286;++c)p[c]=0;for(c=i;c<30;++c)g[c]=0;or=ir(p,sr,286);ur=ir(g,hr,30);return e}function _r(r,e){if(r[0]==3&&!(r[1]&3)){return[new_raw_buf(e),2]}var t=0;var i=0;var n=new_unsafe_buf(e?e:1<<18);var f=0;var a=n.length>>>0;var s=0,h=0;while((i&1)==0){i=Y(r,t);t+=3;if(i>>>1==0){if(t&7)t+=8-(t&7);var l=r[t>>>3]|r[(t>>>3)+1]<<8;t+=32;if(!e&&a>>3,(t>>>3)+l);f+=l;t+=8*l}else while(l-- >0){n[f++]=r[t>>>3];t+=8}continue}else if(i>>>1==1){s=9;h=5}else{t=cr(r,t);s=or;h=ur}if(!e&&a>>1==1?nr[o]:sr[o];t+=u&15;u>>>=4;if((u>>>8&255)===0)n[f++]=u;else if(u==256)break;else{u-=257;var c=u<8?0:u-4>>2;if(c>5)c=0;var _=f+H[u];if(c>0){_+=$(r,t,c);t+=c}o=$(r,t,h);u=i>>>1==1?fr[o]:hr[o];t+=u&15;u>>>=4;var v=u<4?0:u-2>>1;var d=j[u];if(v>0){d+=$(r,t,v);t+=v}if(!e&&a<_){n=rr(n,_);a=n.length}while(f<_){n[f]=n[f-d];++f}}}}return[e?n:n.slice(0,f),t+7>>>3]}function vr(r,e){var t=r.slice(r.l||0);var i=_r(t,e);r.l+=i[1];return i[0]}function dr(r,e){if(r){if(typeof console!=="undefined")console.error(e)}else throw new Error(e)}function wr(r,e){var t=r;prep_blob(t,0);var i=[],n=[];var f={FileIndex:i,FullPaths:n};I(f,{root:e.root});var s=t.length-4;while((t[s]!=80||t[s+1]!=75||t[s+2]!=5||t[s+3]!=6)&&s>=0)--s;t.l=s+4;t.l+=4;var h=t.read_shift(2);t.l+=6;var l=t.read_shift(4);t.l=l;for(s=0;s>2;n=r.charCodeAt(o++);s=(i&3)<<4|n>>4;a=r.charCodeAt(o++);h=(n&15)<<2|a>>6;l=a&63;if(isNaN(n)){h=l=64}else if(isNaN(a)){l=64}t+=e.charAt(f)+e.charAt(s)+e.charAt(h)+e.charAt(l)}return t},decode:function r(t){var i="";var n=0,a=0,f=0,s=0,h=0,l=0,o=0;t=t.replace(/[^\w\+\/\=]/g,"");for(var u=0;u>4;i+=String.fromCharCode(n);l=e.indexOf(t.charAt(u++));a=(h&15)<<4|l>>2;if(l!==64){i+=String.fromCharCode(a)}o=e.indexOf(t.charAt(u++));f=(l&3)<<6|o;if(o!==64){i+=String.fromCharCode(f)}}return i}}}();var has_buf=typeof Buffer!=="undefined"&&typeof process!=="undefined"&&typeof process.versions!=="undefined"&&process.versions.node;var Buffer_from=function(){};if(typeof Buffer!=="undefined"){var nbfs=!Buffer.from;if(!nbfs)try{Buffer.from("foo","utf8")}catch(e){nbfs=true}Buffer_from=nbfs?function(e,r){return r?new Buffer(e,r):new Buffer(e)}:Buffer.from.bind(Buffer);if(!Buffer.alloc)Buffer.alloc=function(e){return new Buffer(e)};if(!Buffer.allocUnsafe)Buffer.allocUnsafe=function(e){return new Buffer(e)}}function new_raw_buf(e){return has_buf?Buffer.alloc(e):new Array(e)}function new_unsafe_buf(e){return has_buf?Buffer.allocUnsafe(e):new Array(e)}var s2a=function r(e){if(has_buf)return Buffer_from(e,"binary");return e.split("").map(function(e){return e.charCodeAt(0)&255})};var chr0=/\u0000/g,chr1=/[\u0001-\u0006]/g;var __toBuffer=function(e){var r=[];for(var t=0;t0&&Buffer.isBuffer(e[0][0])?Buffer.concat(e[0]):___toBuffer(e)};s2a=function(e){return Buffer_from(e,"binary")};bconcat=function(e){return Buffer.isBuffer(e[0])?Buffer.concat(e):__bconcat(e)}}var __readUInt8=function(e,r){return e[r]};var __readUInt16LE=function(e,r){return e[r+1]*(1<<8)+e[r]};var __readInt16LE=function(e,r){var t=e[r+1]*(1<<8)+e[r];return t<32768?t:(65535-t+1)*-1};var __readUInt32LE=function(e,r){return e[r+3]*(1<<24)+(e[r+2]<<16)+(e[r+1]<<8)+e[r]};var __readInt32LE=function(e,r){return(e[r+3]<<24)+(e[r+2]<<16)+(e[r+1]<<8)+e[r]};function ReadShift(e,r){var t,i,n=0;switch(e){case 1:t=__readUInt8(this,this.l);break;case 2:t=(r!=="i"?__readUInt16LE:__readInt16LE)(this,this.l);break;case 4:t=__readInt32LE(this,this.l);break;case 16:n=2;i=__hexlify(this,this.l,e);}this.l+=e;if(n===0)return t;return i}var __writeUInt32LE=function(e,r,t){e[t]=r&255;e[t+1]=r>>>8&255;e[t+2]=r>>>16&255;e[t+3]=r>>>24&255};var __writeInt32LE=function(e,r,t){e[t]=r&255;e[t+1]=r>>8&255;e[t+2]=r>>16&255;e[t+3]=r>>24&255};function WriteShift(e,r,t){var i=0,n=0;switch(t){case"hex":for(;n>8}while(this.l>>=8;this[this.l+1]=r&255;break;case 4:i=4;__writeUInt32LE(this,r,this.l);break;case-4:i=4;__writeInt32LE(this,r,this.l);break;}this.l+=i;return this}function CheckField(e,r){var t=__hexlify(this,this.l,e.length>>1);if(t!==e)throw new Error(r+"Expected "+e+" saw "+t);this.l+=e.length>>1}function prep_blob(e,r){e.l=r;e.read_shift=ReadShift;e.chk=CheckField;e.write_shift=WriteShift}function new_buf(e){var r=new_raw_buf(e);prep_blob(r,0);return r}var CRC32;(function(e){e(CRC32={})})(function(e){e.version="1.2.0";function r(){var e=0,r=new Array(256);for(var t=0;t!=256;++t){e=t;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;e=e&1?-306674912^e>>>1:e>>>1;r[t]=e}return typeof Int32Array!=="undefined"?new Int32Array(r):r}var t=r();function i(e,r){var i=r^-1,n=e.length-1;for(var a=0;a>>8^t[(i^e.charCodeAt(a++))&255];i=i>>>8^t[(i^e.charCodeAt(a++))&255]}if(a===n)i=i>>>8^t[(i^e.charCodeAt(a))&255];return i^-1}function n(e,r){if(e.length>1e4)return a(e,r);var i=r^-1,n=e.length-3;for(var f=0;f>>8^t[(i^e[f++])&255];i=i>>>8^t[(i^e[f++])&255];i=i>>>8^t[(i^e[f++])&255];i=i>>>8^t[(i^e[f++])&255]}while(f>>8^t[(i^e[f++])&255];return i^-1}function a(e,r){var i=r^-1,n=e.length-7;for(var a=0;a>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255];i=i>>>8^t[(i^e[a++])&255]}while(a>>8^t[(i^e[a++])&255];return i^-1}function f(e,r){var i=r^-1;for(var n=0,a=e.length,f,s;n>>8^t[(i^f)&255]}else if(f<2048){i=i>>>8^t[(i^(192|f>>6&31))&255];i=i>>>8^t[(i^(128|f&63))&255]}else if(f>=55296&&f<57344){f=(f&1023)+64;s=e.charCodeAt(n++)&1023;i=i>>>8^t[(i^(240|f>>8&7))&255];i=i>>>8^t[(i^(128|f>>2&63))&255];i=i>>>8^t[(i^(128|s>>6&15|(f&3)<<4))&255];i=i>>>8^t[(i^(128|s&63))&255]}else{i=i>>>8^t[(i^(224|f>>12&15))&255];i=i>>>8^t[(i^(128|f>>6&63))&255];i=i>>>8^t[(i^(128|f&63))&255]}}return i^-1}e.table=t;e.bstr=i;e.buf=n;e.str=f});var CFB=function t(){var e={};e.version="1.2.0";function r(e,r){var t=e.split("/"),i=r.split("/");for(var n=0,a=0,f=Math.min(t.length,i.length);n>>1;e.write_shift(2,t);var i=r.getFullYear()-1980;i=i<<4|r.getMonth()+1;i=i<<5|r.getDate();e.write_shift(2,i)}function a(e){var r=e.read_shift(2)&65535;var t=e.read_shift(2)&65535;var i=new Date;var n=t&31;t>>>=5;var a=t&15;t>>>=4;i.setMilliseconds(0);i.setFullYear(t+1980);i.setMonth(a-1);i.setDate(n);var f=r&31;r>>>=5;var s=r&63;r>>>=6;i.setHours(r);i.setMinutes(s);i.setSeconds(f<<1);return i}function f(e){prep_blob(e,0);var r={};var t=0;while(e.l<=e.length-4){var i=e.read_shift(2);var n=e.read_shift(2),a=e.l+n;var f={};switch(i){case 21589:{t=e.read_shift(1);if(t&1)f.mtime=e.read_shift(4);if(n>5){if(t&2)f.atime=e.read_shift(4);if(t&4)f.ctime=e.read_shift(4)}if(f.mtime)f.mt=new Date(f.mtime*1e3)}break;}e.l=a;r[i]=f}return r}var s;function h(){return s||(s=require("fs"))}function l(e,r){if(e[0]==80&&e[1]==75)return we(e,r);if((e[0]|32)==109&&(e[1]|32)==105)return Ie(e,r);if(e.length<512)throw new Error("CFB file size "+e.length+" < 512");var t=3;var i=512;var n=0;var a=0;var f=0;var s=0;var h=0;var l=[];var _=e.slice(0,512);prep_blob(_,0);var w=o(_);t=w[0];switch(t){case 3:i=512;break;case 4:i=4096;break;case 0:if(w[1]==0)return we(e,r);default:throw new Error("Major Version: Expected 3 or 4 saw "+t);}if(i!==512){_=e.slice(0,i);prep_blob(_,28)}var b=e.slice(0,i);u(_,t);var F=_.read_shift(4,"i");if(t===3&&F!==0)throw new Error("# Directory Sectors: Expected 0 saw "+F);_.l+=4;f=_.read_shift(4,"i");_.l+=4;_.chk("00100000","Mini Stream Cutoff Size: ");s=_.read_shift(4,"i");n=_.read_shift(4,"i");h=_.read_shift(4,"i");a=_.read_shift(4,"i");for(var y=-1,m=0;m<109;++m){y=_.read_shift(4,"i");if(y<0)break;l[m]=y}var x=c(e,i);d(h,a,x,i,l);var C=p(x,f,l,i);C[f].name="!Directory";if(n>0&&s!==B)C[s].name="!MiniFAT";C[l[0]].name="!FAT";C.fat_addrs=l;C.ssz=i;var I={},A=[],E=[],S=[];g(f,C,x,A,n,I,E,s);v(E,S,A);A.shift();var k={FileIndex:E,FullPaths:S};if(r&&r.raw)k.raw={header:b,sectors:x};return k}function o(e){if(e[e.l]==80&&e[e.l+1]==75)return[0,0];e.chk(S,"Header Signature: ");e.l+=16;var r=e.read_shift(2,"u");return[e.read_shift(2,"u"),r]}function u(e,r){var t=9;e.l+=2;switch(t=e.read_shift(2)){case 9:if(r!=3)throw new Error("Sector Shift: Expected 9 saw "+t);break;case 12:if(r!=4)throw new Error("Sector Shift: Expected 12 saw "+t);break;default:throw new Error("Sector Shift: Expected 9 or 12 saw "+t);}e.chk("0600","Mini Sector Shift: ");e.chk("000000000000","Reserved: ")}function c(e,r){var t=Math.ceil(e.length/r)-1;var i=[];for(var n=1;n0&&f>=0){a.push(r.slice(f*E,f*E+E));n-=E;f=__readInt32LE(t,f*4)}if(a.length===0)return new_buf(0);return bconcat(a).slice(0,e.size)}function d(e,r,t,i,n){var a=B;if(e===B){if(r!==0)throw new Error("DIFAT chain shorter than expected")}else if(e!==-1){var f=t[e],s=(i>>>2)-1;if(!f)return;for(var h=0;h=0;){n[h]=true;a[a.length]=h;f.push(e[h]);var o=t[Math.floor(h*4/i)];l=h*4&s;if(i<4+l)throw new Error("FAT boundary crossed: "+h+" 4 "+i);if(!e[o])break;h=__readInt32LE(e[o],l)}return{nodes:a,data:__toBuffer([f])}}function p(e,r,t,i){var n=e.length,a=[];var f=[],s=[],h=[];var l=i-1,o=0,u=0,c=0,v=0;for(o=0;o=n)c-=n;if(f[c])continue;h=[];var _=[];for(u=c;u>=0;){_[u]=true;f[u]=true;s[s.length]=u;h.push(e[u]);var d=t[Math.floor(u*4/i)];v=u*4&l;if(i<4+v)throw new Error("FAT boundary crossed: "+u+" 4 "+i);if(!e[d])break;u=__readInt32LE(e[d],v);if(_[u])break}a[c]={nodes:s,data:__toBuffer([h])}}return a}function g(e,r,t,i,n,a,f,s){var h=0,l=i.length?2:0;var o=r[e].data;var u=0,c=0,v;for(;u0&&h!==B)r[h].name="!StreamData"}else if(p.size>=4096){p.storage="fat";if(r[p.start]===undefined)r[p.start]=w(t,p.start,r.fat_addrs,r.ssz);r[p.start].name=p.name;p.content=r[p.start].data.slice(0,p.size)}else{p.storage="minifat";if(p.size<0)p.size=0;else if(h!==B&&p.start!==B&&r[h]){p.content=_(p,r[h].data,(r[s]||{}).data)}}if(p.content)prep_blob(p.content,0);a[v]=p;f.push(p)}}function b(e,r){return new Date((__readUInt32LE(e,r+4)/1e7*Math.pow(2,32)+__readUInt32LE(e,r)/1e7-11644473600)*1e3)}function F(e,r){h();return l(s.readFileSync(e),r)}function y(e,r){switch(r&&r.type||"base64"){case"file":return F(e,r);case"base64":return l(s2a(Base64.decode(e)),r);case"binary":return l(s2a(e),r);}return l(e,r)}function m(e,r){var t=r||{},i=t.root||"Root Entry";if(!e.FullPaths)e.FullPaths=[];if(!e.FileIndex)e.FileIndex=[];if(e.FullPaths.length!==e.FileIndex.length)throw new Error("inconsistent CFB structure");if(e.FullPaths.length===0){e.FullPaths[0]=i+"/";e.FileIndex[0]={name:i,type:5}}if(t.CLSID)e.FileIndex[0].clsid=t.CLSID;x(e)}function x(e){var r="Sh33tJ5";if(CFB.find(e,"/"+r))return;var t=new_buf(4);t[0]=55;t[1]=t[3]=50;t[2]=54;e.FileIndex.push({name:r,type:2,content:t,size:4,L:69,R:69,C:69});e.FullPaths.push(e.FullPaths[0]+r);C(e)}function C(e,n){m(e);var a=false,f=false;for(var s=e.FullPaths.length-1;s>=0;--s){var h=e.FileIndex[s];switch(h.type){case 0:if(f)a=true;else{e.FileIndex.pop();e.FullPaths.pop()}break;case 1:;case 2:;case 5:f=true;if(isNaN(h.R*h.L*h.C))a=true;if(h.R>-1&&h.L>-1&&h.R==h.L)a=true;break;default:a=true;break;}}if(!a&&!n)return;var l=new Date(1987,1,19),o=0;var u=[];for(s=0;s1?1:-1;v.size=0;v.type=5}else if(_.slice(-1)=="/"){for(o=s+1;o=u.length?-1:o;for(o=s+1;o=u.length?-1:o;v.type=1}else{if(t(e.FullPaths[s+1]||"")==t(_))v.R=s+1;v.type=2}}}function I(e,r){var t=r||{};if(t.fileType=="mad")return Ae(e,t);C(e);switch(t.fileType){case"zip":return ge(e,t);}var i=function(e){var r=0,t=0;for(var i=0;i0){if(a<4096)r+=a+63>>6;else t+=a+511>>9}}var f=e.FullPaths.length+3>>2;var s=r+7>>3;var h=r+127>>7;var l=s+t+f+h;var o=l+127>>7;var u=o<=109?0:Math.ceil((o-109)/127);while(l+o+u+127>>7>o)u=++o<=109?0:Math.ceil((o-109)/127);var c=[1,u,o,h,f,t,r,0];e.FileIndex[0].size=r<<6;c[7]=(e.FileIndex[0].start=c[0]+c[1]+c[2]+c[3]+c[4]+c[5])+(c[6]+7>>3);return c}(e);var n=new_buf(i[7]<<9);var a=0,f=0;{for(a=0;a<8;++a)n.write_shift(1,k[a]);for(a=0;a<8;++a)n.write_shift(2,0);n.write_shift(2,62);n.write_shift(2,3);n.write_shift(2,65534);n.write_shift(2,9);n.write_shift(2,6);for(a=0;a<3;++a)n.write_shift(2,0);n.write_shift(4,0);n.write_shift(4,i[2]);n.write_shift(4,i[0]+i[1]+i[2]+i[3]-1);n.write_shift(4,0);n.write_shift(4,1<<12);n.write_shift(4,i[3]?i[0]+i[1]+i[2]-1:B);n.write_shift(4,i[3]);n.write_shift(-4,i[1]?i[0]-1:B);n.write_shift(4,i[1]);for(a=0;a<109;++a)n.write_shift(-4,a>9)}s(i[6]+7>>3);while(n.l&511)n.write_shift(-4,L.ENDOFCHAIN);f=a=0;for(h=0;h=4096)continue;o.start=f;s(l+63>>6)}while(n.l&511)n.write_shift(-4,L.ENDOFCHAIN);for(a=0;a=4096){n.l=o.start+1<<9;for(h=0;h0&&o.size<4096){for(h=0;h>16|r>>8|r)&255}var J=typeof Uint8Array!=="undefined";var q=J?new Uint8Array(1<<8):[];for(var G=0;G<1<<8;++G)q[G]=$(G);function V(e,r){var t=q[e&255];if(r<=8)return t>>>8-r;t=t<<8|q[e>>8&255];if(r<=16)return t>>>16-r;t=t<<8|q[e>>16&255];return t>>>24-r}function X(e,r){var t=r&7,i=r>>>3;return(e[i]|(t<=6?0:e[i+1]<<8))>>>t&3}function W(e,r){var t=r&7,i=r>>>3;return(e[i]|(t<=5?0:e[i+1]<<8))>>>t&7}function Y(e,r){var t=r&7,i=r>>>3;return(e[i]|(t<=4?0:e[i+1]<<8))>>>t&15}function Z(e,r){var t=r&7,i=r>>>3;return(e[i]|(t<=3?0:e[i+1]<<8))>>>t&31}function K(e,r){var t=r&7,i=r>>>3;return(e[i]|(t<=1?0:e[i+1]<<8))>>>t&127}function Q(e,r,t){var i=r&7,n=r>>>3,a=(1<>>i;if(t<8-i)return f&a;f|=e[n+1]<<8-i;if(t<16-i)return f&a;f|=e[n+2]<<16-i;if(t<24-i)return f&a;f|=e[n+3]<<24-i;return f&a}function ee(e,r){var t=e.length,i=2*t>r?2*t:r+5,n=0;if(t>=r)return e;if(has_buf){var a=new_unsafe_buf(i);if(e.copy)e.copy(a);else for(;n0)t[t.l++]=r[i++]}return t.l}}();return function(r){var t=new_buf(50+Math.floor(r.length*1.1));var i=e(r,t);return t.slice(0,i)}}();function ie(e,r,t){var i=1,n=0,a=0,f=0,s=0,h=e.length;var l=J?new Uint16Array(32):re(32);for(a=0;a<32;++a)l[a]=0;for(a=h;a>i-u;for(f=(1<=0;--f)r[s|f<>8-_;for(var d=(1<<7-_)-1;d>=0;--d)le[v|d<<_]=_&7|c<<3}}var w=[];h=1;for(;w.length>>=3){case 16:a=3+X(e,r);r+=2;v=w[w.length-1];while(a-- >0)w.push(v);break;case 17:a=3+W(e,r);r+=3;while(a-- >0)w.push(0);break;case 18:a=11+K(e,r);r+=7;while(a-- >0)w.push(0);break;default:w.push(v);if(h>>0;var s=0,h=0;while((i&1)==0){i=W(e,t);t+=3;if(i>>>1==0){if(t&7)t+=8-(t&7);var l=e[t>>>3]|e[(t>>>3)+1]<<8;t+=32;if(!r&&f>>3,(t>>>3)+l);a+=l;t+=8*l}else while(l-- >0){n[a++]=e[t>>>3];t+=8}continue}else if(i>>>1==1){s=9;h=5}else{t=ce(e,t);s=oe;h=ue}if(!r&&f>>1==1?ne[o]:se[o];t+=u&15;u>>>=4;if((u>>>8&255)===0)n[a++]=u;else if(u==256)break;else{u-=257;var c=u<8?0:u-4>>2;if(c>5)c=0;var v=a+j[u];if(c>0){v+=Q(e,t,c);t+=c}o=Q(e,t,h);u=i>>>1==1?ae[o]:he[o];t+=u&15;u>>>=4;var _=u<4?0:u-2>>1;var d=H[u];if(_>0){d+=Q(e,t,_);t+=_}if(!r&&f>>3]}function _e(e,r){var t=e.slice(e.l||0);var i=ve(t,r);e.l+=i[1];return i[0]}function de(e,r){if(e){if(typeof console!=="undefined")console.error(r)}else throw new Error(r)}function we(e,r){var t=e;prep_blob(t,0);var i=[],n=[];var a={FileIndex:i,FullPaths:n};m(a,{root:r.root});var s=t.length-4;while((t[s]!=80||t[s+1]!=75||t[s+2]!=5||t[s+3]!=6)&&s>=0)--s;t.l=s+4;t.l+=4;var h=t.read_shift(2);t.l+=6;var l=t.read_shift(4);t.l=l;for(s=0;s0){t=t.slice(0,t.length-1);t=t.slice(0,t.lastIndexOf("/")+1);if(a.slice(0,t.length)==t)break}}var f=(i[1]||"").match(/boundary="(.*?)"/);if(!f)throw new Error("MAD cannot find boundary");var s="--"+(f[1]||"");var h=[],l=[];var o={FileIndex:h,FullPaths:l};m(o);var u,c=0;for(n=0;n=32&&v<128)++u;var d=u>=c*4/5;n.push(i);n.push("Content-Location: "+(t.root||"file:///C:/SheetJS/")+f);n.push("Content-Transfer-Encoding: "+(d?"quoted-printable":"base64"));n.push("Content-Type: "+Fe(s,f));n.push("");n.push(d?me(o):ye(o))}n.push(i+"--\r\n");return n.join("\r\n")}function Ee(e){var r={};m(r,e);return r}function Be(e,r,t,n){var a=n&&n.unsafe;if(!a)m(e);var f=!a&&CFB.find(e,r);if(!f){var s=e.FullPaths[0];if(r.slice(0,s.length)==s)s=r;else{if(s.slice(-1)!="/")s+="/";s=(s+r).replace("//","/")}f={name:i(r),type:2};e.FileIndex.push(f);e.FullPaths.push(s);if(!a)CFB.utils.cfb_gc(e)}f.content=t;f.size=t?t.length:0;if(n){if(n.CLSID)f.clsid=n.CLSID;if(n.mt)f.mt=n.mt;if(n.ct)f.ct=n.ct}return f}function Se(e,r){m(e);var t=CFB.find(e,r);if(t)for(var i=0;i*/{ var mini_size = 0, fat_size = 0; for(var i = 0; i < cfb.FileIndex.length; ++i) { @@ -847,10 +854,13 @@ function a2s(o/*:RawBytes*/)/*:string*/ { function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } @@ -1399,7 +1409,212 @@ function write_zip(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ return bconcat(([bconcat((out/*:any*/)), bconcat(cdirs), o]/*:any*/)); } -function cfb_new(opts/*:?any*/)/*:CFBContainer*/ { +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}/*:any*/); + +function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr/*:string*/)/*:string*/ { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +
*/ = [], split = encoded.split("\r\n"); + for(var si = 0; si < split.length; ++si) { + var str = split[si]; + if(str.length == 0) { o.push(""); continue; } + for(var i = 0; i < str.length;) { + var end = 76; + var tmp = str.slice(i, i + end); + if(tmp.charAt(end - 1) == "=") end --; + else if(tmp.charAt(end - 2) == "=") end -= 2; + else if(tmp.charAt(end - 3) == "=") end -= 3; + tmp = str.slice(i, i + end); + i += end; + if(i < str.length) tmp += "="; + o.push(tmp); + } + } + + return o.join("\r\n"); +} +function parse_quoted_printable(data/*:Array*/)/*:RawBytes*/ { + var o = []; + + /* unify long lines */ + for(var di = 0; di < data.length; ++di) { + var line = data[di]; + while(di <= data.length && line.charAt(line.length - 1) == "=") line = line.slice(0, line.length - 1) + data[++di]; + o.push(line); + } + + /* decode */ + for(var oi = 0; oi < o.length; ++oi) o[oi] = o[oi].replace(/=[0-9A-Fa-f]{2}/g, function($$) { return String.fromCharCode(parseInt($$.slice(1), 16)); }); + return s2a(o.join("\r\n")); +} + + +function parse_mime(cfb/*:CFBContainer*/, data/*:Array*/, root/*:string*/)/*:void*/ { + var fname = "", cte = "", ctype = "", fdata; + var di = 0; + for(;di < 10; ++di) { + var line = data[di]; + if(!line || line.match(/^\s*$/)) break; + var m = line.match(/^(.*?):\s*([^\s].*)$/); + if(m) switch(m[1].toLowerCase()) { + case "content-location": fname = m[2].trim(); break; + case "content-type": ctype = m[2].trim(); break; + case "content-transfer-encoding": cte = m[2].trim(); break; + } + } + ++di; + switch(cte.toLowerCase()) { + case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break; + case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break; + default: throw new Error("Unsupported Content-Transfer-Encoding " + cte); + } + var file = cfb_add(cfb, fname.slice(root.length), fdata, {unsafe: true}); + if(ctype) file.ctype = ctype; +} + +function parse_mad(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { + if(a2s(file.slice(0,13)).toLowerCase() != "mime-version:") throw new Error("Unsupported MAD header"); + var root = (options && options.root || ""); + // $FlowIgnore + var data = (has_buf && Buffer.isBuffer(file) ? file.toString("binary") : a2s(file)).split("\r\n"); + var di = 0, row = ""; + + /* if root is not specified, scan for the common prefix */ + for(di = 0; di < data.length; ++di) { + row = data[di]; + if(!/^Content-Location:/i.test(row)) continue; + row = row.slice(row.indexOf("file")); + if(!root) root = row.slice(0, row.lastIndexOf("/") + 1); + if(row.slice(0, root.length) == root) continue; + while(root.length > 0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array*/ = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:string*/ { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +}function cfb_new(opts/*:?any*/)/*:CFBContainer*/ { var o/*:CFBContainer*/ = ({}/*:any*/); init_cfb(o, opts); return o; diff --git a/index.html b/index.html index 0a67c47..06df8c6 100644 --- a/index.html +++ b/index.html @@ -33,8 +33,9 @@ a { text-decoration: none } Use readAsBinaryString: (when available) Export Current File -- Export data as CFB +- Export data as CFB (Container File Binary Format) - Export data as ZIP +- Export data as MAD (MIME aggregate document)

 
diff --git a/misc/flow.js b/misc/flow.js index a3c21f0..a6f1afa 100644 --- a/misc/flow.js +++ b/misc/flow.js @@ -95,5 +95,6 @@ type CFBEntry = { R: number; C: number; content?: CFBlob; + ctype?: string; } */ diff --git a/misc/node_version.sh b/misc/node_version.sh new file mode 100755 index 0000000..da9b9e0 --- /dev/null +++ b/misc/node_version.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash + +# This script will check the current version of node and install another version +# of npm if node is version 0.8 + +version=$(node --version) + +if [[ $version =~ v0\.8\. ]] +then + npm install -g npm@4.3.0 +fi \ No newline at end of file diff --git a/misc/suppress_export.js b/misc/suppress_export.js index 38daf0a..edd5288 100644 --- a/misc/suppress_export.js +++ b/misc/suppress_export.js @@ -16,4 +16,5 @@ declare var has_buf:boolean; declare var new_buf:any; declare var new_raw_buf:any; declare var new_unsafe_buf:any; +declare var Buffer_from:any; */ diff --git a/package.json b/package.json index 7d21c0e..5d42069 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cfb", - "version": "1.1.4", + "version": "1.2.0", "author": "sheetjs", "description": "Compound File Binary File Format extractor", "keywords": [ @@ -8,9 +8,6 @@ "compression", "office" ], - "bin": { - "cfb": "./bin/cfb.njs" - }, "main": "./cfb", "types": "types", "browser": { @@ -20,7 +17,6 @@ }, "dependencies": { "adler-32": "~1.2.0", - "commander": "^2.16.0", "crc-32": "~1.2.0", "printj": "~1.1.2" }, @@ -50,14 +46,13 @@ "files": [ "LICENSE", "README.md", - "bin/", "dist/", "types/index.d.ts", "types/tsconfig.json", "cfb.js", "xlscfb.flow.js" ], - "homepage": "http://sheetjs.com/opensource", + "homepage": "http://sheetjs.com/", "bugs": { "url": "https://github.com/SheetJS/js-cfb/issues" }, diff --git a/packages/cfb-cli/.npmignore b/packages/cfb-cli/.npmignore new file mode 100644 index 0000000..aa1ec1e --- /dev/null +++ b/packages/cfb-cli/.npmignore @@ -0,0 +1 @@ +*.tgz diff --git a/packages/cfb-cli/LICENSE b/packages/cfb-cli/LICENSE new file mode 100644 index 0000000..ac1d91a --- /dev/null +++ b/packages/cfb-cli/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2013-present SheetJS LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/cfb-cli/README.md b/packages/cfb-cli/README.md new file mode 100644 index 0000000..a3215c0 --- /dev/null +++ b/packages/cfb-cli/README.md @@ -0,0 +1,48 @@ +# Container File Blobs + +This CLI tool inspects and can manipulate supported files, leveraging the base +[`cfb` library](https://www.npmjs.com/package/cfb). + + +## Installation + +It is preferable to install the library globally with npm: + +```bash +$ npm install -g cfb-cli +``` + +The global installation adds a command `cfb-cli` which can work with files. + + +## Usage + +- `cfb file [names...]` extracts the contents of the file. If additional names + are supplied, only the listed files will be extracted. + +- `cfb -l file` lists the contained files (following `unzip -l` "short format") + +- `cfb -r file` attempts to repair by reading and re-writing the file. + This fixes some issues with files generated by non-standard tools. + +- `cfb -c file [files...]` creates a new file containing the listed files. + The default root entry name is `Root Entry`. + +- `cfb -a file [files...]` adds the listed files to the original file. + +- `cfb -d file [files...]` deletes the listed files from the original file. + + +## License + +Please consult the attached LICENSE file for details. All rights not explicitly +granted by the Apache 2.0 license are reserved by the Original Author. + + +## Credits + +Special thanks to [Garrett Luu](https://garrettluu.com/) for spinning off the +command from the CFB module. + + +[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-cfb?pixel)](https://github.com/SheetJS/js-cfb) diff --git a/packages/cfb-cli/package.json b/packages/cfb-cli/package.json index a5773ad..4f53853 100644 --- a/packages/cfb-cli/package.json +++ b/packages/cfb-cli/package.json @@ -1,6 +1,6 @@ { "name": "cfb-cli", - "version": "1.0.0", + "version": "1.0.1", "description": "Command-line interface for cfb", "bin": { "cfb-cli": "./bin/cfb.njs" @@ -11,7 +11,6 @@ "dependencies": { "cfb": "^1.1.4", "commander": "^5.1.0", - "fs": "0.0.1-security", "printj": "^1.2.2" } } diff --git a/types/index.d.ts b/types/index.d.ts index 70456dd..973ab39 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -23,7 +23,7 @@ export function writeFile(cfb: CFB$Container, filename: string, options?: CFB$Wr export const utils: CFB$Utils; export interface CFB$CommonOptions { - /** Input data encoding */ + /** Data encoding */ type?: 'base64' | 'binary' | 'buffer' | 'file' | 'array'; /** If true, throw errors when features are not understood */ @@ -39,7 +39,7 @@ export interface CFB$ParsingOptions extends CFB$CommonOptions { /** Options for write and writeFile */ export interface CFB$WritingOptions extends CFB$CommonOptions { /** Output file type */ - fileType?: 'cfb' | 'zip'; + fileType?: 'cfb' | 'zip' | 'mad'; /** Override default root entry name (CFB only) */ root?: string; @@ -87,6 +87,9 @@ export interface CFB$Entry { /** Storage location -- see CFB$StorageType */ storage?: string; + + /** Content Type (used for MAD) */ + ctype?: string; } /* File object */ diff --git a/xlscfb.flow.js b/xlscfb.flow.js index 067ae18..fefebdf 100644 --- a/xlscfb.flow.js +++ b/xlscfb.flow.js @@ -16,6 +16,7 @@ declare var has_buf:boolean; declare var new_buf:any; declare var new_raw_buf:any; declare var new_unsafe_buf:any; +declare var Buffer_from:any; */ /* cfb.js (C) 2013-present SheetJS -- http://sheetjs.com */ /* vim: set ts=2: */ @@ -142,7 +143,7 @@ CRC32.str = crc32_str; /* [MS-CFB] v20171201 */ var CFB = (function _CFB(){ var exports/*:CFBModule*/ = /*::(*/{}/*:: :any)*/; -exports.version = '1.1.4'; +exports.version = '1.2.0'; /* [MS-CFB] 2.6.4 */ function namecmp(l/*:string*/, r/*:string*/)/*:number*/ { var L = l.split("/"), R = r.split("/"); @@ -232,6 +233,7 @@ var fs/*:: = require('fs'); */; function get_fs() { return fs || (fs = require('fs')); } function parse(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options); +if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options); if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512"); var mver = 3; var ssz = 512; @@ -650,10 +652,15 @@ function rebuild_cfb(cfb/*:CFBContainer*/, f/*:?boolean*/)/*:void*/ { } -function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ { +function _write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var _opts = options || {}; + /* MAD is order-sensitive, skip rebuild and sort */ + if(_opts.fileType == 'mad') return write_mad(cfb, _opts); rebuild_cfb(cfb); - if(_opts.fileType == 'zip') return write_zip(cfb, _opts); + switch(_opts.fileType) { + case 'zip': return write_zip(cfb, _opts); + //case 'mad': return write_mad(cfb, _opts); + } var L = (function(cfb/*:CFBContainer*/)/*:Array*/{ var mini_size = 0, fat_size = 0; for(var i = 0; i < cfb.FileIndex.length; ++i) { @@ -847,10 +854,13 @@ function a2s(o/*:RawBytes*/)/*:string*/ { function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string*/ { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } @@ -1399,7 +1409,212 @@ function write_zip(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes*/ return bconcat(([bconcat((out/*:any*/)), bconcat(cdirs), o]/*:any*/)); } -function cfb_new(opts/*:?any*/)/*:CFBContainer*/ { +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}/*:any*/); + +function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr/*:string*/)/*:string*/ { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +
*/ = [], split = encoded.split("\r\n"); + for(var si = 0; si < split.length; ++si) { + var str = split[si]; + if(str.length == 0) { o.push(""); continue; } + for(var i = 0; i < str.length;) { + var end = 76; + var tmp = str.slice(i, i + end); + if(tmp.charAt(end - 1) == "=") end --; + else if(tmp.charAt(end - 2) == "=") end -= 2; + else if(tmp.charAt(end - 3) == "=") end -= 3; + tmp = str.slice(i, i + end); + i += end; + if(i < str.length) tmp += "="; + o.push(tmp); + } + } + + return o.join("\r\n"); +} +function parse_quoted_printable(data/*:Array*/)/*:RawBytes*/ { + var o = []; + + /* unify long lines */ + for(var di = 0; di < data.length; ++di) { + var line = data[di]; + while(di <= data.length && line.charAt(line.length - 1) == "=") line = line.slice(0, line.length - 1) + data[++di]; + o.push(line); + } + + /* decode */ + for(var oi = 0; oi < o.length; ++oi) o[oi] = o[oi].replace(/=[0-9A-Fa-f]{2}/g, function($$) { return String.fromCharCode(parseInt($$.slice(1), 16)); }); + return s2a(o.join("\r\n")); +} + + +function parse_mime(cfb/*:CFBContainer*/, data/*:Array*/, root/*:string*/)/*:void*/ { + var fname = "", cte = "", ctype = "", fdata; + var di = 0; + for(;di < 10; ++di) { + var line = data[di]; + if(!line || line.match(/^\s*$/)) break; + var m = line.match(/^(.*?):\s*([^\s].*)$/); + if(m) switch(m[1].toLowerCase()) { + case "content-location": fname = m[2].trim(); break; + case "content-type": ctype = m[2].trim(); break; + case "content-transfer-encoding": cte = m[2].trim(); break; + } + } + ++di; + switch(cte.toLowerCase()) { + case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break; + case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break; + default: throw new Error("Unsupported Content-Transfer-Encoding " + cte); + } + var file = cfb_add(cfb, fname.slice(root.length), fdata, {unsafe: true}); + if(ctype) file.ctype = ctype; +} + +function parse_mad(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { + if(a2s(file.slice(0,13)).toLowerCase() != "mime-version:") throw new Error("Unsupported MAD header"); + var root = (options && options.root || ""); + // $FlowIgnore + var data = (has_buf && Buffer.isBuffer(file) ? file.toString("binary") : a2s(file)).split("\r\n"); + var di = 0, row = ""; + + /* if root is not specified, scan for the common prefix */ + for(di = 0; di < data.length; ++di) { + row = data[di]; + if(!/^Content-Location:/i.test(row)) continue; + row = row.slice(row.indexOf("file")); + if(!root) root = row.slice(0, row.lastIndexOf("/") + 1); + if(row.slice(0, root.length) == root) continue; + while(root.length > 0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array*/ = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:string*/ { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +}function cfb_new(opts/*:?any*/)/*:CFBContainer*/ { var o/*:CFBContainer*/ = ({}/*:any*/); init_cfb(o, opts); return o; diff --git a/xlscfb.js b/xlscfb.js index a4a9ad6..ef2fa71 100644 --- a/xlscfb.js +++ b/xlscfb.js @@ -109,7 +109,7 @@ CRC32.str = crc32_str; /* [MS-CFB] v20171201 */ var CFB = (function _CFB(){ var exports = {}; -exports.version = '1.1.4'; +exports.version = '1.2.0'; /* [MS-CFB] 2.6.4 */ function namecmp(l, r) { var L = l.split("/"), R = r.split("/"); @@ -199,6 +199,7 @@ var fs; function get_fs() { return fs || (fs = require('fs')); } function parse(file, options) { if(file[0] == 0x50 && file[1] == 0x4b) return parse_zip(file, options); +if((file[0] | 0x20) == 0x6d && (file[1]|0x20) == 0x69) return parse_mad(file, options); if(file.length < 512) throw new Error("CFB file size " + file.length + " < 512"); var mver = 3; var ssz = 512; @@ -619,8 +620,13 @@ function rebuild_cfb(cfb, f) { function _write(cfb, options) { var _opts = options || {}; + /* MAD is order-sensitive, skip rebuild and sort */ + if(_opts.fileType == 'mad') return write_mad(cfb, _opts); rebuild_cfb(cfb); - if(_opts.fileType == 'zip') return write_zip(cfb, _opts); + switch(_opts.fileType) { + case 'zip': return write_zip(cfb, _opts); + //case 'mad': return write_mad(cfb, _opts); + } var L = (function(cfb){ var mini_size = 0, fat_size = 0; for(var i = 0; i < cfb.FileIndex.length; ++i) { @@ -808,10 +814,13 @@ function a2s(o) { function write(cfb, options) { var o = _write(cfb, options); - switch(options && options.type) { + switch(options && options.type || "buffer") { case "file": get_fs(); fs.writeFileSync(options.filename, (o)); return o; - case "binary": return a2s(o); - case "base64": return Base64.encode(a2s(o)); + case "binary": return typeof o == "string" ? o : a2s(o); + case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o)); + case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o); + /* falls through */ + case "array": return typeof o == "string" ? s2a(o) : o; } return o; } @@ -1360,7 +1369,212 @@ function write_zip(cfb, options) { return bconcat(([bconcat((out)), bconcat(cdirs), o])); } -function cfb_new(opts) { +var ContentTypeMap = ({ + "htm": "text/html", + "xml": "text/xml", + + "gif": "image/gif", + "jpg": "image/jpeg", + "png": "image/png", + + "mso": "application/x-mso", + "thmx": "application/vnd.ms-officetheme", + "sh33tj5": "application/octet-stream" +}); + +function get_content_type(fi, fp) { + if(fi.ctype) return fi.ctype; + + var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + + if(fp) { + m = (ext = fp).match(/[\.\\]([^\.\\])+$/); + if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; + } + + return "application/octet-stream"; +} + +/* 76 character chunks TODO: intertwine encoding */ +function write_base64_76(bstr) { + var data = Base64.encode(bstr); + var o = []; + for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); + return o.join("\r\n") + "\r\n"; +} + +/* +Rules for QP: + - escape =## applies for all non-display characters and literal "=" + - space or tab at end of line must be encoded + - \r\n newlines can be preserved, but bare \r and \n must be escaped + - lines must not exceed 76 characters, use soft breaks =\r\n + +TODO: Some files from word appear to write line extensions with bare equals: + +``` +
0) { + root = root.slice(0, root.length - 1); + root = root.slice(0, root.lastIndexOf("/") + 1); + if(row.slice(0,root.length) == root) break; + } + } + + var mboundary = (data[1] || "").match(/boundary="(.*?)"/); + if(!mboundary) throw new Error("MAD cannot find boundary"); + var boundary = "--" + (mboundary[1] || ""); + + var FileIndex = [], FullPaths = []; + var o = { + FileIndex: FileIndex, + FullPaths: FullPaths + }; + init_cfb(o); + var start_di, fcnt = 0; + for(di = 0; di < data.length; ++di) { + var line = data[di]; + if(line !== boundary && line !== boundary + "--") continue; + if(fcnt++) parse_mime(o, data.slice(start_di, di), root); + start_di = di; + } + return o; +} + +function write_mad(cfb, options) { + var opts = options || {}; + var boundary = opts.boundary || "SheetJS"; + boundary = '------=' + boundary; + + var out = [ + 'MIME-Version: 1.0', + 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', + '', + '', + '' + ]; + + var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; + for(var i = 1; i < cfb.FullPaths.length; ++i) { + fp = cfb.FullPaths[i].slice(root.length); + fi = cfb.FileIndex[i]; + if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; + + /* Normalize filename */ + fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { + return "_x" + c.charCodeAt(0).toString(16) + "_"; + }).replace(/[\u0080-\uFFFF]/g, function(u) { + return "_u" + u.charCodeAt(0).toString(16) + "_"; + }); + + /* Extract content as binary string */ + var ca = fi.content; + // $FlowIgnore + var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); + + /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ + var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; + for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; + var qp = dispcnt >= L * 4 / 5; + + out.push(boundary); + out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); + out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); + out.push('Content-Type: ' + get_content_type(fi, fp)); + out.push(''); + + out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); + } + out.push(boundary + '--\r\n'); + return out.join("\r\n"); +}function cfb_new(opts) { var o = ({}); init_cfb(o, opts); return o;