1// Copyright 2013 The Chromium 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// Custom binding for the fileSystemProvider API. 6 7var binding = require('binding').Binding.create('fileSystemProvider'); 8var fileSystemProviderInternal = 9 require('binding').Binding.create('fileSystemProviderInternal').generate(); 10var eventBindings = require('event_bindings'); 11var fileSystemNatives = requireNative('file_system_natives'); 12var GetDOMError = fileSystemNatives.GetDOMError; 13 14/** 15 * Maximum size of the thumbnail in bytes. 16 * @type {number} 17 * @const 18 */ 19var METADATA_THUMBNAIL_SIZE_LIMIT = 32 * 1024 * 1024; 20 21/** 22 * Regular expression to validate if the thumbnail URI is a valid data URI, 23 * taking into account allowed formats. 24 * @type {RegExp} 25 * @const 26 */ 27var METADATA_THUMBNAIL_FORMAT = new RegExp( 28 '^data:image/(png|jpeg|webp);', 'i'); 29 30/** 31 * Annotates a date with its serialized value. 32 * @param {Date} date Input date. 33 * @return {Date} Date with an extra <code>value</code> attribute. 34 */ 35function annotateDate(date) { 36 // Copy in case the input date is frozen. 37 var result = new Date(date.getTime()); 38 result.value = result.toString(); 39 return result; 40} 41 42/** 43 * Verifies if the passed image URI is valid. 44 * @param {*} uri Image URI. 45 * @return {boolean} True if valid, valse otherwise. 46 */ 47function verifyImageURI(uri) { 48 // The URI is specified by a user, so the type may be incorrect. 49 if (typeof uri != 'string' && !(uri instanceof String)) 50 return false; 51 52 return METADATA_THUMBNAIL_FORMAT.test(uri); 53} 54 55/** 56 * Annotates an entry metadata by serializing its modifiedTime value. 57 * @param {EntryMetadata} metadata Input metadata. 58 * @return {EntryMetadata} metadata Annotated metadata, which can be passed 59 * back to the C++ layer. 60 */ 61function annotateMetadata(metadata) { 62 var result = { 63 isDirectory: metadata.isDirectory, 64 name: metadata.name, 65 size: metadata.size, 66 modificationTime: annotateDate(metadata.modificationTime) 67 }; 68 if ('mimeType' in metadata) 69 result.mimeType = metadata.mimeType; 70 if ('thumbnail' in metadata) 71 result.thumbnail = metadata.thumbnail; 72 return result; 73} 74 75/** 76 * Massages arguments of an event raised by the File System Provider API. 77 * @param {Array.<*>} args Input arguments. 78 * @param {function(Array.<*>)} dispatch Closure to be called with massaged 79 * arguments. 80 */ 81function massageArgumentsDefault(args, dispatch) { 82 var executionStart = Date.now(); 83 var options = args[0]; 84 var onSuccessCallback = function(hasNext) { 85 fileSystemProviderInternal.operationRequestedSuccess( 86 options.fileSystemId, options.requestId, Date.now() - executionStart); 87 }; 88 var onErrorCallback = function(error) { 89 fileSystemProviderInternal.operationRequestedError( 90 options.fileSystemId, options.requestId, error, 91 Date.now() - executionStart); 92 } 93 dispatch([options, onSuccessCallback, onErrorCallback]); 94} 95 96 97binding.registerCustomHook(function(bindingsAPI) { 98 var apiFunctions = bindingsAPI.apiFunctions; 99 100 apiFunctions.setUpdateArgumentsPostValidate( 101 'mount', 102 function(options, successCallback, errorCallback) { 103 // Piggyback the error callback onto the success callback, 104 // so we can use the error callback later. 105 successCallback.errorCallback_ = errorCallback; 106 return [options, successCallback]; 107 }); 108 109 apiFunctions.setCustomCallback( 110 'mount', 111 function(name, request, response) { 112 var domError = null; 113 if (request.callback && response) { 114 // DOMError is present only if mount() failed. 115 if (response[0]) { 116 // Convert a Dictionary to a DOMError. 117 domError = GetDOMError(response[0].name, response[0].message); 118 response.length = 1; 119 } 120 121 var successCallback = request.callback; 122 var errorCallback = request.callback.errorCallback_; 123 delete request.callback; 124 125 if (domError) 126 errorCallback(domError); 127 else 128 successCallback(); 129 } 130 }); 131 132 apiFunctions.setUpdateArgumentsPostValidate( 133 'unmount', 134 function(options, successCallback, errorCallback) { 135 // Piggyback the error callback onto the success callback, 136 // so we can use the error callback later. 137 successCallback.errorCallback_ = errorCallback; 138 return [options, successCallback]; 139 }); 140 141 apiFunctions.setCustomCallback( 142 'unmount', 143 function(name, request, response) { 144 var domError = null; 145 if (request.callback) { 146 // DOMError is present only if mount() failed. 147 if (response && response[0]) { 148 // Convert a Dictionary to a DOMError. 149 domError = GetDOMError(response[0].name, response[0].message); 150 response.length = 1; 151 } 152 153 var successCallback = request.callback; 154 var errorCallback = request.callback.errorCallback_; 155 delete request.callback; 156 157 if (domError) 158 errorCallback(domError); 159 else 160 successCallback(); 161 } 162 }); 163}); 164 165eventBindings.registerArgumentMassager( 166 'fileSystemProvider.onUnmountRequested', 167 massageArgumentsDefault); 168 169eventBindings.registerArgumentMassager( 170 'fileSystemProvider.onGetMetadataRequested', 171 function(args, dispatch) { 172 var executionStart = Date.now(); 173 var options = args[0]; 174 var onSuccessCallback = function(metadata) { 175 var error; 176 // It is invalid to return a thumbnail when it's not requested. The 177 // restriction is added in order to avoid fetching the thumbnail while 178 // it's not needed. 179 if (!options.thumbnail && metadata.thumbnail) 180 error = 'Thumbnail data provided, but not requested.'; 181 182 // Check the format and size. Note, that in the C++ layer, there is 183 // another sanity check to avoid passing any evil URL. 184 if ('thumbnail' in metadata && !verifyImageURI(metadata.thumbnail)) 185 error = 'Thumbnail format invalid.'; 186 187 if ('thumbnail' in metadata && 188 metadata.thumbnail.length > METADATA_THUMBNAIL_SIZE_LIMIT) { 189 error = 'Thumbnail data too large.'; 190 } 191 192 if (error) { 193 console.error(error); 194 fileSystemProviderInternal.operationRequestedError( 195 options.fileSystemId, options.requestId, 'FAILED', 196 Date.now() - executionStart); 197 return; 198 } 199 200 fileSystemProviderInternal.getMetadataRequestedSuccess( 201 options.fileSystemId, 202 options.requestId, 203 annotateMetadata(metadata), 204 Date.now() - executionStart); 205 }; 206 207 var onErrorCallback = function(error) { 208 fileSystemProviderInternal.operationRequestedError( 209 options.fileSystemId, options.requestId, error, 210 Date.now() - executionStart); 211 } 212 213 dispatch([options, onSuccessCallback, onErrorCallback]); 214 }); 215 216eventBindings.registerArgumentMassager( 217 'fileSystemProvider.onReadDirectoryRequested', 218 function(args, dispatch) { 219 var executionStart = Date.now(); 220 var options = args[0]; 221 var onSuccessCallback = function(entries, hasNext) { 222 var annotatedEntries = entries.map(annotateMetadata); 223 // It is invalid to return a thumbnail when it's not requested. 224 var error; 225 annotatedEntries.forEach(function(metadata) { 226 if (metadata.thumbnail) { 227 var error = 228 'Thumbnails must not be provided when reading a directory.'; 229 return; 230 } 231 }); 232 233 if (error) { 234 console.error(error); 235 fileSystemProviderInternal.operationRequestedError( 236 options.fileSystemId, options.requestId, 'FAILED', 237 Date.now() - executionStart); 238 return; 239 } 240 241 fileSystemProviderInternal.readDirectoryRequestedSuccess( 242 options.fileSystemId, options.requestId, annotatedEntries, hasNext, 243 Date.now() - executionStart); 244 }; 245 246 var onErrorCallback = function(error) { 247 fileSystemProviderInternal.operationRequestedError( 248 options.fileSystemId, options.requestId, error, 249 Date.now() - executionStart); 250 } 251 dispatch([options, onSuccessCallback, onErrorCallback]); 252 }); 253 254eventBindings.registerArgumentMassager( 255 'fileSystemProvider.onOpenFileRequested', 256 massageArgumentsDefault); 257 258eventBindings.registerArgumentMassager( 259 'fileSystemProvider.onCloseFileRequested', 260 massageArgumentsDefault); 261 262eventBindings.registerArgumentMassager( 263 'fileSystemProvider.onReadFileRequested', 264 function(args, dispatch) { 265 var executionStart = Date.now(); 266 var options = args[0]; 267 var onSuccessCallback = function(data, hasNext) { 268 fileSystemProviderInternal.readFileRequestedSuccess( 269 options.fileSystemId, options.requestId, data, hasNext, 270 Date.now() - executionStart); 271 }; 272 var onErrorCallback = function(error) { 273 fileSystemProviderInternal.operationRequestedError( 274 options.fileSystemId, options.requestId, error, 275 Date.now() - executionStart); 276 } 277 dispatch([options, onSuccessCallback, onErrorCallback]); 278 }); 279 280eventBindings.registerArgumentMassager( 281 'fileSystemProvider.onCreateDirectoryRequested', 282 massageArgumentsDefault); 283 284eventBindings.registerArgumentMassager( 285 'fileSystemProvider.onDeleteEntryRequested', 286 massageArgumentsDefault); 287 288eventBindings.registerArgumentMassager( 289 'fileSystemProvider.onCreateFileRequested', 290 massageArgumentsDefault); 291 292eventBindings.registerArgumentMassager( 293 'fileSystemProvider.onCopyEntryRequested', 294 massageArgumentsDefault); 295 296eventBindings.registerArgumentMassager( 297 'fileSystemProvider.onMoveEntryRequested', 298 massageArgumentsDefault); 299 300eventBindings.registerArgumentMassager( 301 'fileSystemProvider.onTruncateRequested', 302 massageArgumentsDefault); 303 304eventBindings.registerArgumentMassager( 305 'fileSystemProvider.onWriteFileRequested', 306 massageArgumentsDefault); 307 308eventBindings.registerArgumentMassager( 309 'fileSystemProvider.onAbortRequested', 310 massageArgumentsDefault); 311 312exports.binding = binding.generate(); 313