| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium OS Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** | |
| 8 * A class that takes care of communication between NaCl and archive volume. | |
| 9 * Its job is to handle communication with the naclModule. | |
| 10 * @constructor | |
| 11 * @param {!Object} naclModule The nacl module with which the decompressor | |
| 12 * communicates. | |
| 13 * @param {!unpacker.types.FileSystemId} fileSystemId The file system id of for | |
| 14 * the archive volume to decompress. | |
| 15 * @param {!Blob} blob The correspondent file blob for fileSystemId. | |
| 16 * @param {!unpacker.PassphraseManager} passphraseManager Passphrase manager. | |
| 17 */ | |
| 18 unpacker.Decompressor = function(naclModule, fileSystemId, blob, | |
| 19 passphraseManager) { | |
| 20 /** | |
| 21 * @private {!Object} | |
| 22 * @const | |
| 23 */ | |
| 24 this.naclModule_ = naclModule; | |
| 25 | |
| 26 /** | |
| 27 * @private {!unpacker.types.FileSystemId} | |
| 28 * @const | |
| 29 */ | |
| 30 this.fileSystemId_ = fileSystemId; | |
| 31 | |
| 32 /** | |
| 33 * @private {!Blob} | |
| 34 * @const | |
| 35 */ | |
| 36 this.blob_ = blob; | |
| 37 | |
| 38 /** | |
| 39 * @public {!unpacker.PassphraseManager} | |
| 40 * @const | |
| 41 */ | |
| 42 this.passphraseManager = passphraseManager; | |
| 43 | |
| 44 /** | |
| 45 * Requests in progress. No need to save them onSuspend for now as metadata | |
| 46 * reads are restarted from start. | |
| 47 * @public {!Object<!unpacker.types.RequestId, !Object>} | |
| 48 * @const | |
| 49 */ | |
| 50 this.requestsInProgress = {}; | |
| 51 }; | |
| 52 | |
| 53 /** | |
| 54 * @return {boolean} True if there is any request in progress. | |
| 55 */ | |
| 56 unpacker.Decompressor.prototype.hasRequestsInProgress = function() { | |
| 57 return Object.keys(this.requestsInProgress).length > 0; | |
| 58 }; | |
| 59 | |
| 60 /** | |
| 61 * Sends a request to NaCl and mark it as a request in progress. onSuccess and | |
| 62 * onError are the callbacks used when receiving an answer from NaCl. | |
| 63 * @param {!unpacker.types.RequestId} requestId The operation request id, which | |
| 64 * should be unique per every volume. | |
| 65 * @param {function(...)} onSuccess Callback to execute on success. | |
| 66 * @param {function(!ProviderError)} onError Callback to execute on error. | |
| 67 * @param {!Object} naclRequest A request that must be sent to NaCl using | |
| 68 * postMessage. | |
| 69 * @private | |
| 70 */ | |
| 71 unpacker.Decompressor.prototype.addRequest_ = function(requestId, onSuccess, | |
| 72 onError, naclRequest) { | |
| 73 console.assert(!this.requestsInProgress[requestId], | |
| 74 'There is already a request with the id ' + requestId + '.'); | |
| 75 | |
| 76 this.requestsInProgress[requestId] = { | |
| 77 onSuccess: onSuccess, | |
| 78 onError: onError | |
| 79 }; | |
| 80 | |
| 81 this.naclModule_.postMessage(naclRequest); | |
| 82 }; | |
| 83 | |
| 84 /** | |
| 85 * Creates a request for reading metadata. | |
| 86 * @param {!unpacker.types.RequestId} requestId | |
| 87 * @param {string} encoding Default encoding for the archive's headers. | |
| 88 * @param {function(!Object<string, !Object>)} onSuccess Callback to execute | |
| 89 * once the metadata is obtained from NaCl. It has one parameter, which is | |
| 90 * the metadata itself. The metadata has as key the full path to an entry | |
| 91 * and as value information about the entry. | |
| 92 * @param {function(!ProviderError)} onError Callback to execute on error. | |
| 93 */ | |
| 94 unpacker.Decompressor.prototype.readMetadata = function(requestId, encoding, | |
| 95 onSuccess, onError) { | |
| 96 this.addRequest_( | |
| 97 requestId, onSuccess, onError, | |
| 98 unpacker.request.createReadMetadataRequest(this.fileSystemId_, requestId, | |
| 99 encoding, this.blob_.size)); | |
| 100 }; | |
| 101 | |
| 102 /** | |
| 103 * Sends an open file request to NaCl. | |
| 104 * @param {!unpacker.types.RequestId} requestId | |
| 105 * @param {number} index Index of the file in the header list. | |
| 106 * @param {string} encoding Default encoding for the archive's headers. | |
| 107 * @param {function()} onSuccess Callback to execute on successful open. | |
| 108 * @param {function(!ProviderError)} onError Callback to execute on error. | |
| 109 */ | |
| 110 unpacker.Decompressor.prototype.openFile = function(requestId, index, encoding, | |
| 111 onSuccess, onError) { | |
| 112 this.addRequest_( | |
| 113 requestId, onSuccess, onError, | |
| 114 unpacker.request.createOpenFileRequest(this.fileSystemId_, requestId, | |
| 115 index, encoding, this.blob_.size)); | |
| 116 }; | |
| 117 | |
| 118 /** | |
| 119 * Sends a close file request to NaCl. | |
| 120 * @param {!unpacker.types.RequestId} requestId | |
| 121 * @param {!unpacker.types.RequestId} openRequestId The request id of the | |
| 122 * corresponding open file operation for the file to close. | |
| 123 * @param {function()} onSuccess Callback to execute on successful open. | |
| 124 * @param {function(!ProviderError)} onError Callback to execute on error. | |
| 125 */ | |
| 126 unpacker.Decompressor.prototype.closeFile = function(requestId, openRequestId, | |
| 127 onSuccess, onError) { | |
| 128 this.addRequest_(requestId, onSuccess, onError, | |
| 129 unpacker.request.createCloseFileRequest( | |
| 130 this.fileSystemId_, requestId, openRequestId)); | |
| 131 }; | |
| 132 | |
| 133 /** | |
| 134 * Sends a read file request to NaCl. | |
| 135 * @param {!unpacker.types.RequestId} requestId | |
| 136 * @param {!unpacker.types.RequestId} openRequestId The request id of the | |
| 137 * corresponding open file operation for the file to read. | |
| 138 * @param {number} offset The offset from where read operation should start. | |
| 139 * @param {number} length The number of bytes to read. | |
| 140 * @param {function(!ArrayBuffer, boolean)} onSuccess Callback to execute on | |
| 141 * success. | |
| 142 * @param {function(!ProviderError)} onError Callback to execute on error. | |
| 143 */ | |
| 144 unpacker.Decompressor.prototype.readFile = function( | |
| 145 requestId, openRequestId, offset, length, onSuccess, onError) { | |
| 146 this.addRequest_( | |
| 147 requestId, onSuccess, onError, | |
| 148 unpacker.request.createReadFileRequest(this.fileSystemId_, requestId, | |
| 149 openRequestId, offset, length)); | |
| 150 }; | |
| 151 | |
| 152 /** | |
| 153 * Processes messages from NaCl module. | |
| 154 * @param {!Object} data The data contained in the message from NaCl. Its | |
| 155 * types depend on the operation of the request. | |
| 156 * @param {!unpacker.request.Operation} operation An operation from request.js. | |
| 157 * @param {number} requestId The request id, which should be unique per every | |
| 158 * volume. | |
| 159 */ | |
| 160 unpacker.Decompressor.prototype.processMessage = function(data, operation, | |
| 161 requestId) { | |
| 162 // Create a request reference for asynchronous calls as sometimes we delete | |
| 163 // some requestsInProgress from this.requestsInProgress. | |
| 164 var requestInProgress = this.requestsInProgress[requestId]; | |
| 165 console.assert(requestInProgress, 'No request with id <' + requestId + | |
| 166 '> for: ' + this.fileSystemId_ + '.'); | |
| 167 | |
| 168 switch (operation) { | |
| 169 case unpacker.request.Operation.READ_METADATA_DONE: | |
| 170 var metadata = data[unpacker.request.Key.METADATA]; | |
| 171 console.assert(metadata, 'No metadata.'); | |
| 172 requestInProgress.onSuccess(metadata); | |
| 173 break; | |
| 174 | |
| 175 case unpacker.request.Operation.READ_CHUNK: | |
| 176 this.readChunk_(data, requestId); | |
| 177 // this.requestsInProgress_[requestId] should be valid as long as NaCL | |
| 178 // can still make READ_CHUNK requests. | |
| 179 return; | |
| 180 | |
| 181 case unpacker.request.Operation.READ_PASSPHRASE: | |
| 182 this.readPassphrase_(data, requestId); | |
| 183 // this.requestsInProgress_[requestId] should be valid as long as NaCL | |
| 184 // can still make READ_PASSPHRASE requests. | |
| 185 return; | |
| 186 | |
| 187 case unpacker.request.Operation.OPEN_FILE_DONE: | |
| 188 requestInProgress.onSuccess(); | |
| 189 // this.requestsInProgress_[requestId] should be valid until closing the | |
| 190 // file so NaCL can make READ_CHUNK requests. | |
| 191 return; | |
| 192 | |
| 193 case unpacker.request.Operation.CLOSE_FILE_DONE: | |
| 194 var openRequestId = data[unpacker.request.Key.OPEN_REQUEST_ID]; | |
| 195 console.assert(openRequestId, 'No open request id.'); | |
| 196 | |
| 197 openRequestId = Number(openRequestId); // Received as string. | |
| 198 delete this.requestsInProgress[openRequestId]; | |
| 199 requestInProgress.onSuccess(); | |
| 200 break; | |
| 201 | |
| 202 case unpacker.request.Operation.READ_FILE_DONE: | |
| 203 var buffer = data[unpacker.request.Key.READ_FILE_DATA]; | |
| 204 console.assert(buffer, 'No buffer for read file operation.'); | |
| 205 var hasMoreData = data[unpacker.request.Key.HAS_MORE_DATA]; | |
| 206 console.assert(buffer !== undefined, | |
| 207 'No HAS_MORE_DATA boolean value for file operation.'); | |
| 208 | |
| 209 requestInProgress.onSuccess(buffer, hasMoreData /* Last call. */); | |
| 210 if (hasMoreData) | |
| 211 return; // Do not delete requestInProgress. | |
| 212 break; | |
| 213 | |
| 214 case unpacker.request.Operation.FILE_SYSTEM_ERROR: | |
| 215 console.error('File system error for <' + this.fileSystemId_ + '>: ' + | |
| 216 data[unpacker.request.Key.ERROR]); // The error contains | |
| 217 // the '.' at the end. | |
| 218 requestInProgress.onError('FAILED'); | |
| 219 break; | |
| 220 | |
| 221 case unpacker.request.Operation.CONSOLE_LOG: | |
| 222 case unpacker.request.Operation.CONSOLE_DEBUG: | |
| 223 var src_file = data[unpacker.request.Key.SRC_FILE]; | |
| 224 var src_line = data[unpacker.request.Key.SRC_LINE]; | |
| 225 var src_func = data[unpacker.request.Key.SRC_FUNC]; | |
| 226 var msg = data[unpacker.request.Key.MESSAGE]; | |
| 227 var log = operation == unpacker.request.Operation.CONSOLE_LOG ? | |
| 228 console.log : console.debug; | |
| 229 log(src_file + ':' + src_func + ':' + src_line + ': ' + msg); | |
| 230 break; | |
| 231 | |
| 232 default: | |
| 233 console.error('Invalid NaCl operation: ' + operation + '.'); | |
| 234 requestInProgress.onError('FAILED'); | |
| 235 } | |
| 236 delete this.requestsInProgress[requestId]; | |
| 237 }; | |
| 238 | |
| 239 /** | |
| 240 * Reads a chunk of data from this.blob_ for READ_CHUNK operation. | |
| 241 * @param {!Object} data The data received from the NaCl module. | |
| 242 * @param {number} requestId The request id, which should be unique per every | |
| 243 * volume. | |
| 244 * @private | |
| 245 */ | |
| 246 unpacker.Decompressor.prototype.readChunk_ = function(data, requestId) { | |
| 247 // Offset and length are received as strings. See request.js. | |
| 248 var offset_str = data[unpacker.request.Key.OFFSET]; | |
| 249 var length_str = data[unpacker.request.Key.LENGTH]; | |
| 250 | |
| 251 // Explicit check if offset is undefined as it can be 0. | |
| 252 console.assert(offset_str !== undefined && !isNaN(offset_str) && | |
| 253 Number(offset_str) >= 0 && | |
| 254 Number(offset_str) < this.blob_.size, | |
| 255 'Invalid offset.'); | |
| 256 console.assert(length_str && !isNaN(length_str) && Number(length_str) > 0, | |
| 257 'Invalid length.'); | |
| 258 | |
| 259 var offset = Number(offset_str); | |
| 260 var length = Math.min(this.blob_.size - offset, Number(length_str)); | |
| 261 | |
| 262 // Read a chunk from offset to offset + length. | |
| 263 var blob = this.blob_.slice(offset, offset + length); | |
| 264 var fileReader = new FileReader(); | |
| 265 | |
| 266 fileReader.onload = function(event) { | |
| 267 this.naclModule_.postMessage(unpacker.request.createReadChunkDoneResponse( | |
| 268 this.fileSystemId_, requestId, event.target.result, offset)); | |
| 269 }.bind(this); | |
| 270 | |
| 271 fileReader.onerror = function(event) { | |
| 272 console.error('Failed to read a chunk of data from the archive.'); | |
| 273 this.naclModule_.postMessage(unpacker.request.createReadChunkErrorResponse( | |
| 274 this.fileSystemId_, requestId)); | |
| 275 // Reading from the source file failed. Assume that the file is gone and | |
| 276 // unmount the archive. | |
| 277 // TODO(523195): Show a notification that the source file is gone. | |
| 278 unpacker.app.unmountVolume(this.fileSystemId_, true); | |
| 279 }.bind(this); | |
| 280 | |
| 281 fileReader.readAsArrayBuffer(blob); | |
| 282 }; | |
| 283 | |
| 284 /** | |
| 285 * Reads a passphrase from user input for READ_PASSPHRASE operation. | |
| 286 * @param {!Object} data The data received from the NaCl module. | |
| 287 * @param {number} requestId The request id, which should be unique per every | |
| 288 * volume. | |
| 289 * @private | |
| 290 */ | |
| 291 unpacker.Decompressor.prototype.readPassphrase_ = function(data, requestId) { | |
| 292 this.passphraseManager.getPassphrase() | |
| 293 .then(function(passphrase) { | |
| 294 this.naclModule_.postMessage( | |
| 295 unpacker.request.createReadPassphraseDoneResponse( | |
| 296 this.fileSystemId_, requestId, passphrase)); | |
| 297 }.bind(this)) | |
| 298 .catch(function(error) { | |
| 299 console.error(error.stack || error); | |
| 300 this.naclModule_.postMessage( | |
| 301 unpacker.request.createReadPassphraseErrorResponse( | |
| 302 this.fileSystemId_, requestId)); | |
| 303 // TODO(mtomasz): Instead of unmounting just let the current operation | |
| 304 // fail and ask for password for another files. This is however | |
| 305 // impossible for now due to a bug in libarchive. | |
| 306 unpacker.app.unmountVolume(this.fileSystemId_, true); | |
| 307 }.bind(this)); | |
| 308 }; | |
| OLD | NEW |