var ContentTypeMap = ({ "htm": "text/html", "xml": "text/xml", "gif": "image/gif", "jpg": "image/jpeg", "png": "image/png", "mso": "application/x-mso", "thmx": "application/vnd.ms-officetheme", "sh33tj5": "application/octet-stream" }/*:any*/); function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ { if(fi.ctype) return fi.ctype; var ext = fi.name || "", m = ext.match(/\.([^\.]+)$/); if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; if(fp) { m = (ext = fp).match(/[\.\\]([^\.\\])+$/); if(m && ContentTypeMap[m[1]]) return ContentTypeMap[m[1]]; } return "application/octet-stream"; } /* 76 character chunks TODO: intertwine encoding */ function write_base64_76(bstr/*:string*/)/*:string*/ { var data = Base64.encode(bstr); var o = []; for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76)); return o.join("\r\n") + "\r\n"; } /* Rules for QP: - escape =## applies for all non-display characters and literal "=" - space or tab at end of line must be encoded - \r\n newlines can be preserved, but bare \r and \n must be escaped - lines must not exceed 76 characters, use soft breaks =\r\n TODO: Some files from word appear to write line extensions with bare equals: ``` */ = [], split = encoded.split("\r\n"); for(var si = 0; si < split.length; ++si) { var str = split[si]; if(str.length == 0) { o.push(""); continue; } for(var i = 0; i < str.length;) { var end = 76; var tmp = str.slice(i, i + end); if(tmp.charAt(end - 1) == "=") end --; else if(tmp.charAt(end - 2) == "=") end -= 2; else if(tmp.charAt(end - 3) == "=") end -= 3; tmp = str.slice(i, i + end); i += end; if(i < str.length) tmp += "="; o.push(tmp); } } return o.join("\r\n"); } function parse_quoted_printable(data/*:Array*/)/*:RawBytes*/ { var o = []; /* unify long lines */ for(var di = 0; di < data.length; ++di) { var line = data[di]; while(di <= data.length && line.charAt(line.length - 1) == "=") line = line.slice(0, line.length - 1) + data[++di]; o.push(line); } /* decode */ for(var oi = 0; oi < o.length; ++oi) o[oi] = o[oi].replace(/=[0-9A-Fa-f]{2}/g, function($$) { return String.fromCharCode(parseInt($$.slice(1), 16)); }); return s2a(o.join("\r\n")); } function parse_mime(cfb/*:CFBContainer*/, data/*:Array*/, root/*:string*/)/*:void*/ { var fname = "", cte = "", ctype = "", fdata; var di = 0; for(;di < 10; ++di) { var line = data[di]; if(!line || line.match(/^\s*$/)) break; var m = line.match(/^(.*?):\s*([^\s].*)$/); if(m) switch(m[1].toLowerCase()) { case "content-location": fname = m[2].trim(); break; case "content-type": ctype = m[2].trim(); break; case "content-transfer-encoding": cte = m[2].trim(); break; } } ++di; switch(cte.toLowerCase()) { case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break; case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break; default: throw new Error("Unsupported Content-Transfer-Encoding " + cte); } var file = cfb_add(cfb, fname.slice(root.length), fdata, {unsafe: true}); if(ctype) file.ctype = ctype; } function parse_mad(file/*:RawBytes*/, options/*:CFBReadOpts*/)/*:CFBContainer*/ { if(a2s(file.slice(0,13)).toLowerCase() != "mime-version:") throw new Error("Unsupported MAD header"); var root = (options && options.root || ""); // $FlowIgnore var data = (has_buf && Buffer.isBuffer(file) ? file.toString("binary") : a2s(file)).split("\r\n"); var di = 0, row = ""; /* if root is not specified, scan for the common prefix */ for(di = 0; di < data.length; ++di) { row = data[di]; if(!/^Content-Location:/i.test(row)) continue; row = row.slice(row.indexOf("file")); if(!root) root = row.slice(0, row.lastIndexOf("/") + 1); if(row.slice(0, root.length) == root) continue; while(root.length > 0) { root = root.slice(0, root.length - 1); root = root.slice(0, root.lastIndexOf("/") + 1); if(row.slice(0,root.length) == root) break; } } var mboundary = (data[1] || "").match(/boundary="(.*?)"/); if(!mboundary) throw new Error("MAD cannot find boundary"); var boundary = "--" + (mboundary[1] || ""); var FileIndex/*:CFBFileIndex*/ = [], FullPaths/*:Array*/ = []; var o = { FileIndex: FileIndex, FullPaths: FullPaths }; init_cfb(o); var start_di, fcnt = 0; for(di = 0; di < data.length; ++di) { var line = data[di]; if(line !== boundary && line !== boundary + "--") continue; if(fcnt++) parse_mime(o, data.slice(start_di, di), root); start_di = di; } return o; } function write_mad(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:string*/ { var opts = options || {}; var boundary = opts.boundary || "SheetJS"; boundary = '------=' + boundary; var out = [ 'MIME-Version: 1.0', 'Content-Type: multipart/related; boundary="' + boundary.slice(2) + '"', '', '', '' ]; var root = cfb.FullPaths[0], fp = root, fi = cfb.FileIndex[0]; for(var i = 1; i < cfb.FullPaths.length; ++i) { fp = cfb.FullPaths[i].slice(root.length); fi = cfb.FileIndex[i]; if(!fi.size || !fi.content || fp == "\u0001Sh33tJ5") continue; /* Normalize filename */ fp = fp.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7E-\xFF]/g, function(c) { return "_x" + c.charCodeAt(0).toString(16) + "_"; }).replace(/[\u0080-\uFFFF]/g, function(u) { return "_u" + u.charCodeAt(0).toString(16) + "_"; }); /* Extract content as binary string */ var ca = fi.content; // $FlowIgnore var cstr = has_buf && Buffer.isBuffer(ca) ? ca.toString("binary") : a2s(ca); /* 4/5 of first 1024 chars ascii -> quoted printable, else base64 */ var dispcnt = 0, L = Math.min(1024, cstr.length), cc = 0; for(var csl = 0; csl <= L; ++csl) if((cc=cstr.charCodeAt(csl)) >= 0x20 && cc < 0x80) ++dispcnt; var qp = dispcnt >= L * 4 / 5; out.push(boundary); out.push('Content-Location: ' + (opts.root || 'file:///C:/SheetJS/') + fp); out.push('Content-Transfer-Encoding: ' + (qp ? 'quoted-printable' : 'base64')); out.push('Content-Type: ' + get_content_type(fi, fp)); out.push(''); out.push(qp ? write_quoted_printable(cstr) : write_base64_76(cstr)); } out.push(boundary + '--\r\n'); return out.join("\r\n"); }