1 /** 2 * Copyright (C) 2012 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global runtime, core, DOMParser, externs*/ 26 /*jslint bitwise: true*/ 27 28 /** 29 * @constructor 30 * @param {!string} url path to zip file, should be readable by the runtime 31 * @param {?function(?string, !core.Zip):undefined} entriesReadCallback callback 32 * indicating the zip 33 * has loaded this list of entries, the arguments are a string that 34 * indicates error if present and the created object 35 */ 36 core.Zip = function Zip(url, entriesReadCallback) { 37 "use strict"; 38 var /**@type{!core.Zip}*/ 39 self = this, 40 /**@type{!JSZip}*/ 41 zip, 42 base64 = new core.Base64(); 43 44 /** 45 * @param {!string} filename 46 * @param {!function(?string, ?Uint8Array)} callback receiving err and data 47 * @return {undefined} 48 */ 49 function load(filename, callback) { 50 var entry = zip.file(filename); 51 if (entry) { 52 callback(null, entry.asUint8Array()); 53 } else { 54 callback(filename + " not found.", null); 55 } 56 } 57 /** 58 * @param {!string} filename 59 * @param {!function(?string, ?string):undefined} callback receiving err and data 60 * @return {undefined} 61 */ 62 function loadAsString(filename, callback) { 63 // the javascript implementation simply reads the file and converts to 64 // string 65 load(filename, function (err, data) { 66 if (err || data === null) { 67 return callback(err, null); 68 } 69 var d = runtime.byteArrayToString(data, "utf8"); 70 callback(null, d); 71 }); 72 } 73 /** 74 * @param {!string} filename 75 * @param {!{rootElementReady: function(?string, ?string=, boolean=):undefined}} handler 76 * @return {undefined} 77 */ 78 function loadContentXmlAsFragments(filename, handler) { 79 // the javascript implementation simply reads the file 80 loadAsString(filename, function (err, data) { 81 if (err) { 82 return handler.rootElementReady(err); 83 } 84 handler.rootElementReady(null, data, true); 85 }); 86 } 87 /** 88 * @param {!string} filename 89 * @param {!string} mimetype 90 * @param {!function(?string,?string):undefined} callback 91 */ 92 function loadAsDataURL(filename, mimetype, callback) { 93 load(filename, function (err, data) { 94 if (err || !data) { 95 return callback(err, null); 96 } 97 var /**@const@type{!Uint8Array}*/p = data, 98 chunksize = 45000, // must be multiple of 3 and less than 50000 99 i = 0, 100 dataurl; 101 if (!mimetype) { 102 if (p[1] === 0x50 && p[2] === 0x4E && p[3] === 0x47) { 103 mimetype = "image/png"; 104 } else if (p[0] === 0xFF && p[1] === 0xD8 && p[2] === 0xFF) { 105 mimetype = "image/jpeg"; 106 } else if (p[0] === 0x47 && p[1] === 0x49 && p[2] === 0x46) { 107 mimetype = "image/gif"; 108 } else { 109 mimetype = ""; 110 } 111 } 112 dataurl = 'data:' + mimetype + ';base64,'; 113 // to avoid exceptions, base64 encoding is done in chunks 114 // it would make sense to move this to base64.toBase64 115 while (i < data.length) { 116 dataurl += base64.convertUTF8ArrayToBase64( 117 p.subarray(i, Math.min(i + chunksize, p.length)) 118 ); 119 i += chunksize; 120 } 121 callback(null, dataurl); 122 }); 123 } 124 /** 125 * @param {!string} filename 126 * @param {function(?string,?Document):undefined} callback 127 * @return {undefined} 128 */ 129 function loadAsDOM(filename, callback) { 130 loadAsString(filename, function (err, xmldata) { 131 if (err || xmldata === null) { 132 callback(err, null); 133 return; 134 } 135 var parser = new DOMParser(), 136 dom = parser.parseFromString(xmldata, "text/xml"); 137 callback(null, dom); 138 }); 139 } 140 /** 141 * Add or replace an entry to the zip file. 142 * This data is not stored to disk yet, and therefore, no callback is 143 * necessary. 144 * @param {!string} filename 145 * @param {!Uint8Array} data 146 * @param {!boolean} compressed 147 * @param {!Date} date 148 * @return {undefined} 149 */ 150 function save(filename, data, compressed, date) { 151 zip.file(filename, data, {date: date, compression: compressed ? null : "STORE"}); 152 } 153 /** 154 * Removes entry from the zip. 155 * @param {!string} filename 156 * @return {!boolean} return false if entry is not found; otherwise true. 157 */ 158 function remove(filename) { 159 var exists = zip.file(filename) !== null; 160 zip.remove(filename); 161 return exists; 162 } 163 /** 164 * Create a bytearray from the zipfile. 165 * @param {!function(!Uint8Array):undefined} successCallback receiving zip as bytearray 166 * @param {!function(?string):undefined} errorCallback receiving possible err 167 * @return {undefined} 168 */ 169 function createByteArray(successCallback, errorCallback) { 170 try { 171 successCallback(/**@type{!Uint8Array}*/(zip.generate({type: "uint8array", compression: "DEFLATE"}))); 172 } catch(/**@type{!Error}*/e) { 173 errorCallback(e.message); 174 } 175 } 176 /** 177 * Write the zipfile to the given path. 178 * @param {!string} newurl 179 * @param {!function(?string):undefined} callback receiving possible err 180 * @return {undefined} 181 */ 182 function writeAs(newurl, callback) { 183 createByteArray(function (data) { 184 runtime.writeFile(newurl, data, callback); 185 }, callback); 186 } 187 /** 188 * Write the zipfile to the given path. 189 * @param {!function(?string):undefined} callback receiving possible err 190 * @return {undefined} 191 */ 192 function write(callback) { 193 writeAs(url, callback); 194 } 195 this.load = load; 196 this.save = save; 197 this.remove = remove; 198 this.write = write; 199 this.writeAs = writeAs; 200 this.createByteArray = createByteArray; 201 // a special function that makes faster odf loading possible 202 this.loadContentXmlAsFragments = loadContentXmlAsFragments; 203 this.loadAsString = loadAsString; 204 this.loadAsDOM = loadAsDOM; 205 this.loadAsDataURL = loadAsDataURL; 206 207 /** 208 * @return {!Array.<!{filename: !string}>} 209 */ 210 this.getEntries = function () { 211 return Object.keys(zip.files).map(function(filename) { return { filename: filename }; }); 212 }; 213 214 zip = new externs.JSZip(); 215 // if no callback is defined, this is a new file 216 if (entriesReadCallback === null) { 217 return; 218 } 219 runtime.readFile(url, "binary", function (err, result) { 220 if (typeof result === "string") { 221 err = "file was read as a string. Should be Uint8Array."; 222 } 223 if (err || !result || result.length === 0) { 224 entriesReadCallback("File '" + url + "' cannot be read. Err: " + (err || "[none]"), self); 225 } else { 226 try { 227 // CRC32 check disabled to improve performance 228 zip.load(/**@type{!Uint8Array}*/(result), { checkCRC32: false }); 229 entriesReadCallback(null, self); 230 } catch (/**@type{!Error}*/e) { 231 entriesReadCallback(e.message, self); 232 } 233 } 234 }); 235 }; 236