Repository: cordova-plugin-file-transfer
Updated Branches:
  refs/heads/master 6f9b59fd8 -> affb5ec0a


CB-7316 Improves current specs compatibility:

* Update handling credentials routine, replace parsing with Uri object by 
regexp parsing due to security errors in Windows.
* Add support to aborting download/upload operations for windows platform.
* Add support of progress events for download/upload operations for windows 
platform.
  - progress event object for upload operation is always empty, due to Windows 
restrictions.
* Fix multiple errors with handling invalid URLs and paths.
* Fix error callbacks in download/upload methods. Now they called with proper 
Error objects.


Project: 
http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/commit/f92244af
Tree: 
http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/tree/f92244af
Diff: 
http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/diff/f92244af

Branch: refs/heads/master
Commit: f92244aff9641dd3a287e7efe7f15344cec3db1c
Parents: b5d1dff
Author: Vladimir Kotikov <[email protected]>
Authored: Fri Aug 15 12:41:59 2014 +0400
Committer: Vladimir Kotikov <[email protected]>
Committed: Mon Aug 18 10:07:49 2014 +0400

----------------------------------------------------------------------
 www/FileTransfer.js               |  31 ++---
 www/windows8/FileTransferProxy.js | 202 ++++++++++++++++++++++++++++-----
 2 files changed, 188 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/blob/f92244af/www/FileTransfer.js
----------------------------------------------------------------------
diff --git a/www/FileTransfer.js b/www/FileTransfer.js
index 4668a3c..ff4bbef 100644
--- a/www/FileTransfer.js
+++ b/www/FileTransfer.js
@@ -32,24 +32,23 @@ function newProgressEvent(result) {
     return pe;
 }
 
+function getUrlCredentials(urlString) {
+    var credentialsPattern = /^http\:\/\/((.*?)\:(.*?))@.*$/g,
+        credentials = credentialsPattern.exec(urlString);
+
+    return credentials && credentials[1];
+}
+
 function getBasicAuthHeader(urlString) {
     var header =  null;
 
-    if (window.btoa) {
-        // parse the url using the Location object
-        var url = document.createElement('a');
-        url.href = urlString;
-
-        var credentials = null;
-        var protocol = url.protocol + "//";
-        var origin = protocol + url.host.replace(":" + url.port, ""); // 
Windows 8 (IE10) append :80 or :443 to url.host
-
-        // check whether there are the username:password credentials in the url
-        if (url.href.indexOf(origin) !== 0) { // credentials found
-            var atIndex = url.href.indexOf("@");
-            credentials = url.href.substring(protocol.length, atIndex);
-        }
 
+    // This is changed due to MS Windows doesn't support credentials in http 
uris
+    // so we detect them by regexp and strip off from result url
+    // Proof: 
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/a327cf3c-f033-4a54-8b7f-03c56ba3203f/windows-foundation-uri-security-problem
+
+    if (window.btoa) {
+        var credentials = getUrlCredentials(urlString);
         if (credentials) {
             var authHeader = "Authorization";
             var authHeaderValue = "Basic " + window.btoa(credentials);
@@ -97,6 +96,8 @@ FileTransfer.prototype.upload = function(filePath, server, 
successCallback, erro
     var httpMethod = null;
     var basicAuthHeader = getBasicAuthHeader(server);
     if (basicAuthHeader) {
+        server = server.replace(getUrlCredentials(server) + '@', '');
+
         options = options || {};
         options.headers = options.headers || {};
         options.headers[basicAuthHeader.name] = basicAuthHeader.value;
@@ -157,6 +158,8 @@ FileTransfer.prototype.download = function(source, target, 
successCallback, erro
 
     var basicAuthHeader = getBasicAuthHeader(source);
     if (basicAuthHeader) {
+        source = source.replace(getUrlCredentials(source) + '@', '');
+
         options = options || {};
         options.headers = options.headers || {};
         options.headers[basicAuthHeader.name] = basicAuthHeader.value;

http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/blob/f92244af/www/windows8/FileTransferProxy.js
----------------------------------------------------------------------
diff --git a/www/windows8/FileTransferProxy.js 
b/www/windows8/FileTransferProxy.js
index d72e985..ba474a4 100644
--- a/www/windows8/FileTransferProxy.js
+++ b/www/windows8/FileTransferProxy.js
@@ -19,19 +19,50 @@
  *
 */
 
+/*jshint -W030 */
 
 var FileTransferError = require('./FileTransferError'),
+    ProgressEvent = require('org.apache.cordova.file.ProgressEvent'),
     FileUploadResult = require('org.apache.cordova.file.FileUploadResult'),
     FileEntry = require('org.apache.cordova.file.FileEntry');
 
+
+// Some private helper functions, hidden by the module
+function cordovaPathToNative(path) {
+    // turn / into \\
+    var cleanPath = path.replace(/\//g, '\\');
+    // turn  \\ into \
+    cleanPath = cleanPath.replace(/\\\\/g, '\\');
+    // strip end \\ characters
+    cleanPath = cleanPath.replace(/\\+$/g, '');
+    return cleanPath;
+}
+
+function nativePathToCordova(path) {
+    var cleanPath = path.replace(/\\/g, '/');
+    return cleanPath;
+}
+
+var fileTransferOps = [];
+
+function FileTransferOperation(state, promise) {
+    this.state = state;
+    this.promise = promise;
+}
+
+FileTransferOperation.PENDING = 0;
+FileTransferOperation.DONE = 1;
+FileTransferOperation.CANCELLED = 2;
+
+
 module.exports = {
 
 /*
 exec(win, fail, 'FileTransfer', 'upload', 
 [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, 
chunkedMode, headers, this._id, httpMethod]);
 */
-    upload:function(successCallback, error, options) {
-        var filePath = options[0];
+    upload:function(successCallback, errorCallback, options) {
+        var filePath = cordovaPathToNative(options[0]);
         var server = options[1];
         var fileKey = options[2] || 'source';
         var fileName = options[3];
@@ -40,9 +71,10 @@ exec(win, fail, 'FileTransfer', 'upload',
         var trustAllHosts = options[6]; // todo
         var chunkedMode = options[7]; // todo 
         var headers = options[8] || {};
+        var uploadId = options[9];
 
         if (filePath === null || typeof filePath === 'undefined') {
-            error && error(FileTransferError.FILE_NOT_FOUND_ERR);
+            errorCallback && 
errorCallback(FileTransferError.FILE_NOT_FOUND_ERR);
             return;
         }
 
@@ -50,6 +82,9 @@ exec(win, fail, 'FileTransfer', 'upload',
             filePath = 
Windows.Storage.ApplicationData.current.localFolder.path + 
String(filePath).substr(8).split("/").join("\\");
         }
 
+        // Create internal download operation object
+        fileTransferOps[uploadId] = new 
FileTransferOperation(FileTransferOperation.PENDING, null);
+
         
Windows.Storage.StorageFile.getFileFromPathAsync(filePath).then(function 
(storageFile) {
 
             if(!fileName) {
@@ -63,6 +98,13 @@ exec(win, fail, 'FileTransfer', 'upload',
 
             
storageFile.openAsync(Windows.Storage.FileAccessMode.read).then(function 
(stream) {
 
+                // check if upload isn't already cancelled
+                var uploadOp = fileTransferOps[uploadId];
+                if (uploadOp && uploadOp.state == 
FileTransferOperation.CANCELLED) {
+                    // Here we should call errorCB with ABORT_ERR error
+                    errorCallback && errorCallback(new 
FileTransferError(FileTransferError.ABORT_ERR, filePath, server));
+                    return;
+                }
 
                 var blob = MSApp.createBlobFromRandomAccessStream(mimeType, 
stream);
 
@@ -73,39 +115,49 @@ exec(win, fail, 'FileTransfer', 'upload',
                     formData.append(key,params[key]);
                 }
 
-                WinJS.xhr({ type: "POST", url: server, data: formData, 
headers: headers }).then(function (response) {
-                    storageFile.getBasicPropertiesAsync().done(function 
(basicProperties) {
-
-                        
Windows.Storage.FileIO.readBufferAsync(storageFile).done(function (buffer) {
-                            var dataReader = 
Windows.Storage.Streams.DataReader.fromBuffer(buffer);
-                            var fileContent = 
dataReader.readString(buffer.length);
-                            dataReader.close();
-                            var ftResult = new FileUploadResult();
-                            ftResult.bytesSent = basicProperties.size;
-                            ftResult.responseCode = response.status;
-                            ftResult.response = fileContent;
-                            successCallback && successCallback(ftResult);
-                        });
+                var uploadOperation;
+                try {
+                    // Create XHR promise for uploading data to server
+                    uploadOperation = WinJS.xhr({ type: "POST", url: server, 
data: formData, headers: headers });
+                    fileTransferOps[uploadId].promise = uploadOperation;
+                } catch (e) {
+                    // it will fail if URL is malformed, so we handle this 
situation
+                    errorCallback && errorCallback(new 
FileTransferError(FileTransferError.INVALID_URL_ERR, filePath, server, null, 
null, e));
+                    return;
+                }
 
+                uploadOperation.then(function (response) {
+                    
storageFile.getBasicPropertiesAsync().done(function(basicProperties) {
+                        var ftResult = new 
FileUploadResult(basicProperties.size, response.status, response.responseText);
+                        successCallback && successCallback(ftResult);
                     });
-                }, function () {
-                    error && error(FileTransferError.INVALID_URL_ERR);
+                }, function(err) {
+                    if ('status' in err) {
+                        errorCallback && errorCallback(new 
FileTransferError(FileTransferError.CONNECTION_ERR, filePath, server, 
err.status, err.responseText, err));
+                    } else {
+                        errorCallback && errorCallback(new 
FileTransferError(FileTransferError.INVALID_URL_ERR, filePath, server, null, 
null, err));
+                    }
+                }, function(evt) {
+                    // progress event handler, calls successCallback with 
empty ProgressEvent
+                    // We can't specify ProgressEvent data here since evt not 
provides any helpful information
+                    var progressEvent = new ProgressEvent('progress');
+                    successCallback && successCallback(progressEvent, { 
keepCallback: true });
                 });
             });
-
-        },function() {
-            error && error(FileTransferError.FILE_NOT_FOUND_ERR);
+        }, function(err) {
+            errorCallback && errorCallback(new 
FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, server, null, 
null, err));
         });
     },
 
-    download:function(successCallback, error, options) {
+    download:function(successCallback, errorCallback, options) {
         var source = options[0];
-        var target = options[1];
+        var target = cordovaPathToNative(options[1]);
+        var downloadId = options[3];
         var headers = options[4] || {};
 
 
         if (target === null || typeof target === undefined) {
-            error && error(FileTransferError.FILE_NOT_FOUND_ERR);
+            errorCallback && 
errorCallback(FileTransferError.FILE_NOT_FOUND_ERR);
             return;
         }
         if (String(target).substr(0, 8) == "file:///") {
@@ -114,30 +166,118 @@ exec(win, fail, 'FileTransfer', 'upload',
         var path = target.substr(0, String(target).lastIndexOf("\\"));
         var fileName = target.substr(String(target).lastIndexOf("\\") + 1);
         if (path === null || fileName === null) {
-            error && error(FileTransferError.FILE_NOT_FOUND_ERR);
+            errorCallback && 
errorCallback(FileTransferError.FILE_NOT_FOUND_ERR);
             return;
         }
 
         var download = null;
 
+        // Create internal download operation object
+        fileTransferOps[downloadId] = new 
FileTransferOperation(FileTransferOperation.PENDING, null);
 
         
Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(function 
(storageFolder) {
             storageFolder.createFileAsync(fileName, 
Windows.Storage.CreationCollisionOption.generateUniqueName).then(function 
(storageFile) {
-                var uri = Windows.Foundation.Uri(source);
-                var downloader = new 
Windows.Networking.BackgroundTransfer.BackgroundDownloader();
 
+                // check if download isn't already cancelled
+                var downloadOp = fileTransferOps[downloadId];
+                if (downloadOp && downloadOp.state == 
FileTransferOperation.CANCELLED) {
+                    // Here we should call errorCB with ABORT_ERR error
+                    errorCallback && errorCallback(new 
FileTransferError(FileTransferError.ABORT_ERR, source, target));
+                    return;
+                }
+
+                // if download isn't cancelled, contunue with creating and 
preparing download operation
+                var downloader = new 
Windows.Networking.BackgroundTransfer.BackgroundDownloader();
                 for (var header in headers) {
                     downloader.setRequestHeader(header, headers[header]);
                 }
-                download = downloader.createDownload(uri, storageFile);
-                download.startAsync().then(function () {
+
+                // create download object. This will throw an exception if URL 
is malformed
+                try {
+                    var uri = Windows.Foundation.Uri(source);
+                    download = downloader.createDownload(uri, storageFile);
+                } catch (e) {
+                    // so we handle this and call errorCallback
+                    errorCallback && errorCallback(new 
FileTransferError(FileTransferError.INVALID_URL_ERR));
+                    return;
+                }
+
+                var downloadOperation = download.startAsync();
+                // update internal TransferOperation object with newly created 
promise
+                fileTransferOps[downloadId].promise = downloadOperation;
+
+                downloadOperation.then(function () {
+
+                    // Update TransferOperation object with new state, delete 
promise property
+                    // since it is not actual anymore
+                    var currentDownloadOp = fileTransferOps[downloadId];
+                    if (currentDownloadOp) {
+                        currentDownloadOp.state = FileTransferOperation.DONE;
+                        currentDownloadOp.promise = null;
+                    }
+
                     successCallback && successCallback(new 
FileEntry(storageFile.name, storageFile.path));
-                }, function () {
-                    error && error(FileTransferError.INVALID_URL_ERR);
+                }, function (error) {
+
+                    var result;
+                    // Handle download error here. If download was cancelled,
+                    // message property will be specified
+                    if (error.message == 'Canceled') {
+                        result = new 
FileTransferError(FileTransferError.ABORT_ERR, source, target, null, null, 
error);
+                    } else {
+                        // in the other way, try to get response property
+                        var response = download.getResponseInformation();
+                        if (!response) {
+                            result = new 
FileTransferError(FileTransferError.CONNECTION_ERR, source, target);
+                        } else if (response.statusCode == 401 || 
response.statusCode == 404) {
+                            result = new 
FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, 
response.statusCode, null, error);
+                        }
+                    }
+
+                    // Update TransferOperation object with new state, delete 
promise property
+                    // since it is not actual anymore
+                    var currentDownloadOp = fileTransferOps[downloadId];
+                    if (currentDownloadOp) {
+                        currentDownloadOp.state = 
FileTransferOperation.CANCELLED;
+                        currentDownloadOp.promise = null;
+                    }
+
+                    // Cleanup, remove incompleted file
+                    storageFile.deleteAsync().then(function () {
+                        errorCallback && errorCallback(result);
+                    });
+
+
+                }, function(evt) {
+
+                    var progressEvent = new ProgressEvent('progress', {
+                        loaded: evt.progress.bytesReceived,
+                        total: evt.progress.totalBytesToReceive,
+                        target: evt.resultFile
+                    });
+                    progressEvent.lengthComputable = true;
+
+                    successCallback && successCallback(progressEvent, { 
keepCallback: true });
                 });
+            }, function (error) {
+                errorCallback && errorCallback(new 
FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, null, 
null, error));
             });
+        }, function (error) {
+            errorCallback && errorCallback(new 
FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, null, 
null, error));
         });
+    },
+
+    abort: function (successCallback, error, options) {
+        var fileTransferOpId = options[0];
+
+        // Try to find transferOperation with id specified, and cancel its' 
promise
+        var currentOp = fileTransferOps[fileTransferOpId];
+        if (currentOp) {
+            currentOp.state = FileTransferOperation.CANCELLED;
+            currentOp.promise && currentOp.promise.cancel();
+        }
     }
+
 };
 
 require("cordova/exec/proxy").add("FileTransfer",module.exports);

Reply via email to