Repository: cordova-plugin-file-transfer Updated Branches: refs/heads/master 9e93bad83 -> 007f98692
CB-9563 Mulptipart form data is used even a header named Content-Type is present Adds non-multipart implementation for Windows and a corresponding test Adds a fix for Android for uploadResult.bytesSent = 0 for small files Fixes parameters of FILE_NOT_FOUND_ERR case in upload operation on Windows - passing source file name instead of duplicated server/destination argument github: close #117 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/007f9869 Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/tree/007f9869 Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/diff/007f9869 Branch: refs/heads/master Commit: 007f98692b15f5a6c4902abf393bee8dc5edeced Parents: 9e93bad Author: daserge <[email protected]> Authored: Tue Nov 24 01:20:00 2015 +0300 Committer: daserge <[email protected]> Committed: Wed Nov 25 19:57:48 2015 +0300 ---------------------------------------------------------------------- src/android/FileTransfer.java | 59 +++++----- src/windows/FileTransferProxy.js | 215 ++++++++++++++++++---------------- tests/tests.js | 40 +++++-- 3 files changed, 175 insertions(+), 139 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/blob/007f9869/src/android/FileTransfer.java ---------------------------------------------------------------------- diff --git a/src/android/FileTransfer.java b/src/android/FileTransfer.java index 0efbd4e..b9b99dc 100644 --- a/src/android/FileTransfer.java +++ b/src/android/FileTransfer.java @@ -291,7 +291,7 @@ public class FileTransfer extends CordovaPlugin { final JSONObject headers = args.optJSONObject(8) == null ? params.optJSONObject("headers") : args.optJSONObject(8); final String objectId = args.getString(9); final String httpMethod = getArgument(args, 10, "POST"); - + final CordovaResourceApi resourceApi = webView.getResourceApi(); Log.d(LOG_TAG, "fileKey: " + fileKey); @@ -303,7 +303,7 @@ public class FileTransfer extends CordovaPlugin { Log.d(LOG_TAG, "headers: " + headers); Log.d(LOG_TAG, "objectId: " + objectId); Log.d(LOG_TAG, "httpMethod: " + httpMethod); - + final Uri targetUri = resourceApi.remapUri(Uri.parse(target)); // Accept a path or a URI for the source. Uri tmpSrc = Uri.parse(source); @@ -323,7 +323,7 @@ public class FileTransfer extends CordovaPlugin { synchronized (activeRequests) { activeRequests.put(objectId, context); } - + cordova.getThreadPool().execute(new Runnable() { public void run() { if (context.aborted) { @@ -363,13 +363,13 @@ public class FileTransfer extends CordovaPlugin { // Use a post method. conn.setRequestMethod(httpMethod); - + // if we specified a Content-Type header, don't do multipart form upload boolean multipartFormUpload = (headers == null) || !headers.has("Content-Type"); if (multipartFormUpload) { conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); } - + // Set the cookies on the response String cookie = getCookies(target); @@ -410,10 +410,10 @@ public class FileTransfer extends CordovaPlugin { byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8"); byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8"); - + // Get a input stream of the file on the phone OpenForReadResult readResult = resourceApi.openForRead(sourceUri); - + int stringLength = beforeDataBytes.length + tailParamsBytes.length; if (readResult.length >= 0) { fixedLength = (int)readResult.length; @@ -439,7 +439,7 @@ public class FileTransfer extends CordovaPlugin { } conn.connect(); - + OutputStream sendStream = null; try { sendStream = conn.getOutputStream(); @@ -449,26 +449,26 @@ public class FileTransfer extends CordovaPlugin { } context.connection = conn; } - + if (multipartFormUpload) { //We don't want to change encoding, we just want this to write for all Unicode. sendStream.write(beforeDataBytes); totalBytes += beforeDataBytes.length; } - + // create a buffer of maximum size int bytesAvailable = readResult.inputStream.available(); int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); byte[] buffer = new byte[bufferSize]; - + // read file and write it into form... int bytesRead = readResult.inputStream.read(buffer, 0, bufferSize); - + long prevBytesRead = 0; while (bytesRead > 0) { + totalBytes += bytesRead; result.setBytesSent(totalBytes); sendStream.write(buffer, 0, bytesRead); - totalBytes += bytesRead; if (totalBytes > prevBytesRead + 102400) { prevBytesRead = totalBytes; Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes"); @@ -483,7 +483,7 @@ public class FileTransfer extends CordovaPlugin { progressResult.setKeepCallback(true); context.sendPluginResult(progressResult); } - + if (multipartFormUpload) { // send multipart form data necessary after file data... sendStream.write(tailParamsBytes); @@ -513,7 +513,7 @@ public class FileTransfer extends CordovaPlugin { } context.connection = conn; } - + ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength())); byte[] buffer = new byte[1024]; int bytesRead = 0; @@ -528,10 +528,10 @@ public class FileTransfer extends CordovaPlugin { } safeClose(inStream); } - + Log.d(LOG_TAG, "got response from server"); Log.d(LOG_TAG, responseString.substring(0, Math.min(256, responseString.length()))); - + // send request and retrieve response result.setResponseCode(responseCode); result.setResponse(responseString); @@ -568,7 +568,7 @@ public class FileTransfer extends CordovaPlugin { https.setSSLSocketFactory(oldSocketFactory); } } - } + } } }); } @@ -601,11 +601,11 @@ public class FileTransfer extends CordovaPlugin { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[] {}; } - + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } - + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @@ -735,7 +735,7 @@ public class FileTransfer extends CordovaPlugin { final boolean trustEveryone = args.optBoolean(2); final String objectId = args.getString(3); final JSONObject headers = args.optJSONObject(4); - + final Uri sourceUri = resourceApi.remapUri(Uri.parse(source)); // Accept a path or a URI for the source. Uri tmpTarget = Uri.parse(target); @@ -790,12 +790,12 @@ public class FileTransfer extends CordovaPlugin { return; } - + final RequestContext context = new RequestContext(source, target, callbackContext); synchronized (activeRequests) { activeRequests.put(objectId, context); } - + cordova.getThreadPool().execute(new Runnable() { public void run() { if (context.aborted) { @@ -815,7 +815,7 @@ public class FileTransfer extends CordovaPlugin { file = resourceApi.mapUriToFile(targetUri); context.targetFile = file; - + Log.d(LOG_TAG, "Download file:" + sourceUri); FileProgressResult progress = new FileProgressResult(); @@ -840,9 +840,9 @@ public class FileTransfer extends CordovaPlugin { // Setup the connection not to verify hostnames https.setHostnameVerifier(DO_NOT_VERIFY); } - + connection.setRequestMethod("GET"); - + // TODO: Make OkHttp use this CookieManager by default. String cookie = getCookies(sourceUri.toString()); @@ -850,15 +850,15 @@ public class FileTransfer extends CordovaPlugin { { connection.setRequestProperty("cookie", cookie); } - + // This must be explicitly set for gzip progress tracking to work. connection.setRequestProperty("Accept-Encoding", "gzip"); - + // Handle the other headers if (headers != null) { addHeadersToRequest(connection, headers); } - + connection.connect(); if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { cached = true; @@ -946,7 +946,6 @@ public class FileTransfer extends CordovaPlugin { result = new PluginResult(PluginResult.Status.ERROR, "File plugin not found; cannot save downloaded file"); } } - } catch (FileNotFoundException e) { JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection, e); Log.e(LOG_TAG, error.toString(), e); http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/blob/007f9869/src/windows/FileTransferProxy.js ---------------------------------------------------------------------- diff --git a/src/windows/FileTransferProxy.js b/src/windows/FileTransferProxy.js index d9e884e..635f3d6 100644 --- a/src/windows/FileTransferProxy.js +++ b/src/windows/FileTransferProxy.js @@ -81,6 +81,8 @@ exec(win, fail, 'FileTransfer', 'upload', var uploadId = options[9]; var httpMethod = options[10]; + var isMultipart = typeof headers["Content-Type"] === 'undefined'; + if (!filePath || (typeof filePath !== 'string')) { errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR,null,server)); return; @@ -131,120 +133,129 @@ exec(win, fail, 'FileTransfer', 'upload', } } - // adding params supplied to request payload - var transferParts = []; - for (var key in params) { - if (params.hasOwnProperty(key)) { - var contentPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(); - contentPart.setHeader("Content-Disposition", "form-data; name=\"" + key + "\""); - contentPart.setText(params[key]); - transferParts.push(contentPart); - } - } - - // Adding file to upload to request payload - var fileToUploadPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(fileKey, fileName); - fileToUploadPart.setFile(storageFile); - transferParts.push(fileToUploadPart); - // create download object. This will throw an exception if URL is malformed var uri = new Windows.Foundation.Uri(server); + + var createUploadOperation; try { - uploader.createUploadAsync(uri, transferParts).then( - function (upload) { - // update internal TransferOperation object with newly created promise - var uploadOperation = upload.startAsync(); - fileTransferOps[uploadId].promise = uploadOperation; - - uploadOperation.then( - function (result) { - // Update TransferOperation object with new state, delete promise property - // since it is not actual anymore - var currentUploadOp = fileTransferOps[uploadId]; - if (currentUploadOp) { - currentUploadOp.state = FileTransferOperation.DONE; - currentUploadOp.promise = null; - } + if (isMultipart) { + // adding params supplied to request payload + var transferParts = []; + for (var key in params) { + if (params.hasOwnProperty(key)) { + var contentPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(); + contentPart.setHeader("Content-Disposition", "form-data; name=\"" + key + "\""); + contentPart.setText(params[key]); + transferParts.push(contentPart); + } + } - var response = result.getResponseInformation(); - var ftResult = new FileUploadResult(result.progress.bytesSent, response.statusCode, ''); + // Adding file to upload to request payload + var fileToUploadPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(fileKey, fileName); + fileToUploadPart.setFile(storageFile); + transferParts.push(fileToUploadPart); - // if server's response doesn't contain any data, then resolve operation now - if (result.progress.bytesReceived === 0) { - successCallback(ftResult); - return; - } + createUploadOperation = uploader.createUploadAsync(uri, transferParts); + } else { + createUploadOperation = WinJS.Promise.wrap(uploader.createUpload(uri, storageFile)); + } + } catch (e) { + errorCallback(new FTErr(FTErr.INVALID_URL_ERR)); + return; + } - // otherwise create a data reader, attached to response stream to get server's response - var reader = new Windows.Storage.Streams.DataReader(result.getResultStreamAt(0)); - reader.loadAsync(result.progress.bytesReceived).then(function (size) { - ftResult.response = reader.readString(size); - successCallback(ftResult); - reader.close(); - }); - }, - function (error) { - var source = nativePathToCordova(filePath); - - // Handle download error here. - // Wrap this routines into promise due to some async methods - var getTransferError = new WinJS.Promise(function(resolve) { - if (error.message === 'Canceled') { - // If download was cancelled, message property will be specified - resolve(new FTErr(FTErr.ABORT_ERR, source, server, null, null, error)); + createUploadOperation.then( + function (upload) { + // update internal TransferOperation object with newly created promise + var uploadOperation = upload.startAsync(); + fileTransferOps[uploadId].promise = uploadOperation; + + uploadOperation.then( + function (result) { + // Update TransferOperation object with new state, delete promise property + // since it is not actual anymore + var currentUploadOp = fileTransferOps[uploadId]; + if (currentUploadOp) { + currentUploadOp.state = FileTransferOperation.DONE; + currentUploadOp.promise = null; + } + + var response = result.getResponseInformation(); + var ftResult = new FileUploadResult(result.progress.bytesSent, response.statusCode, ''); + + // if server's response doesn't contain any data, then resolve operation now + if (result.progress.bytesReceived === 0) { + successCallback(ftResult); + return; + } + + // otherwise create a data reader, attached to response stream to get server's response + var reader = new Windows.Storage.Streams.DataReader(result.getResultStreamAt(0)); + reader.loadAsync(result.progress.bytesReceived).then(function (size) { + ftResult.response = reader.readString(size); + successCallback(ftResult); + reader.close(); + }); + }, + function (error) { + var source = nativePathToCordova(filePath); + + // Handle download error here. + // Wrap this routines into promise due to some async methods + var getTransferError = new WinJS.Promise(function(resolve) { + if (error.message === 'Canceled') { + // If download was cancelled, message property will be specified + resolve(new FTErr(FTErr.ABORT_ERR, source, server, null, null, error)); + } else { + // in the other way, try to get response property + var response = upload.getResponseInformation(); + if (!response) { + resolve(new FTErr(FTErr.CONNECTION_ERR, source, server)); } else { - // in the other way, try to get response property - var response = upload.getResponseInformation(); - if (!response) { - resolve(new FTErr(FTErr.CONNECTION_ERR, source, server)); - } else { - var reader = new Windows.Storage.Streams.DataReader(upload.getResultStreamAt(0)); - reader.loadAsync(upload.progress.bytesReceived).then(function (size) { - var responseText = reader.readString(size); - resolve(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, server, response.statusCode, responseText, error)); - reader.close(); - }); - } + var reader = new Windows.Storage.Streams.DataReader(upload.getResultStreamAt(0)); + reader.loadAsync(upload.progress.bytesReceived).then(function (size) { + var responseText = reader.readString(size); + resolve(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, server, response.statusCode, responseText, error)); + reader.close(); + }); } - }); - - // Update TransferOperation object with new state, delete promise property - // since it is not actual anymore - var currentUploadOp = fileTransferOps[uploadId]; - if (currentUploadOp) { - currentUploadOp.state = FileTransferOperation.CANCELLED; - currentUploadOp.promise = null; } + }); + + // Update TransferOperation object with new state, delete promise property + // since it is not actual anymore + var currentUploadOp = fileTransferOps[uploadId]; + if (currentUploadOp) { + currentUploadOp.state = FileTransferOperation.CANCELLED; + currentUploadOp.promise = null; + } - // Cleanup, remove incompleted file - getTransferError.then(function(transferError) { - storageFile.deleteAsync().then(function() { - errorCallback(transferError); - }); + // Cleanup, remove incompleted file + getTransferError.then(function(transferError) { + storageFile.deleteAsync().then(function() { + errorCallback(transferError); }); - }, - function (evt) { - var progressEvent = new ProgressEvent('progress', { - loaded: evt.progress.bytesSent, - total: evt.progress.totalBytesToSend, - target: evt.resultFile - }); - progressEvent.lengthComputable = true; - successCallback(progressEvent, { keepCallback: true }); - } - ); - }, - function (err) { - var errorObj = new FTErr(FTErr.INVALID_URL_ERR); - errorObj.exception = err; - errorCallback(errorObj); - } - ); - } catch (e) { - errorCallback(new FTErr(FTErr.INVALID_URL_ERR)); - } + }); + }, + function (evt) { + var progressEvent = new ProgressEvent('progress', { + loaded: evt.progress.bytesSent, + total: evt.progress.totalBytesToSend, + target: evt.resultFile + }); + progressEvent.lengthComputable = true; + successCallback(progressEvent, { keepCallback: true }); + } + ); + }, + function (err) { + var errorObj = new FTErr(FTErr.INVALID_URL_ERR); + errorObj.exception = err; + errorCallback(errorObj); + } + ); }, function(err) { - errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, server, server, null, null, err)); + errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, fileName, server, null, null, err)); }); }, http://git-wip-us.apache.org/repos/asf/cordova-plugin-file-transfer/blob/007f9869/tests/tests.js ---------------------------------------------------------------------- diff --git a/tests/tests.js b/tests/tests.js index fff1eef..48361c5 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -936,15 +936,41 @@ exports.defineAutoTests = function () { done(); }; - var uploadOptionsPut = new FileUploadOptions(); - uploadOptionsPut.fileKey = "file"; - uploadOptionsPut.fileName = fileName; - uploadOptionsPut.mimeType = "text/plain"; - uploadOptionsPut.params = uploadParams; - uploadOptionsPut.httpMethod = "PUT"; + uploadOptions.httpMethod = "PUT"; // NOTE: removing uploadOptions cause Android to timeout - transfer.upload(localFilePath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptionsPut); + transfer.upload(localFilePath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptions); + }, UPLOAD_TIMEOUT); + + it("filetransfer.spec.32 should be able to upload a file (non-multipart)", function (done) { + + var fileURL = SERVER + '/upload'; + + var uploadWin = function (uploadResult) { + + expect(uploadResult.bytesSent).toBeGreaterThan(0); + expect(uploadResult.responseCode).toBe(200); + expect(uploadResult.response).toBeDefined(); + if (uploadResult.response) { + expect(uploadResult.response).toEqual(fileContents + "\n"); + } + expect(transfer.onprogress).toHaveBeenCalled(); + + if (cordova.platformId === 'ios') { + expect(uploadResult.headers).toBeDefined('Expected headers to be defined.'); + expect(uploadResult.headers['Content-Type']).toBeDefined('Expected content-type header to be defined.'); + } + + done(); + }; + + // Content-Type header disables multipart + uploadOptions.headers = { + "Content-Type": "text/plain" + }; + + // NOTE: removing uploadOptions cause Android to timeout + transfer.upload(localFilePath, fileURL, uploadWin, unexpectedCallbacks.httpFail, uploadOptions); }, UPLOAD_TIMEOUT); }); }); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
