blob: 28cbb95d9a49fea48c44aa6fbcd089c601e37347 [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');
var fileSystemNatives = requireNative('file_system_natives');
var GetDOMError = fileSystemNatives.GetDOMError;
/**
* 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);
}
/**
* 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 = {
isDirectory: metadata.isDirectory,
name: metadata.name,
size: metadata.size,
modificationTime: annotateDate(metadata.modificationTime)
};
if ('mimeType' in metadata)
result.mimeType = metadata.mimeType;
if ('thumbnail' in metadata)
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]);
}
binding.registerCustomHook(function(bindingsAPI) {
var apiFunctions = bindingsAPI.apiFunctions;
apiFunctions.setUpdateArgumentsPostValidate(
'mount',
function(options, successCallback, errorCallback) {
// Piggyback the error callback onto the success callback,
// so we can use the error callback later.
successCallback.errorCallback_ = errorCallback;
return [options, successCallback];
});
apiFunctions.setCustomCallback(
'mount',
function(name, request, response) {
var domError = null;
if (request.callback && response) {
// DOMError is present only if mount() failed.
if (response[0]) {
// Convert a Dictionary to a DOMError.
domError = GetDOMError(response[0].name, response[0].message);
response.length = 1;
}
var successCallback = request.callback;
var errorCallback = request.callback.errorCallback_;
delete request.callback;
if (domError)
errorCallback(domError);
else
successCallback();
}
});
apiFunctions.setUpdateArgumentsPostValidate(
'unmount',
function(options, successCallback, errorCallback) {
// Piggyback the error callback onto the success callback,
// so we can use the error callback later.
successCallback.errorCallback_ = errorCallback;
return [options, successCallback];
});
apiFunctions.setCustomCallback(
'unmount',
function(name, request, response) {
var domError = null;
if (request.callback) {
// DOMError is present only if mount() failed.
if (response && response[0]) {
// Convert a Dictionary to a DOMError.
domError = GetDOMError(response[0].name, response[0].message);
response.length = 1;
}
var successCallback = request.callback;
var errorCallback = request.callback.errorCallback_;
delete request.callback;
if (domError)
errorCallback(domError);
else
successCallback();
}
});
});
eventBindings.registerArgumentMassager(
'fileSystemProvider.onUnmountRequested',
massageArgumentsDefault);
eventBindings.registerArgumentMassager(
'fileSystemProvider.onGetMetadataRequested',
function(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(metadata) {
var error;
// 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)
error = 'Thumbnail data provided, but not requested.';
// Check the format and size. Note, that in the C++ layer, there is
// another sanity check to avoid passing any evil URL.
if ('thumbnail' in metadata && !verifyImageURI(metadata.thumbnail))
error = 'Thumbnail format invalid.';
if ('thumbnail' in metadata &&
metadata.thumbnail.length > METADATA_THUMBNAIL_SIZE_LIMIT) {
error = 'Thumbnail data too large.';
}
if (error) {
console.error(error);
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.onReadDirectoryRequested',
function(args, dispatch) {
var executionStart = Date.now();
var options = args[0];
var onSuccessCallback = function(entries, hasNext) {
var annotatedEntries = entries.map(annotateMetadata);
// It is invalid to return a thumbnail when it's not requested.
var error;
annotatedEntries.forEach(function(metadata) {
if (metadata.thumbnail) {
var error =
'Thumbnails must not be provided when reading a directory.';
return;
}
});
if (error) {
console.error(error);
fileSystemProviderInternal.operationRequestedError(
options.fileSystemId, options.requestId, 'FAILED',
Date.now() - executionStart);
return;
}
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);
exports.binding = binding.generate();