version bump 1.2.0: MAD
This commit is contained in:
parent
1a1920c567
commit
76e4603fa5
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,6 +27,7 @@ test_files_pres
|
||||
*.[eE][tT][hH]
|
||||
*.[zZ][iI][pP]
|
||||
*.[mM][sS][iIgG]
|
||||
*.[mM][hH][tT]
|
||||
*.123
|
||||
*.htm
|
||||
*.html
|
||||
|
10
.travis.yml
10
.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"
|
||||
|
@ -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
|
||||
|
2
Makefile
2
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)
|
||||
|
36
README.md
36
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
|
||||
|
||||
|
@ -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; });
|
||||
};
|
||||
|
@ -1 +1 @@
|
||||
exports.version = '1.1.4';
|
||||
exports.version = '1.2.0';
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
206
bits/84_mht.js
Normal file
206
bits/84_mht.js
Normal file
@ -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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text/*:string*/)/*:string*/ {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o/*:Array<string>*/ = [], 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<string>*/)/*: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<string>*/, 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<string>*/ = [];
|
||||
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");
|
||||
}
|
230
cfb.flow.js
230
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<number>*/{
|
||||
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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text/*:string*/)/*:string*/ {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o/*:Array<string>*/ = [], 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<string>*/)/*: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<string>*/, 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<string>*/ = [];
|
||||
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;
|
||||
|
226
cfb.js
226
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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text) {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o = [], 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) {
|
||||
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, data, root) {
|
||||
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, options) {
|
||||
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 = [], 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;
|
||||
|
226
dist/cfb.js
vendored
226
dist/cfb.js
vendored
@ -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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text) {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o = [], 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) {
|
||||
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, data, root) {
|
||||
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, options) {
|
||||
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 = [], 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;
|
||||
|
3
dist/cfb.min.js
vendored
3
dist/cfb.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/cfb.min.map
vendored
2
dist/cfb.min.map
vendored
File diff suppressed because one or more lines are too long
229
dist/xlscfb.js
vendored
229
dist/xlscfb.js
vendored
@ -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<number>*/{
|
||||
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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text/*:string*/)/*:string*/ {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o/*:Array<string>*/ = [], 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<string>*/)/*: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<string>*/, 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<string>*/ = [];
|
||||
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;
|
||||
|
@ -33,8 +33,9 @@ a { text-decoration: none }
|
||||
Use readAsBinaryString: (when available) <input type="checkbox" name="userabs" checked>
|
||||
|
||||
<b>Export Current File</b>
|
||||
- <a id="savecfb" onclick="savefile('cfb');" href="#">Export data as CFB</a>
|
||||
- <a id="savecfb" onclick="savefile('cfb');" href="#">Export data as CFB</a> (Container File Binary Format)
|
||||
- <a id="savezip" onclick="savefile('zip');" href="#">Export data as ZIP</a>
|
||||
- <a id="savemad" onclick="savefile('mad');" href="#">Export data as MAD</a> (MIME aggregate document)
|
||||
</pre>
|
||||
<pre id="out"></pre>
|
||||
<br />
|
||||
|
@ -95,5 +95,6 @@ type CFBEntry = {
|
||||
R: number;
|
||||
C: number;
|
||||
content?: CFBlob;
|
||||
ctype?: string;
|
||||
}
|
||||
*/
|
||||
|
11
misc/node_version.sh
Executable file
11
misc/node_version.sh
Executable file
@ -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
|
@ -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;
|
||||
*/
|
||||
|
@ -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"
|
||||
},
|
||||
|
1
packages/cfb-cli/.npmignore
Normal file
1
packages/cfb-cli/.npmignore
Normal file
@ -0,0 +1 @@
|
||||
*.tgz
|
201
packages/cfb-cli/LICENSE
Normal file
201
packages/cfb-cli/LICENSE
Normal file
@ -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.
|
48
packages/cfb-cli/README.md
Normal file
48
packages/cfb-cli/README.md
Normal file
@ -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)
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
7
types/index.d.ts
vendored
7
types/index.d.ts
vendored
@ -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 */
|
||||
|
229
xlscfb.flow.js
229
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<number>*/{
|
||||
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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text/*:string*/)/*:string*/ {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o/*:Array<string>*/ = [], 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<string>*/)/*: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<string>*/, 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<string>*/ = [];
|
||||
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;
|
||||
|
226
xlscfb.js
226
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:
|
||||
|
||||
```
|
||||
<table class=3DMsoTableGrid border=3D1 cellspacing=3D0 cellpadding=3D0 width=
|
||||
="70%"
|
||||
```
|
||||
*/
|
||||
function write_quoted_printable(text) {
|
||||
var encoded = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF=]/g, function(c) {
|
||||
var w = c.charCodeAt(0).toString(16).toUpperCase();
|
||||
return "=" + (w.length == 1 ? "0" + w : w);
|
||||
});
|
||||
|
||||
encoded = encoded.replace(/ $/mg, "=20").replace(/\t$/mg, "=09");
|
||||
|
||||
if(encoded.charAt(0) == "\n") encoded = "=0D" + encoded.slice(1);
|
||||
encoded = encoded.replace(/\r(?!\n)/mg, "=0D").replace(/\n\n/mg, "\n=0A").replace(/([^\r\n])\n/mg, "$1=0A");
|
||||
|
||||
var o = [], 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) {
|
||||
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, data, root) {
|
||||
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, options) {
|
||||
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 = [], 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;
|
||||
|
Loading…
Reference in New Issue
Block a user