blob: 91a271876da952bcdd39f7c0fc4dbf99db29c44d [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Custom binding for the fileSystemProvider API.
var binding = require('binding').Binding.create('fileSystemProvider');
var fileSystemProviderInternal =
require('binding').Binding.create('fileSystemProviderInternal').generate();
var eventBindings = require('event_bindings');
/**
* Maximum size of the thumbnail in bytes.
* @type {number}
* @const
*/
var METADATA_THUMBNAIL_SIZE_LIMIT = 32 * 1024 * 1024;
/**
* Regular expression to validate if the thumbnail URI is a valid data URI,
* taking into account allowed formats.
* @type {RegExp}
* @const
*/
var METADATA_THUMBNAIL_FORMAT = new RegExp(
'^data:image/(png|jpeg|webp);', 'i');
/**
* Annotates a date with its serialized value.
* @param {Date} date Input date.
* @return {Date} Date with an extra <code>value</code> attribute.
*/
function annotateDate(date) {
// Copy in case the input date is frozen.
var result = new Date(date.getTime());
result.value = result.toString();
return result;
}
/**
* Verifies if the passed image URI is valid.
* @param {*} uri Image URI.
* @return {boolean} True if valid, valse otherwise.
*/
function verifyImageURI(uri) {
// The URI is specified by a user, so the type may be incorrect.
if (typeof uri != 'string' && !(uri instanceof String))
return false;
return METADATA_THUMBNAIL_FORMAT.test(uri);
}
/**
* Verifies if the passed metadata is valid.
* @param {!GetMetadataOptions|!ReadDirectoryOptions} options
* @return {boolean} True if valid, false if invalid.
*/
function verifyMetadata(options, metadata) {
// Ideally we'd like to consider the following as errors, but for backward
// compatibility they are warnings only.
if (!options.isDirectory && metadata.isDirectory !== undefined)
console.warn('IsDirectory specified, but not requested.');
if (!options.name && metadata.name !== undefined)
console.warn('Name specified, but not requested.');
if (!options.size && metadata.size !== undefined)
console.warn('Size specified, but not requested.');
if (!options.modificationTime && metadata.modificationTime !== undefined)
console.warn('Last modification time specified, but not requested.');
if (!options.mimeType && metadata.mimeType !== undefined) {
console.warn('MIME type specified, but not requested.');
} else {
if (metadata.mimeType === '') {
warning = 'MIME type must not be an empty string.' +
'If unknown, then do not set it.';
}
}
if (options.isDirectory && metadata.isDirectory === undefined) {
console.error('IsDirectory is required for this request.');
return false;
}
if (options.name && metadata.name === undefined) {
console.error('Name is required for this request.');
return false;
}
if (options.size && metadata.size === undefined) {
console.error('Size is required for this request.');
return false;
}
if (options.modificationTime && metadata.modificationTime === undefined) {
console.error('Last modification time is required for this request.');
return false;
}
// It is invalid to return a thumbnail when it's not requested. The
// restriction is added in order to avoid fetching the thumbnail while
// it's not needed.
if (!options.thumbnail && metadata.thumbnail !== undefined) {
console.error('Thumbnail data provided, but not requested.');
return false;
}
// Check the format and size. Note, that in the C++ layer, there is
// another sanity check to avoid passing any evil URL.
if (metadata.thumbnail !== undefined && !verifyImageURI(metadata.thumbnail)) {
console.error('Thumbnail format invalid.');
return false;
}
if (metadata.thumbnail !== undefined &&
metadata.thumbnail.length > METADATA_THUMBNAIL_SIZE_LIMIT) {
console.error('Thumbnail data too large.');
return false;
}
return true;
}
/**
* Annotates an entry metadata by serializing its modifiedTime value.
* @param {EntryMetadata} metadata Input metadata.
* @return {EntryMetadata} metadata Annotated metadata, which can be passed
* back to the C++ layer.
*/
function annotateMetadata(metadata) {
var result = {};
if (metadata.isDirectory !== undefined)
result.isDirectory = metadata.isDirectory;
if (metadata.name !== undefined)
result.name = metadata.name;
if (metadata.size !== undefined)
result.size = metadata.size;
if (metadata.modificationTime !== undefined)
result.modificationTime = annotateDate(metadata.modificationTime);
if (metadata.mimeType !== undefined)
result.mimeType = metadata.mimeType;
if (metadata.thumbnail !== undefined)
result.thumbnail = metadata.thumbnail;
return result;
}
/**
* Massages arguments of an event raised by the File System Provider API.
* @param {Array<*>} args Input arguments.
* @param {function(Array<*>)} dispatch Closure to be called with massaged
* arguments.
*/
function massageArgumentsDefault(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(hasNext) {
fileSystemProviderInternal.operationRequestedSuccess(
options.fileSystemId, options.requestId, Date.now() - executionStart);
};
var onErrorCallback = function(error) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, error,
Date.now() - executionStart);
}
dispatch([options, onSuccessCallback, onErrorCallback]);
}
eventBindings.registerArgumentMassager(
'fileSystemProvider.onUnmountRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onGetMetadataRequested',
function(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(metadata) {
if (!verifyMetadata(options, metadata)) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, 'FAILED',
Date.now() - executionStart);
return;
}
fileSystemProviderInternal.getMetadataRequestedSuccess(
options.fileSystemId,
options.requestId,
annotateMetadata(metadata),
Date.now() - executionStart);
};
var onErrorCallback = function(error) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, error,
Date.now() - executionStart);
}
dispatch([options, onSuccessCallback, onErrorCallback]);
});
eventBindings.registerArgumentMassager(
'fileSystemProvider.onGetActionsRequested',
function(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(actions) {
fileSystemProviderInternal.getActionsRequestedSuccess(
options.fileSystemId,
options.requestId,
actions,
Date.now() - executionStart);
};
var onErrorCallback = function(error) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, error,
Date.now() - executionStart);
}
dispatch([options, onSuccessCallback, onErrorCallback]);
});
eventBindings.registerArgumentMassager(
'fileSystemProvider.onReadDirectoryRequested',
function(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(entries, hasNext) {
var error = false;
for (var i = 0; i < entries.length; i++) {
if (!verifyMetadata(options, entries[i])) {
error = true;
break;
}
}
if (error) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, 'FAILED',
Date.now() - executionStart);
return;
}
var annotatedEntries = entries.map(annotateMetadata);
fileSystemProviderInternal.readDirectoryRequestedSuccess(
options.fileSystemId, options.requestId, annotatedEntries, hasNext,
Date.now() - executionStart);
};
var onErrorCallback = function(error) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, error,
Date.now() - executionStart);
}
dispatch([options, onSuccessCallback, onErrorCallback]);
});
eventBindings.registerArgumentMassager(
'fileSystemProvider.onOpenFileRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onCloseFileRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onReadFileRequested',
function(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(data, hasNext) {
fileSystemProviderInternal.readFileRequestedSuccess(
options.fileSystemId, options.requestId, data, hasNext,
Date.now() - executionStart);
};
var onErrorCallback = function(error) {
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, error,
Date.now() - executionStart);
}
dispatch([options, onSuccessCallback, onErrorCallback]);
});
eventBindings.registerArgumentMassager(
'fileSystemProvider.onCreateDirectoryRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onDeleteEntryRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onCreateFileRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onCopyEntryRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onMoveEntryRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onTruncateRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onWriteFileRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onAbortRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onObserveDirectoryRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onUnobserveEntryRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onAddWatcherRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onRemoveWatcherRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onConfigureRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onExecuteActionRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onMountRequested',
function(args, dispatch) {
var onSuccessCallback = function() {
// TODO(mtomasz): To be implemented.
};
var onErrorCallback = function(error) {
// TODO(mtomasz): To be implemented.
}
dispatch([onSuccessCallback, onErrorCallback]);
});
exports.$set('binding', binding.generate());