file_system_provider_custom_bindings.js revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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