From c488237c150805e61f0882e8761be936847c34ec Mon Sep 17 00:00:00 2001 From: Pawel Andruszkiewicz Date: Fri, 13 Nov 2015 16:16:10 +0100 Subject: [PATCH] [File] Reworked internal represenation of URLs, added support for cdvfile:// fullPath - full path relative to the FileSystem root. [Verification] Code compiles, pass rate: 139/140, all manual tests pass. Change-Id: I28f82922def166af4ded5e38f494ee05e6230787 Signed-off-by: Pawel Andruszkiewicz --- src/file/cordova_file_api.js | 1 + src/file/js/DirectoryEntry.js | 16 ++++- src/file/js/DirectoryReader.js | 8 ++- src/file/js/Entry.js | 56 +++++++++++------ src/file/js/File.js | 1 + src/file/js/FileReader.js | 7 +++ src/file/js/FileSystem.js | 31 ++++++++++ src/file/js/FileWriter.js | 21 ++++++- src/file/js/resolveLocalFileSystemURI.js | 7 +-- src/file/js/rootsUtils.js | 102 +++++++++++++++++++++++++------ 10 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 src/file/js/FileSystem.js diff --git a/src/file/cordova_file_api.js b/src/file/cordova_file_api.js index d28e754..b5a171d 100755 --- a/src/file/cordova_file_api.js +++ b/src/file/cordova_file_api.js @@ -26,4 +26,5 @@ //= require('requestFileSystem.js'); //= require('resolveLocalFileSystemURI.js'); +//= require('FileSystem.js'); //= require('File.js'); diff --git a/src/file/js/DirectoryEntry.js b/src/file/js/DirectoryEntry.js index 820c725..3f11fd4 100644 --- a/src/file/js/DirectoryEntry.js +++ b/src/file/js/DirectoryEntry.js @@ -46,13 +46,18 @@ cordova.define('cordova-plugin-file.tizen.DirectoryEntry', function(require, exp } var getFunction = function(successCallback, errorCallback, args, isDirectory) { - var uri = rootsUtils.stripTrailingSlash(args[0]), + var uri = rootsUtils.internalUrlToNativePath(args[0]), path = rootsUtils.stripTrailingSlash(args[1]), options = args[2] || {}, create_flag = !!options.create, exclusive_flag = !!options.exclusive, absolute_path = ''; + if (!uri) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } + if ('/' === path[0]) { // path seems absolute, checking if path is a child of this object URI if (0 === path.indexOf(uri)) { @@ -69,7 +74,7 @@ cordova.define('cordova-plugin-file.tizen.DirectoryEntry', function(require, exp absolute_path = uri + '/' + path; } - var root = rootsUtils.findFilesystem(absolute_path).fullPath; + var root = rootsUtils.findFilesystem(absolute_path).nativeURL; var clean_path = sanitizePath(absolute_path); if (0 !== clean_path.indexOf(root)) { @@ -147,7 +152,12 @@ module.exports = { getFunction(successCallback, errorCallback, args, true); }, removeRecursively: function(successCallback, errorCallback, args) { - var uri = args[0]; + var uri = rootsUtils.internalUrlToNativePath(args[0]); + + if (!uri) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } if (rootsUtils.isRootUri(uri)) { console.error('It is not allowed to remove root directory.'); diff --git a/src/file/js/DirectoryReader.js b/src/file/js/DirectoryReader.js index 3d1d926..6feed91 100644 --- a/src/file/js/DirectoryReader.js +++ b/src/file/js/DirectoryReader.js @@ -20,7 +20,13 @@ cordova.define('cordova-plugin-file.tizen.DirectoryReader', function(require, ex module.exports = { readEntries: function(successCallback, errorCallback, args) { - var uri = args[0]; + var uri = rootsUtils.internalUrlToNativePath(args[0]); + + if (!uri) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } + var fail = function(e) { errorCallback && errorCallback(ConvertTizenFileError(e)); } diff --git a/src/file/js/Entry.js b/src/file/js/Entry.js index 1b56c28..1b258e8 100644 --- a/src/file/js/Entry.js +++ b/src/file/js/Entry.js @@ -42,10 +42,10 @@ var resolveParent = function(srcURL, errorCallback, rest){ }; var changeFile = function(method, successCallback, errorCallback, args) { - var srcURL = args[0]; + var srcURL = rootsUtils.internalUrlToNativePath(args[0]); var name = args[2]; - var destDir = args[1]; - var destURL = rootsUtils.stripTrailingSlash(destDir) + '/' + name; + var destDir = rootsUtils.internalUrlToNativePath(args[1]); + var destURL = destDir + '/' + name; function fail(e, msg) { console.error(msg); @@ -54,12 +54,17 @@ var changeFile = function(method, successCallback, errorCallback, args) { } } + if (!srcURL || !destDir) { + fail(FileError.ENCODING_ERR, 'Error - Failed to decode internal URL.'); + return; + } + if (!rootsUtils.isValidFileName(name)) { fail(FileError.ENCODING_ERR, 'Error - Disallowed character detected in the file name: ' + name); return; } - if (-1 !== (rootsUtils.getFullPath(destURL) + '/').indexOf(rootsUtils.getFullPath(srcURL) + '/')) { + if (-1 !== (destURL + '/').indexOf(srcURL + '/')) { fail(FileError.INVALID_MODIFICATION_ERR, 'Error - Cannot copy/move onto itself.'); return; } @@ -126,18 +131,23 @@ var changeFile = function(method, successCallback, errorCallback, args) { module.exports = { getFileMetadata: function(successCallback, errorCallback, args) { - try { - tizen.filesystem.resolve(args[0], function (file) { - var result = { 'size': file.fileSize, 'lastModifiedDate': file.modified }; - successCallback && successCallback(result); - }, function (err) { - errorCallback && errorCallback(ConvertTizenFileError(err)); - }, 'r'); - } catch (exception) { - console.error('Error - resolve failed'); - errorCallback && errorCallback(ConvertTizenFileError(exception)); - } - }, + var uri = rootsUtils.internalUrlToNativePath(args[0]); + if (!uri) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } + try { + tizen.filesystem.resolve(uri, function (file) { + var result = { 'size': file.fileSize, 'lastModifiedDate': file.modified }; + successCallback && successCallback(result); + }, function (err) { + errorCallback && errorCallback(ConvertTizenFileError(err)); + }, 'r'); + } catch (exception) { + console.error('Error - resolve failed'); + errorCallback && errorCallback(ConvertTizenFileError(exception)); + } + }, setMetadata: function(successCallback, errorCallback, args) { console.error('setMetadata - Not supported'); errorCallback && errorCallback(FileError.ENCODING_ERR); @@ -149,7 +159,12 @@ module.exports = { changeFile('copyTo', successCallback, errorCallback, args); }, remove: function(successCallback, errorCallback, args) { - var url = args[0]; + var url = rootsUtils.internalUrlToNativePath(args[0]); + + if (!url) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } if (rootsUtils.isRootUri(url)) { console.error('It is not allowed to remove root directory.'); @@ -181,7 +196,12 @@ module.exports = { ); }, getParent: function(successCallback, errorCallback, args) { - var url = args[0]; + var url = rootsUtils.internalUrlToNativePath(args[0]); + + if (!url) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } if (rootsUtils.isRootUri(url)) { successCallback && successCallback(rootsUtils.findFilesystem(url)); diff --git a/src/file/js/File.js b/src/file/js/File.js index 51b67d0..fd6bcd1 100644 --- a/src/file/js/File.js +++ b/src/file/js/File.js @@ -51,5 +51,6 @@ console.log('Loaded cordova.file API'); exports = function(require) { require('cordova-tizen').addPlugin('cordova-plugin-file.File', plugin_name, 'runs'); + require('cordova-tizen').addPlugin('cordova-plugin-file.FileSystem', 'cordova-plugin-file.tizen.FileSystem', 'merges', 'window.FileSystem'); }; // TODO: remove -> end diff --git a/src/file/js/FileReader.js b/src/file/js/FileReader.js index 1651082..d724459 100644 --- a/src/file/js/FileReader.js +++ b/src/file/js/FileReader.js @@ -19,6 +19,13 @@ cordova.define('cordova-plugin-file.tizen.FileReader', function(require, exports // TODO: remove -> end function read(operation, url, start, end, successCallback, errorCallback, encoding) { + url = rootsUtils.internalUrlToNativePath(url); + + if (!url) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } + var fail = function(e) { errorCallback && errorCallback(ConvertTizenFileError(e)); } diff --git a/src/file/js/FileSystem.js b/src/file/js/FileSystem.js new file mode 100644 index 0000000..8eacf99 --- /dev/null +++ b/src/file/js/FileSystem.js @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: remove when added to public cordova repository -> begin +cordova.define('cordova-plugin-file.tizen.FileSystem', function(require, exports, module) { +// TODO: remove -> end + +module.exports = { + __format__: function(fullPath) { + return 'cdvfile://localhost/' + this.name + fullPath; + } +}; + +console.log('Loaded cordova.file FileSystem'); + +// TODO: remove when added to public cordova repository -> begin +}); +// TODO: remove -> end diff --git a/src/file/js/FileWriter.js b/src/file/js/FileWriter.js index b606a3b..9150723 100644 --- a/src/file/js/FileWriter.js +++ b/src/file/js/FileWriter.js @@ -55,11 +55,16 @@ function toUTF8Array(str) { module.exports = { write: function(successCallback, errorCallback, args) { - var uri = args[0]; + var uri = rootsUtils.internalUrlToNativePath(args[0]); var data = args[1]; var position = args[2]; var isBinary = args[3]; + if (!uri) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } + if (!isBinary) { if ('string' === typeof data) { // convert to UTF-8, as this is the default encoding for read operations @@ -103,7 +108,7 @@ module.exports = { // for this, we need to truncate after write... module.exports.truncate(function() { successCallback && successCallback(length); - }, errorCallback, [uri, stream.position]); + }, errorCallback, [args[0], stream.position]); } catch (error) { errorCallback && errorCallback(ConvertTizenFileError(error)); } @@ -132,9 +137,19 @@ module.exports = { }, truncate: function(successCallback, errorCallback, args) { - var uri = rootsUtils.getFullPath(args[0]); + var uri = rootsUtils.internalUrlToNativePath(args[0]); var length = args[1]; + if (!uri) { + errorCallback && errorCallback(FileError.ENCODING_ERR); + return; + } + + var uriPrefix = 'file://'; + if (0 === uri.indexOf(uriPrefix)) { + uri = uri.substring(uriPrefix.length); + } + var callArgs = { 'uri': uri, 'length': length diff --git a/src/file/js/resolveLocalFileSystemURI.js b/src/file/js/resolveLocalFileSystemURI.js index 19491fe..54b2fc8 100644 --- a/src/file/js/resolveLocalFileSystemURI.js +++ b/src/file/js/resolveLocalFileSystemURI.js @@ -20,12 +20,9 @@ cordova.define('cordova-plugin-file.tizen.resolveLocalFileSystemURI', function(r module.exports = { resolveLocalFileSystemURI: function(successCallback, errorCallback, args) { - var path = args[0]; - // fix for file.spec.10 - path = path.split('?')[0]; + var path = rootsUtils.internalUrlToNativePath(args[0]); - // fix for file.spec.12 - if (0 !== path.indexOf('file://')) { // 'file://' scheme is required + if (!path) { errorCallback && errorCallback(FileError.ENCODING_ERR); return; } diff --git a/src/file/js/rootsUtils.js b/src/file/js/rootsUtils.js index 7760557..1a3fa88 100644 --- a/src/file/js/rootsUtils.js +++ b/src/file/js/rootsUtils.js @@ -15,24 +15,26 @@ */ var rootsUtils = (function() { - var uriPrefix = 'file://'; + var filePrefix = 'file:///'; function stripTrailingSlash(str) { - if ('/' === str.substr(-1)) { + if (filePrefix !== str && '/' === str.substr(-1)) { return str.substr(0, str.length - 1); } return str; } function getName(uri) { - return stripTrailingSlash(uri).replace(/^.*(\\|\/|\:)/, ''); + return getFullPath(uri).replace(/^.*(\\|\/|\:)/, ''); } function getFullPath(uri) { - if (0 === uri.indexOf(uriPrefix)) { - uri = uri.substr(uriPrefix.length); + var tmp = findFilesystem(uri); + tmp = getNativeUrl(uri).substring(tmp.nativeURL.length); + if (!tmp) { + tmp = '/'; } - return stripTrailingSlash(uri); + return tmp; } function getNativeUrl(uri) { @@ -53,32 +55,32 @@ var rootsUtils = (function() { { filesystemName: 'temporary', name: '', - fullPath: '', + fullPath: '/', nativeURL: 'wgt-private-tmp' }, { filesystemName: 'persistent', name: '', - fullPath: '', + fullPath: '/', nativeURL: 'wgt-private' } ]; - var rootDirUri = 'file:///'; - var roots = [ { filesystemName: 'root', - name: getName(rootDirUri), - fullPath: getFullPath(rootDirUri), - nativeURL: getNativeUrl(rootDirUri) + name: '', + fullPath: '/', + nativeURL: 'file:///' } ]; + var name_to_root; + function getRoots(successCallback) { if (roots_to_resolve.length > 0) { tizen.filesystem.resolve(roots_to_resolve[0].nativeURL, function(dir) { - roots_to_resolve[0] = createEntry(dir, roots_to_resolve[0].filesystemName); + roots_to_resolve[0].nativeURL = getNativeUrl(dir.toURI()); roots.push(roots_to_resolve[0]); roots_to_resolve.splice(0, 1); // remove first item @@ -90,6 +92,12 @@ var rootsUtils = (function() { successCallback(roots); }); } else { + if (!name_to_root) { + name_to_root = {}; + for (var i = 0; i < roots.length; ++i) { + name_to_root[roots[i].filesystemName] = roots[i]; + } + } successCallback(roots.slice()); } } @@ -101,9 +109,9 @@ var rootsUtils = (function() { } function findFilesystem(uri) { - var fullPath = getFullPath(uri); + var nativeUrl = getNativeUrl(uri); for (var i = roots.length - 1; i > 0; --i) { - if (0 === strncmp(fullPath, roots[i].fullPath, roots[i].fullPath.length)) { + if (0 === strncmp(nativeUrl, roots[i].nativeURL, roots[i].nativeURL.length)) { return roots[i]; } } @@ -113,7 +121,7 @@ var rootsUtils = (function() { function isRootUri(uri) { var fs = findFilesystem(uri); - return (fs.fullPath === getFullPath(uri)); + return (fs.nativeURL === getNativeUrl(uri)); } // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#naming-restrictions @@ -129,6 +137,63 @@ var rootsUtils = (function() { return true; } + var localhost = '//localhost/' + var cdvPrefix = 'cdvfile:///'; + + function internalUrlToNativePath(url) { + var input = url; + + // skip parameters + url = url.split('?')[0]; + + // remove localhost + url = url.replace(localhost, '///'); + + if (0 === url.indexOf(cdvPrefix)) { + // cdvfile protocol + url = url.substring(cdvPrefix.length); + + var idx = url.indexOf('/'); + + if (-1 !== idx) { + var fsName = url.substring(0, idx); + var fullPath = url.substring(idx); + url = name_to_root[fsName] ? name_to_root[fsName].nativeURL + fullPath : undefined; + } else { + // malformed URL + url = undefined; + } + } else if (0 === url.indexOf(filePrefix)) { + // check if the filesystem for this URL exists + var found = false; + for (var i = 0; i < roots.length && !found; ++i) { + if (0 === url.indexOf(roots[i].nativeURL)) { + found = true; + } + } + + if (!found) { + url = undefined; + } + } else { + // backwards compatibility, device absolute path + // only TEMPORARY and PERSISTENT paths are allowed + url = filePrefix + url.substring(1); // skip '/' + if (0 !== url.indexOf(name_to_root.temporary.nativeURL) && + 0 !== url.indexOf(name_to_root.persistent.nativeURL)) { + url = undefined; + } + } + + if (url) { + url = getNativeUrl(url); + } else { + console.error('Failed to decode internal URL: ' + input); + } + + return url; + } + return { getRoots: getRoots, findFilesystem: findFilesystem, @@ -138,6 +203,7 @@ var rootsUtils = (function() { stripTrailingSlash: stripTrailingSlash, createEntry: createEntry, isRootUri: isRootUri, - isValidFileName: isValidFileName + isValidFileName: isValidFileName, + internalUrlToNativePath: internalUrlToNativePath }; })(); -- 2.7.4