diff --git a/README.md b/README.md index ae19ffe..55bd104 100644 --- a/README.md +++ b/README.md @@ -58,19 +58,11 @@ cordova plugin add cordova-plugin-file-transfer ## Supported Platforms -- Amazon Fire OS - Android -- BlackBerry 10 - Browser -- Firefox OS** - iOS -- Windows Phone 7 and 8* - Windows -\* _Do not support `onprogress` nor `abort()`_ - -\** _Do not support `onprogress`_ - # FileTransfer The `FileTransfer` object provides a way to upload files using an HTTP @@ -259,10 +251,6 @@ fileTransfer.download( ); ``` -### WP8 Quirks - -- Download requests is being cached by native implementation. To avoid caching, pass `if-Modified-Since` header to download method. - ### Browser Quirks - __withCredentials__: _boolean_ that tells the browser to set the withCredentials flag on the XMLHttpRequest diff --git a/package.json b/package.json index a9788f9..993feca 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,8 @@ "id": "cordova-plugin-file-transfer", "platforms": [ "android", - "amazon-fireos", - "ubuntu", - "blackberry10", "ios", - "wp7", - "wp8", - "windows8", "windows", - "firefoxos", "browser" ] }, @@ -37,15 +30,8 @@ "transfer", "ecosystem:cordova", "cordova-android", - "cordova-amazon-fireos", - "cordova-ubuntu", - "cordova-blackberry10", "cordova-ios", - "cordova-wp7", - "cordova-wp8", - "cordova-windows8", "cordova-windows", - "cordova-firefoxos", "cordova-browser" ], "author": "Apache Software Foundation", diff --git a/plugin.xml b/plugin.xml index 41c7a91..1a1fd19 100644 --- a/plugin.xml +++ b/plugin.xml @@ -56,45 +56,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -108,41 +69,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -150,17 +76,6 @@ - - - - - - - - - - - diff --git a/src/amazon/FileTransfer.java b/src/amazon/FileTransfer.java deleted file mode 100644 index 1563a39..0000000 --- a/src/amazon/FileTransfer.java +++ /dev/null @@ -1,898 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you 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. -*/ -package org.apache.cordova.filetransfer; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URLConnection; -import java.net.URLDecoder; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.HashMap; -import java.util.Iterator; -import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.cordova.Config; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.CordovaResourceApi; -import org.apache.cordova.CordovaResourceApi.OpenForReadResult; -import org.apache.cordova.PluginResult; -import org.apache.cordova.file.FileUtils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.net.Uri; -import android.os.Build; -import android.util.Log; -import com.amazon.android.webkit.AmazonCookieManager; - -public class FileTransfer extends CordovaPlugin { - - private static final String LOG_TAG = "FileTransfer"; - private static final String LINE_START = "--"; - private static final String LINE_END = "\r\n"; - private static final String BOUNDARY = "+++++"; - - public static int FILE_NOT_FOUND_ERR = 1; - public static int INVALID_URL_ERR = 2; - public static int CONNECTION_ERR = 3; - public static int ABORTED_ERR = 4; - - private static HashMap activeRequests = new HashMap(); - private static final int MAX_BUFFER_SIZE = 16 * 1024; - - private static final class RequestContext { - String source; - String target; - File targetFile; - CallbackContext callbackContext; - InputStream currentInputStream; - OutputStream currentOutputStream; - boolean aborted; - RequestContext(String source, String target, CallbackContext callbackContext) { - this.source = source; - this.target = target; - this.callbackContext = callbackContext; - } - void sendPluginResult(PluginResult pluginResult) { - synchronized (this) { - if (!aborted) { - callbackContext.sendPluginResult(pluginResult); - } - } - } - } - - /** - * Adds an interface method to an InputStream to return the number of bytes - * read from the raw stream. This is used to track total progress against - * the HTTP Content-Length header value from the server. - */ - private static abstract class TrackingInputStream extends FilterInputStream { - public TrackingInputStream(final InputStream in) { - super(in); - } - public abstract long getTotalRawBytesRead(); - } - - private static class ExposedGZIPInputStream extends GZIPInputStream { - public ExposedGZIPInputStream(final InputStream in) throws IOException { - super(in); - } - public Inflater getInflater() { - return inf; - } - } - - /** - * Provides raw bytes-read tracking for a GZIP input stream. Reports the - * total number of compressed bytes read from the input, rather than the - * number of uncompressed bytes. - */ - private static class TrackingGZIPInputStream extends TrackingInputStream { - private ExposedGZIPInputStream gzin; - public TrackingGZIPInputStream(final ExposedGZIPInputStream gzin) throws IOException { - super(gzin); - this.gzin = gzin; - } - public long getTotalRawBytesRead() { - return gzin.getInflater().getBytesRead(); - } - } - - /** - * Provides simple total-bytes-read tracking for an existing InputStream - */ - private static class SimpleTrackingInputStream extends TrackingInputStream { - private long bytesRead = 0; - public SimpleTrackingInputStream(InputStream stream) { - super(stream); - } - - private int updateBytesRead(int newBytesRead) { - if (newBytesRead != -1) { - bytesRead += newBytesRead; - } - return newBytesRead; - } - - @Override - public int read() throws IOException { - return updateBytesRead(super.read()); - } - - // Note: FilterInputStream delegates read(byte[] bytes) to the below method, - // so we don't override it or else double count (CB-5631). - @Override - public int read(byte[] bytes, int offset, int count) throws IOException { - return updateBytesRead(super.read(bytes, offset, count)); - } - - public long getTotalRawBytesRead() { - return bytesRead; - } - } - - @Override - public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { - if (action.equals("upload") || action.equals("download")) { - String source = args.getString(0); - String target = args.getString(1); - - if (action.equals("upload")) { - try { - source = URLDecoder.decode(source, "UTF-8"); - upload(source, target, args, callbackContext); - } catch (UnsupportedEncodingException e) { - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.MALFORMED_URL_EXCEPTION, "UTF-8 error.")); - } - } else { - download(source, target, args, callbackContext); - } - return true; - } else if (action.equals("abort")) { - String objectId = args.getString(0); - abort(objectId); - callbackContext.success(); - return true; - } - return false; - } - - private static void addHeadersToRequest(URLConnection connection, JSONObject headers) { - try { - for (Iterator iter = headers.keys(); iter.hasNext(); ) { - String headerKey = iter.next().toString(); - JSONArray headerValues = headers.optJSONArray(headerKey); - if (headerValues == null) { - headerValues = new JSONArray(); - headerValues.put(headers.getString(headerKey)); - } - connection.setRequestProperty(headerKey, headerValues.getString(0)); - for (int i = 1; i < headerValues.length(); ++i) { - connection.addRequestProperty(headerKey, headerValues.getString(i)); - } - } - } catch (JSONException e1) { - // No headers to be manipulated! - } - } - - /** - * Uploads the specified file to the server URL provided using an HTTP multipart request. - * @param source Full path of the file on the file system - * @param target URL of the server to receive the file - * @param args JSON Array of args - * @param callbackContext callback id for optional progress reports - * - * args[2] fileKey Name of file request parameter - * args[3] fileName File name to be used on server - * args[4] mimeType Describes file content type - * args[5] params key:value pairs of user-defined parameters - * @return FileUploadResult containing result of upload request - */ - private void upload(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException { - Log.d(LOG_TAG, "upload " + source + " to " + target); - - // Setup the options - final String fileKey = getArgument(args, 2, "file"); - final String fileName = getArgument(args, 3, "image.jpg"); - final String mimeType = getArgument(args, 4, "image/jpeg"); - final JSONObject params = args.optJSONObject(5) == null ? new JSONObject() : args.optJSONObject(5); - final boolean trustEveryone = args.optBoolean(6); - // Always use chunked mode unless set to false as per API - final boolean chunkedMode = args.optBoolean(7) || args.isNull(7); - // Look for headers on the params map for backwards compatibility with older Cordova versions. - 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); - Log.d(LOG_TAG, "fileName: " + fileName); - Log.d(LOG_TAG, "mimeType: " + mimeType); - Log.d(LOG_TAG, "params: " + params); - Log.d(LOG_TAG, "trustEveryone: " + trustEveryone); - Log.d(LOG_TAG, "chunkedMode: " + chunkedMode); - 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); - final Uri sourceUri = resourceApi.remapUri( - tmpSrc.getScheme() != null ? tmpSrc : Uri.fromFile(new File(source))); - - int uriType = CordovaResourceApi.getUriType(targetUri); - final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS; - if (uriType != CordovaResourceApi.URI_TYPE_HTTP && !useHttps) { - JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0); - Log.e(LOG_TAG, "Unsupported URI: " + targetUri); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - 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) { - return; - } - HttpURLConnection conn = null; - HostnameVerifier oldHostnameVerifier = null; - SSLSocketFactory oldSocketFactory = null; - int totalBytes = 0; - int fixedLength = -1; - try { - // Create return object - FileUploadResult result = new FileUploadResult(); - FileProgressResult progress = new FileProgressResult(); - - //------------------ CLIENT REQUEST - // Open a HTTP connection to the URL based on protocol - conn = resourceApi.createHttpConnection(targetUri); - if (useHttps && trustEveryone) { - // Setup the HTTPS connection class to trust everyone - HttpsURLConnection https = (HttpsURLConnection)conn; - oldSocketFactory = trustAllHosts(https); - // Save the current hostnameVerifier - oldHostnameVerifier = https.getHostnameVerifier(); - // Setup the connection not to verify hostnames - https.setHostnameVerifier(DO_NOT_VERIFY); - } - - // Allow Inputs - conn.setDoInput(true); - - // Allow Outputs - conn.setDoOutput(true); - - // Don't use a cached copy. - conn.setUseCaches(false); - - // 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 = AmazonCookieManager.getInstance().getCookie(target); - if (cookie != null) { - conn.setRequestProperty("Cookie", cookie); - } - - // Handle the other headers - if (headers != null) { - addHeadersToRequest(conn, headers); - } - - /* - * Store the non-file portions of the multipart data as a string, so that we can add it - * to the contentSize, since it is part of the body of the HTTP request. - */ - StringBuilder beforeData = new StringBuilder(); - try { - for (Iterator iter = params.keys(); iter.hasNext();) { - Object key = iter.next(); - if(!String.valueOf(key).equals("headers")) - { - beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END); - beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"'); - beforeData.append(LINE_END).append(LINE_END); - beforeData.append(params.getString(key.toString())); - beforeData.append(LINE_END); - } - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - - beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END); - beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";"); - beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END); - beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END); - 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; - if (multipartFormUpload) - fixedLength += stringLength; - progress.setLengthComputable(true); - progress.setTotal(fixedLength); - } - Log.d(LOG_TAG, "Content Length: " + fixedLength); - // setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices. - // http://code.google.com/p/android/issues/detail?id=3164 - // It also causes OOM if HTTPS is used, even on newer devices. - boolean useChunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps); - useChunkedMode = useChunkedMode || (fixedLength == -1); - - if (useChunkedMode) { - conn.setChunkedStreamingMode(MAX_BUFFER_SIZE); - // Although setChunkedStreamingMode sets this header, setting it explicitly here works - // around an OutOfMemoryException when using https. - conn.setRequestProperty("Transfer-Encoding", "chunked"); - } else { - conn.setFixedLengthStreamingMode(fixedLength); - } - - conn.connect(); - - OutputStream sendStream = null; - try { - sendStream = conn.getOutputStream(); - synchronized (context) { - if (context.aborted) { - return; - } - context.currentOutputStream = sendStream; - } - - 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) { - 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"); - } - bytesAvailable = readResult.inputStream.available(); - bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE); - bytesRead = readResult.inputStream.read(buffer, 0, bufferSize); - - // Send a progress event. - progress.setLoaded(totalBytes); - PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); - progressResult.setKeepCallback(true); - context.sendPluginResult(progressResult); - } - - if (multipartFormUpload) { - // send multipart form data necessary after file data... - sendStream.write(tailParamsBytes); - totalBytes += tailParamsBytes.length; - } - sendStream.flush(); - } finally { - safeClose(readResult.inputStream); - safeClose(sendStream); - } - context.currentOutputStream = null; - Log.d(LOG_TAG, "Sent " + totalBytes + " of " + fixedLength); - - //------------------ read the SERVER RESPONSE - String responseString; - int responseCode = conn.getResponseCode(); - Log.d(LOG_TAG, "response code: " + responseCode); - Log.d(LOG_TAG, "response headers: " + conn.getHeaderFields()); - TrackingInputStream inStream = null; - try { - inStream = getInputStream(conn); - synchronized (context) { - if (context.aborted) { - return; - } - context.currentInputStream = inStream; - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(1024, conn.getContentLength())); - byte[] buffer = new byte[1024]; - int bytesRead = 0; - // write bytes to file - while ((bytesRead = inStream.read(buffer)) > 0) { - out.write(buffer, 0, bytesRead); - } - responseString = out.toString("UTF-8"); - } finally { - context.currentInputStream = null; - 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); - - context.sendPluginResult(new PluginResult(PluginResult.Status.OK, result.toJSONObject())); - } catch (FileNotFoundException e) { - JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), e); - context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - } catch (IOException e) { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), e); - Log.e(LOG_TAG, "Failed after uploading " + totalBytes + " of " + fixedLength + " bytes."); - context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - context.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - } catch (Throwable t) { - // Shouldn't happen, but will - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), t); - context.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - } finally { - synchronized (activeRequests) { - activeRequests.remove(objectId); - } - - if (conn != null) { - // Revert back to the proper verifier and socket factories - // Revert back to the proper verifier and socket factories - if (trustEveryone && useHttps) { - HttpsURLConnection https = (HttpsURLConnection) conn; - https.setHostnameVerifier(oldHostnameVerifier); - https.setSSLSocketFactory(oldSocketFactory); - } - } - } - } - }); - } - - private static void safeClose(Closeable stream) { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - } - } - } - - private static TrackingInputStream getInputStream(URLConnection conn) throws IOException { - String encoding = conn.getContentEncoding(); - if (encoding != null && encoding.equalsIgnoreCase("gzip")) { - return new TrackingGZIPInputStream(new ExposedGZIPInputStream(conn.getInputStream())); - } - return new SimpleTrackingInputStream(conn.getInputStream()); - } - - // always verify the host - don't check for certificate - private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; - // Create a trust manager that does not validate certificate chains - private static final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - 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 { - } - } }; - - /** - * This function will install a trust manager that will blindly trust all SSL - * certificates. The reason this code is being added is to enable developers - * to do development using self signed SSL certificates on their web server. - * - * The standard HttpsURLConnection class will throw an exception on self - * signed certificates if this code is not run. - */ - private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) { - // Install the all-trusting trust manager - SSLSocketFactory oldFactory = connection.getSSLSocketFactory(); - try { - // Install our all trusting manager - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - SSLSocketFactory newFactory = sc.getSocketFactory(); - connection.setSSLSocketFactory(newFactory); - } catch (Exception e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - return oldFactory; - } - - private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection) { - - int httpStatus = 0; - StringBuilder bodyBuilder = new StringBuilder(); - String body = null; - if (connection != null) { - try { - if (connection instanceof HttpURLConnection) { - httpStatus = ((HttpURLConnection)connection).getResponseCode(); - InputStream err = ((HttpURLConnection) connection).getErrorStream(); - if(err != null) - { - BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8")); - try { - String line = reader.readLine(); - while(line != null) { - bodyBuilder.append(line); - line = reader.readLine(); - if(line != null) { - bodyBuilder.append('\n'); - } - } - body = bodyBuilder.toString(); - } finally { - reader.close(); - } - } - } - // IOException can leave connection object in a bad state, so catch all exceptions. - } catch (Throwable e) { - Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e); - } - } - - return createFileTransferError(errorCode, source, target, body, httpStatus); - } - - /** - * Create an error object based on the passed in errorCode - * @param errorCode the error - * @return JSONObject containing the error - */ - private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) { - JSONObject error = null; - try { - error = new JSONObject(); - error.put("code", errorCode); - error.put("source", source); - error.put("target", target); - if(body != null) - { - error.put("body", body); - } - if (httpStatus != null) { - error.put("http_status", httpStatus); - } - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - } - return error; - } - - /** - * Convenience method to read a parameter from the list of JSON args. - * @param args the args passed to the Plugin - * @param position the position to retrieve the arg from - * @param defaultString the default to be used if the arg does not exist - * @return String with the retrieved value - */ - private static String getArgument(JSONArray args, int position, String defaultString) { - String arg = defaultString; - if (args.length() > position) { - arg = args.optString(position); - if (arg == null || "null".equals(arg)) { - arg = defaultString; - } - } - return arg; - } - - /** - * Downloads a file form a given URL and saves it to the specified directory. - * - * @param source URL of the server to receive the file - * @param target Full path of the file on the file system - */ - private void download(final String source, final String target, JSONArray args, CallbackContext callbackContext) throws JSONException { - Log.d(LOG_TAG, "download " + source + " to " + target); - - final CordovaResourceApi resourceApi = webView.getResourceApi(); - - 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); - final Uri targetUri = resourceApi.remapUri( - tmpTarget.getScheme() != null ? tmpTarget : Uri.fromFile(new File(target))); - - int uriType = CordovaResourceApi.getUriType(sourceUri); - final boolean useHttps = uriType == CordovaResourceApi.URI_TYPE_HTTPS; - final boolean isLocalTransfer = !useHttps && uriType != CordovaResourceApi.URI_TYPE_HTTP; - if (uriType == CordovaResourceApi.URI_TYPE_UNKNOWN) { - JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0); - Log.e(LOG_TAG, "Unsupported URI: " + targetUri); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - return; - } - - // TODO: refactor to also allow resources & content: - if (!isLocalTransfer && !Config.isUrlWhiteListed(source)) { - Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'"); - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401); - callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error)); - 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) { - return; - } - HttpURLConnection connection = null; - HostnameVerifier oldHostnameVerifier = null; - SSLSocketFactory oldSocketFactory = null; - File file = null; - PluginResult result = null; - TrackingInputStream inputStream = null; - - OutputStream outputStream = null; - try { - OpenForReadResult readResult = null; - outputStream = resourceApi.openOutputStream(targetUri); - - file = resourceApi.mapUriToFile(targetUri); - context.targetFile = file; - - Log.d(LOG_TAG, "Download file:" + sourceUri); - - FileProgressResult progress = new FileProgressResult(); - - if (isLocalTransfer) { - readResult = resourceApi.openForRead(sourceUri); - if (readResult.length != -1) { - progress.setLengthComputable(true); - progress.setTotal(readResult.length); - } - inputStream = new SimpleTrackingInputStream(readResult.inputStream); - } else { - // connect to server - // Open a HTTP connection to the URL based on protocol - connection = resourceApi.createHttpConnection(sourceUri); - if (useHttps && trustEveryone) { - // Setup the HTTPS connection class to trust everyone - HttpsURLConnection https = (HttpsURLConnection)connection; - oldSocketFactory = trustAllHosts(https); - // Save the current hostnameVerifier - oldHostnameVerifier = https.getHostnameVerifier(); - // Setup the connection not to verify hostnames - https.setHostnameVerifier(DO_NOT_VERIFY); - } - - connection.setRequestMethod("GET"); - - // TODO: Make OkHttp use this AmazonCookieManager by default. - String cookie = AmazonCookieManager.getInstance().getCookie(sourceUri.toString()); - if(cookie != null) - { - 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.getContentEncoding() == null || connection.getContentEncoding().equalsIgnoreCase("gzip")) { - // Only trust content-length header if we understand - // the encoding -- identity or gzip - if (connection.getContentLength() != -1) { - progress.setLengthComputable(true); - progress.setTotal(connection.getContentLength()); - } - } - inputStream = getInputStream(connection); - } - - try { - synchronized (context) { - if (context.aborted) { - return; - } - context.currentInputStream = inputStream; - } - - // write bytes to file - byte[] buffer = new byte[MAX_BUFFER_SIZE]; - int bytesRead = 0; - while ((bytesRead = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, bytesRead); - // Send a progress event. - progress.setLoaded(inputStream.getTotalRawBytesRead()); - PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); - progressResult.setKeepCallback(true); - context.sendPluginResult(progressResult); - } - } finally { - context.currentInputStream = null; - safeClose(inputStream); - safeClose(outputStream); - } - - Log.d(LOG_TAG, "Saved file: " + target); - - // create FileEntry object - FileUtils filePlugin = (FileUtils)webView.pluginManager.getPlugin("File"); - if (filePlugin != null) { - JSONObject fileEntry = filePlugin.getEntryForFile(file); - if (fileEntry != null) { - result = new PluginResult(PluginResult.Status.OK, fileEntry); - } else { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); - Log.e(LOG_TAG, "File plugin cannot represent download path"); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } - } else { - Log.e(LOG_TAG, "File plugin not found; cannot save downloaded file"); - 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); - Log.e(LOG_TAG, error.toString(), e); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } catch (IOException e) { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); - Log.e(LOG_TAG, error.toString(), e); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } catch (JSONException e) { - Log.e(LOG_TAG, e.getMessage(), e); - result = new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } catch (Throwable e) { - JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); - Log.e(LOG_TAG, error.toString(), e); - result = new PluginResult(PluginResult.Status.IO_EXCEPTION, error); - } finally { - safeClose(outputStream); - synchronized (activeRequests) { - activeRequests.remove(objectId); - } - - if (connection != null) { - // Revert back to the proper verifier and socket factories - if (trustEveryone && useHttps) { - HttpsURLConnection https = (HttpsURLConnection) connection; - https.setHostnameVerifier(oldHostnameVerifier); - https.setSSLSocketFactory(oldSocketFactory); - } - } - - if (result == null) { - result = new PluginResult(PluginResult.Status.ERROR, createFileTransferError(CONNECTION_ERR, source, target, connection)); - } - // Remove incomplete download. - if (result.getStatus() != PluginResult.Status.OK.ordinal() && file != null) { - file.delete(); - } - context.sendPluginResult(result); - } - } - }); - } - - /** - * Abort an ongoing upload or download. - */ - private void abort(String objectId) { - final RequestContext context; - synchronized (activeRequests) { - context = activeRequests.remove(objectId); - } - if (context != null) { - File file = context.targetFile; - if (file != null) { - file.delete(); - } - // Trigger the abort callback immediately to minimize latency between it and abort() being called. - JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1); - synchronized (context) { - context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error)); - context.aborted = true; - } - // Closing the streams can block, so execute on a background thread. - cordova.getThreadPool().execute(new Runnable() { - public void run() { - synchronized (context) { - safeClose(context.currentInputStream); - safeClose(context.currentOutputStream); - } - } - }); - } - } -} diff --git a/src/ubuntu/file-transfer.cpp b/src/ubuntu/file-transfer.cpp deleted file mode 100644 index 5b1adea..0000000 --- a/src/ubuntu/file-transfer.cpp +++ /dev/null @@ -1,265 +0,0 @@ -/* - * - * Copyright 2013 Canonical Ltd. - * - * 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. - * -*/ - -#include "file-transfer.h" -#include -#include - -static void SetHeaders(QNetworkRequest &request, const QVariantMap &headers) { - for (const QString &key: headers.keys()) { - QVariant val = *headers.find(key); - QString value = val.toString(); - if (val.userType() == QMetaType::QVariantList || val.userType() == QMetaType::QStringList) { - QList list = val.toList(); - for (QVariant v: list) { - if (value.size()) - value += ", "; - value += v.toString(); - } - } - request.setRawHeader(key.toUtf8(), value.toUtf8()); - } -} - -void FileTransfer::download(int scId, int ecId, const QString& url, const QString &target, bool /*trustAllHost*/, int id, const QVariantMap &headers) { - QSharedPointer request(new FileTransferRequest(_manager, scId, ecId, id, this)); - - assert(_id2request.find(id) == _id2request.end()); - - _id2request.insert(id, request); - - request->connect(request.data(), &FileTransferRequest::done, [&]() { - auto it = _id2request.find(id); - while (it != _id2request.end() && it.key() == id) { - if (it.value().data() == request.data()) { - _id2request.erase(it); - break; - } - it++; - } - }); - request->download(url, target, headers); -} - -void FileTransfer::upload(int scId, int ecId, const QString &fileURI, const QString& url, const QString& fileKey, const QString& fileName, const QString& mimeType, - const QVariantMap & params, bool /*trustAllHosts*/, bool /*chunkedMode*/, const QVariantMap &headers, int id, const QString &/*httpMethod*/) { - QSharedPointer request(new FileTransferRequest(_manager, scId, ecId, id, this)); - - assert(_id2request.find(id) == _id2request.end()); - - _id2request.insert(id, request); - - request->connect(request.data(), &FileTransferRequest::done, [&]() { - auto it = _id2request.find(id); - while (it != _id2request.end() && it.key() == id) { - if (it.value().data() == request.data()) { - _id2request.erase(it); - break; - } - it++; - } - }); - request->upload(url, fileURI, fileKey, fileName, mimeType, params, headers); -} - -void FileTransfer::abort(int scId, int ecId, int id) { - Q_UNUSED(scId) - Q_UNUSED(ecId) - - auto it = _id2request.find(id); - while (it != _id2request.end() && it.key() == id) { - (*it)->abort(); - it++; - } -} - -void FileTransferRequest::download(const QString& uri, const QString &targetURI, const QVariantMap &headers) { - QUrl url(uri); - QNetworkRequest request; - - QSharedPointer filePlugin(_plugin->cordova()->getPlugin()); - - if (!filePlugin.data()) - return; - - if (!url.isValid()) { - QVariantMap map; - map.insert("code", INVALID_URL_ERR); - map.insert("source", uri); - map.insert("target", targetURI); - _plugin->cb(_ecId, map); - emit done(); - return; - } - - request.setUrl(url); - if (url.password().size() || url.userName().size()) { - QString headerData = "Basic " + (url.userName() + ":" + url.password()).toLocal8Bit().toBase64(); - request.setRawHeader("Authorization", headerData.toLocal8Bit()); - } - SetHeaders(request, headers); - _reply = QSharedPointer(_manager.get(request)); - - _reply->connect(_reply.data(), &QNetworkReply::finished, [this, targetURI, uri, filePlugin]() { - if (!_scId || _reply->error() != QNetworkReply::NoError) - return; - - QPair f1(dynamic_cast(filePlugin.data())->resolveURI(targetURI)); - - QFile res(f1.second.absoluteFilePath()); - if (!f1.first || !res.open(QIODevice::WriteOnly)) { - QVariantMap map; - map.insert("code", INVALID_URL_ERR); - map.insert("source", uri); - map.insert("target", targetURI); - _plugin->cb(_ecId, map); - emit done(); - return; - } - res.write(_reply->readAll()); - - _plugin->cb(_scId, dynamic_cast(filePlugin.data())->file2map(f1.second)); - - emit done(); - }); - _reply->connect(_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError))); - _reply->connect(_reply.data(), SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(progress(qint64, qint64))); -} - -void FileTransferRequest::upload(const QString& _url, const QString& fileURI, QString fileKey, QString fileName, QString mimeType, const QVariantMap ¶ms, const QVariantMap &headers) { - QUrl url(_url); - QNetworkRequest request; - - QSharedPointer filePlugin(_plugin->cordova()->getPlugin()); - - if (!filePlugin.data()) - return; - - if (!url.isValid()) { - QVariantMap map; - map.insert("code", INVALID_URL_ERR); - map.insert("source", fileURI); - map.insert("target", _url); - _plugin->cb(_ecId, map); - emit done(); - return; - } - - QPair f1(dynamic_cast(filePlugin.data())->resolveURI(fileURI)); - QFile file(f1.second.absoluteFilePath()); - if (!f1.first || !file.open(QIODevice::ReadOnly)) { - QVariantMap map; - map.insert("code", FILE_NOT_FOUND_ERR); - map.insert("source", fileURI); - map.insert("target", _url); - _plugin->cb(_ecId, map); - emit done(); - return; - } - QString content{file.readAll()}; - - request.setUrl(url); - if (url.password().size() || url.userName().size()) { - QString headerData = "Basic " + (url.userName() + ":" + url.password()).toLocal8Bit().toBase64(); - request.setRawHeader("Authorization", headerData.toLocal8Bit()); - } - SetHeaders(request, headers); - - QString boundary = QString("CORDOVA-QT-%1A").arg(qrand()); - while (content.contains(boundary)) { - boundary += QString("B%1A").arg(qrand()); - } - - request.setHeader(QNetworkRequest::ContentTypeHeader, QString("multipart/form-data; boundary=") + boundary); - - fileKey.replace("\"", ""); - fileName.replace("\"", ""); - mimeType.replace("\"", ""); - QString part = "--" + boundary + "\r\n"; - - part += "Content-Disposition: form-data; name=\"" + fileKey +"\"; filename=\"" + fileName + "\"\r\n"; - part += "Content-Type: " + mimeType + "\r\n\r\n"; - part += content + "\r\n"; - - for (QString key: params.keys()) { - part += "--" + boundary + "\r\n"; - part += "Content-Disposition: form-data; name=\"" + key + "\";\r\n\r\n"; - part += params.find(key)->toString(); - part += "\r\n"; - } - - part += QString("--") + boundary + "--" + "\r\n"; - - _reply = QSharedPointer(_manager.post(request, QByteArray(part.toUtf8()))); - - _reply->connect(_reply.data(), &QNetworkReply::finished, [this, content]() { - if (_reply->error() != QNetworkReply::NoError) - return; - int status = 200; - QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - - if (statusCode.isValid()) { - status = statusCode.toInt(); - } - - QVariantMap map; - map.insert("responseCode", status); - map.insert("response", QString(_reply->readAll())); - map.insert("bytesSent", content.size()); - _plugin->cb(_scId, map); - emit done(); - }); - _reply->connect(_reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError))); - _reply->connect(_reply.data(), SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(progress(qint64, qint64))); -} - -void FileTransferRequest::abort() { - QVariantMap map; - map.insert("code", ABORT_ERR); - _plugin->cb(_ecId, map); - _scId = 0; - emit done(); -} - -void FileTransferRequest::error(QNetworkReply::NetworkError code) { - Q_UNUSED(code); - - int status = 404; - QVariant statusCode = _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (statusCode.isValid()) { - status = statusCode.toInt(); - } - - QVariantMap map; - map.insert("http_status", status); - map.insert("body", QString(_reply->readAll())); - map.insert("code", CONNECTION_ERR); - _plugin->cb(_ecId, map); - emit done(); -} - -void FileTransferRequest::progress(qint64 bytesReceived, qint64 bytesTotal) { - QVariantMap map; - map.insert("lengthComputable", true); - map.insert("total", bytesTotal); - map.insert("loaded", bytesReceived); - - if (bytesReceived && bytesTotal && _scId) - _plugin->callbackWithoutRemove(_scId, CordovaInternal::format(map)); -} diff --git a/src/ubuntu/file-transfer.h b/src/ubuntu/file-transfer.h deleted file mode 100644 index 75822cb..0000000 --- a/src/ubuntu/file-transfer.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * - * Copyright 2013 Canonical Ltd. - * - * 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. - * -*/ - -#ifndef FILE_TRANSFER_H_SDASDASDAS -#define FILE_TRANSFER_H_SDASDASDAS - -#include -#include - -#include - -class FileTransfer; - -class FileTransferRequest: public QObject { - Q_OBJECT - - QNetworkAccessManager &_manager; - int _scId, _ecId; - int _id; - QSharedPointer _reply; - - enum FileTransferError { - FILE_NOT_FOUND_ERR = 1, - INVALID_URL_ERR = 2, - CONNECTION_ERR = 3, - ABORT_ERR = 4 - }; - -public: - FileTransferRequest(QNetworkAccessManager &manager, int scId, int ecId, int id, FileTransfer *plugin): - _manager(manager), - _scId(scId), - _ecId(ecId), - _id(id), - _plugin(plugin) { - } - - void download(const QString& url, const QString &targetURI, const QVariantMap &headers); - void upload(const QString& _url, const QString& fileURI, QString fileKey, QString fileName, QString mimeType, const QVariantMap ¶ms, const QVariantMap &headers); - void abort(); - -signals: - void done(); - -private slots: - void progress(qint64 bytesReceived, qint64 bytesTotal); - void error(QNetworkReply::NetworkError code); -private: - FileTransfer *_plugin; - Q_DISABLE_COPY(FileTransferRequest); -}; - -class FileTransfer : public CPlugin { - Q_OBJECT -public: - explicit FileTransfer(Cordova *cordova): CPlugin(cordova) { - } - - Cordova* cordova() { - return m_cordova; - } - - virtual const QString fullName() override { - return FileTransfer::fullID(); - } - - virtual const QString shortName() override { - return "FileTransfer"; - } - - static const QString fullID() { - return "FileTransfer"; - } - -public slots: - void abort(int scId, int ecId, int id); - void download(int scId, int ecId, const QString& url, const QString &target, bool /*trustAllHost*/, int id, const QVariantMap &/*headers*/); - void upload(int scId, int ecId, const QString &filePath, const QString& url, const QString& fileKey, const QString& fileName, const QString& mimeType, - const QVariantMap & params, bool /*trustAllHosts*/, bool /*chunkedMode*/, const QVariantMap &headers, int id, const QString &httpMethod); - -private: - QNetworkAccessManager _manager; - QMultiMap > _id2request; - int lastRequestId; -}; - -#endif diff --git a/src/wp/FileTransfer.cs b/src/wp/FileTransfer.cs deleted file mode 100644 index 4be46e8..0000000 --- a/src/wp/FileTransfer.cs +++ /dev/null @@ -1,994 +0,0 @@ -/* - 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. -*/ - -using Microsoft.Phone.Controls; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.IsolatedStorage; -using System.Linq; -using System.Net; -using System.Runtime.Serialization; -using System.Windows; -using System.Security; -using System.Diagnostics; -using System.Threading.Tasks; -using WPCordovaClassLib.Cordova.JSON; -using System.Reflection; - -namespace WPCordovaClassLib.Cordova.Commands -{ - public class FileTransfer : BaseCommand - { - public class DownloadRequestState - { - // This class stores the State of the request. - public HttpWebRequest request; - public TransferOptions options; - public bool isCancelled; - - public DownloadRequestState() - { - request = null; - options = null; - isCancelled = false; - } - } - - public class TransferOptions - { - /// File path to upload OR File path to download to - public string FilePath { get; set; } - - public string Url { get; set; } - /// Flag to recognize if we should trust every host (only in debug environments) - public bool TrustAllHosts { get; set; } - public string Id { get; set; } - public string Headers { get; set; } - public string CallbackId { get; set; } - public bool ChunkedMode { get; set; } - /// Server address - public string Server { get; set; } - /// File key - public string FileKey { get; set; } - /// File name on the server - public string FileName { get; set; } - /// File Mime type - public string MimeType { get; set; } - /// Additional options - public string Params { get; set; } - public string Method { get; set; } - - public TransferOptions() - { - FileKey = "file"; - FileName = "image.jpg"; - MimeType = "image/jpeg"; - } - } - - /// - /// Boundary symbol - /// - private string Boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x"); - - // Error codes - public const int FileNotFoundError = 1; - public const int InvalidUrlError = 2; - public const int ConnectionError = 3; - public const int AbortError = 4; // not really an error, but whatevs - - private static Dictionary InProcDownloads = new Dictionary(); - - // Private instance of the main WebBrowser instance - // NOTE: Any access to this object needs to occur on the UI thread via the Dispatcher - private WebBrowser browser; - - - - /// - /// Uploading response info - /// - [DataContract] - public class FileUploadResult - { - /// - /// Amount of sent bytes - /// - [DataMember(Name = "bytesSent")] - public long BytesSent { get; set; } - - /// - /// Server response code - /// - [DataMember(Name = "responseCode")] - public long ResponseCode { get; set; } - - /// - /// Server response - /// - [DataMember(Name = "response", EmitDefaultValue = false)] - public string Response { get; set; } - - /// - /// Creates FileUploadResult object with response values - /// - /// Amount of sent bytes - /// Server response code - /// Server response - public FileUploadResult(long bytesSent, long responseCode, string response) - { - this.BytesSent = bytesSent; - this.ResponseCode = responseCode; - this.Response = response; - } - } - /// - /// Represents transfer error codes for callback - /// - [DataContract] - public class FileTransferError - { - /// - /// Error code - /// - [DataMember(Name = "code", IsRequired = true)] - public int Code { get; set; } - - /// - /// The source URI - /// - [DataMember(Name = "source", IsRequired = true)] - public string Source { get; set; } - - /// - /// The target URI - /// - /// - [DataMember(Name = "target", IsRequired = true)] - public string Target { get; set; } - - [DataMember(Name = "body", IsRequired = true)] - public string Body { get; set; } - - /// - /// The http status code response from the remote URI - /// - [DataMember(Name = "http_status", IsRequired = true)] - public int HttpStatus { get; set; } - - /// - /// Creates FileTransferError object - /// - /// Error code - public FileTransferError(int errorCode) - { - this.Code = errorCode; - this.Source = null; - this.Target = null; - this.HttpStatus = 0; - this.Body = ""; - } - public FileTransferError(int errorCode, string source, string target, int status, string body = "") - { - this.Code = errorCode; - this.Source = source; - this.Target = target; - this.HttpStatus = status; - this.Body = body; - } - } - - /// - /// Represents a singular progress event to be passed back to javascript - /// - [DataContract] - public class FileTransferProgress - { - /// - /// Is the length of the response known? - /// - [DataMember(Name = "lengthComputable", IsRequired = true)] - public bool LengthComputable { get; set; } - /// - /// amount of bytes loaded - /// - [DataMember(Name = "loaded", IsRequired = true)] - public long BytesLoaded { get; set; } - /// - /// Total bytes - /// - [DataMember(Name = "total", IsRequired = false)] - public long BytesTotal { get; set; } - - public FileTransferProgress(long bTotal = 0, long bLoaded = 0) - { - LengthComputable = bTotal > 0; - BytesLoaded = bLoaded; - BytesTotal = bTotal; - } - } - - /// - /// Represents a request header passed from Javascript to upload/download operations - /// - [DataContract] - protected struct Header - { - [DataMember(Name = "name")] - public string Name; - - [DataMember(Name = "value")] - public string Value; - } - - private static MethodInfo JsonDeserializeUsingJsonNet; - - public FileTransfer() - { - if (JsonDeserializeUsingJsonNet == null) - { - var method = typeof(JsonHelper).GetMethod("Deserialize", new Type[] { typeof(string), typeof(bool) }); - if (method != null) - { - JsonDeserializeUsingJsonNet = method.MakeGenericMethod(new Type[] { typeof(Header[]) }); - } - } - } - - /// Helper method to copy all relevant cookies from the WebBrowser control into a header on - /// the HttpWebRequest - /// - /// The source browser to copy the cookies from - /// The destination HttpWebRequest to add the cookie header to - /// Nothing - private async Task CopyCookiesFromWebBrowser(HttpWebRequest webRequest) - { - var tcs = new TaskCompletionSource(); - - // Accessing WebBrowser needs to happen on the UI thread - Deployment.Current.Dispatcher.BeginInvoke(() => - { - // Get the WebBrowser control - if (this.browser == null) - { - PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; - if (frame != null) - { - PhoneApplicationPage page = frame.Content as PhoneApplicationPage; - if (page != null) - { - CordovaView cView = page.FindName("CordovaView") as CordovaView; - if (cView != null) - { - this.browser = cView.Browser; - } - } - } - } - - try - { - // Only copy the cookies if the scheme and host match (to avoid any issues with secure/insecure cookies) - // NOTE: since the returned CookieCollection appears to munge the original cookie's domain value in favor of the actual Source domain, - // we can't know for sure whether the cookies would be applicable to any other hosts, so best to play it safe and skip for now. - if (this.browser != null && this.browser.Source.IsAbsoluteUri == true && - this.browser.Source.Scheme == webRequest.RequestUri.Scheme && this.browser.Source.Host == webRequest.RequestUri.Host) - { - string cookieHeader = ""; - string requestPath = webRequest.RequestUri.PathAndQuery; - CookieCollection cookies = this.browser.GetCookies(); - - // Iterate over the cookies and add to the header - foreach (Cookie cookie in cookies) - { - // Check that the path is allowed, first - // NOTE: Path always seems to be empty for now, even if the cookie has a path set by the server. - if (cookie.Path.Length == 0 || requestPath.IndexOf(cookie.Path, StringComparison.InvariantCultureIgnoreCase) == 0) - { - cookieHeader += cookie.Name + "=" + cookie.Value + "; "; - } - } - - // Finally, set the header if we found any cookies - if (cookieHeader.Length > 0) - { - webRequest.Headers["Cookie"] = cookieHeader; - } - } - } - catch (Exception) - { - // Swallow the exception - } - - // Complete the task - tcs.SetResult(Type.Missing); - }); - - await tcs.Task; - } - - /// - /// Upload options - /// - //private TransferOptions uploadOptions; - - /// - /// Bytes sent - /// - private long bytesSent; - - /// - /// sends a file to a server - /// - /// Upload options - /// exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); - public void upload(string options) - { - options = options.Replace("{}", ""); // empty objects screw up the Deserializer - string callbackId = ""; - - TransferOptions uploadOptions = null; - HttpWebRequest webRequest = null; - - try - { - try - { - string[] args = JSON.JsonHelper.Deserialize(options); - uploadOptions = new TransferOptions(); - uploadOptions.FilePath = args[0]; - uploadOptions.Server = args[1]; - uploadOptions.FileKey = args[2]; - uploadOptions.FileName = args[3]; - uploadOptions.MimeType = args[4]; - uploadOptions.Params = args[5]; - - bool trustAll = false; - bool.TryParse(args[6],out trustAll); - uploadOptions.TrustAllHosts = trustAll; - - bool doChunked = false; - bool.TryParse(args[7], out doChunked); - uploadOptions.ChunkedMode = doChunked; - - //8 : Headers - //9 : id - //10: method - - uploadOptions.Headers = args[8]; - uploadOptions.Id = args[9]; - uploadOptions.Method = args[10]; - - uploadOptions.CallbackId = callbackId = args[11]; - } - catch (Exception) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - return; - } - - Uri serverUri; - try - { - serverUri = new Uri(uploadOptions.Server); - } - catch (Exception) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(InvalidUrlError, uploadOptions.Server, null, 0))); - return; - } - webRequest = (HttpWebRequest)WebRequest.Create(serverUri); - webRequest.ContentType = "multipart/form-data; boundary=" + Boundary; - webRequest.Method = uploadOptions.Method; - - DownloadRequestState reqState = new DownloadRequestState(); - InProcDownloads[uploadOptions.Id] = reqState; - reqState.options = uploadOptions; - reqState.request = webRequest; - - try - { - // Associate cookies with the request - // This is an async call, so we need to await it in order to preserve proper control flow - Task cookieTask = CopyCookiesFromWebBrowser(webRequest); - cookieTask.Wait(); - } - catch (AggregateException ae) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(FileTransfer.ConnectionError, uploadOptions.FilePath, uploadOptions.Server, 0, ae.InnerException.Message))); - return; - } - - if (!string.IsNullOrEmpty(uploadOptions.Headers)) - { - Dictionary headers = parseHeaders(uploadOptions.Headers); - if (headers != null) - { - foreach (string key in headers.Keys) - { - webRequest.Headers[key] = headers[key]; - } - } - } - - webRequest.BeginGetRequestStream(uploadCallback, reqState); - } - catch (Exception /*ex*/) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)),callbackId); - } - } - - // example : "{\"Authorization\":\"Basic Y29yZG92YV91c2VyOmNvcmRvdmFfcGFzc3dvcmQ=\"}" - protected Dictionary parseHeaders(string jsonHeaders) - { - try - { - if (FileTransfer.JsonDeserializeUsingJsonNet != null) - { - return ((Header[])FileTransfer.JsonDeserializeUsingJsonNet.Invoke(null, new object[] { jsonHeaders, true })) - .ToDictionary(header => header.Name, header => header.Value); - } - else - { - return JsonHelper.Deserialize(jsonHeaders) - .ToDictionary(header => header.Name, header => header.Value); - } - } - catch (Exception) - { - Debug.WriteLine("Failed to parseHeaders from string :: " + jsonHeaders); - } - return new Dictionary(); - } - - public void download(string options) - { - TransferOptions downloadOptions = null; - HttpWebRequest webRequest = null; - string callbackId; - - try - { - // source, target, trustAllHosts, this._id, headers - string[] optionStrings = JSON.JsonHelper.Deserialize(options); - - downloadOptions = new TransferOptions(); - downloadOptions.Url = optionStrings[0]; - downloadOptions.FilePath = optionStrings[1]; - - bool trustAll = false; - bool.TryParse(optionStrings[2],out trustAll); - downloadOptions.TrustAllHosts = trustAll; - - downloadOptions.Id = optionStrings[3]; - downloadOptions.Headers = optionStrings[4]; - downloadOptions.CallbackId = callbackId = optionStrings[5]; - } - catch (Exception) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); - return; - } - - try - { - // is the URL a local app file? - if (downloadOptions.Url.StartsWith("x-wmapp0") || downloadOptions.Url.StartsWith("file:")) - { - using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) - { - string cleanUrl = downloadOptions.Url.Replace("x-wmapp0:", "").Replace("file:", "").Replace("//",""); - - // pre-emptively create any directories in the FilePath that do not exist - string directoryName = getDirectoryName(downloadOptions.FilePath); - if (!string.IsNullOrEmpty(directoryName) && !isoFile.DirectoryExists(directoryName)) - { - isoFile.CreateDirectory(directoryName); - } - - // just copy from one area of iso-store to another ... - if (isoFile.FileExists(downloadOptions.Url)) - { - isoFile.CopyFile(downloadOptions.Url, downloadOptions.FilePath); - } - else - { - // need to unpack resource from the dll - Uri uri = new Uri(cleanUrl, UriKind.Relative); - var resource = Application.GetResourceStream(uri); - - if (resource != null) - { - // create the file destination - if (!isoFile.FileExists(downloadOptions.FilePath)) - { - var destFile = isoFile.CreateFile(downloadOptions.FilePath); - destFile.Close(); - } - - using (FileStream fileStream = new IsolatedStorageFileStream(downloadOptions.FilePath, FileMode.Open, FileAccess.Write, isoFile)) - { - long totalBytes = resource.Stream.Length; - int bytesRead = 0; - using (BinaryReader reader = new BinaryReader(resource.Stream)) - { - using (BinaryWriter writer = new BinaryWriter(fileStream)) - { - int BUFFER_SIZE = 1024; - byte[] buffer; - - while (true) - { - buffer = reader.ReadBytes(BUFFER_SIZE); - // fire a progress event ? - bytesRead += buffer.Length; - if (buffer.Length > 0) - { - writer.Write(buffer); - DispatchFileTransferProgress(bytesRead, totalBytes, callbackId); - } - else - { - writer.Close(); - reader.Close(); - fileStream.Close(); - break; - } - } - } - } - } - } - } - } - - File.FileEntry entry = File.FileEntry.GetEntry(downloadOptions.FilePath); - if (entry != null) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); - } - else - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, File.NOT_FOUND_ERR), callbackId); - } - - return; - } - else - { - // otherwise it is web-bound, we will actually download it - //Debug.WriteLine("Creating WebRequest for url : " + downloadOptions.Url); - webRequest = (HttpWebRequest)WebRequest.Create(downloadOptions.Url); - } - } - catch (Exception /*ex*/) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(InvalidUrlError, downloadOptions.Url, null, 0))); - return; - } - - if (downloadOptions != null && webRequest != null) - { - DownloadRequestState state = new DownloadRequestState(); - state.options = downloadOptions; - state.request = webRequest; - InProcDownloads[downloadOptions.Id] = state; - - try - { - // Associate cookies with the request - // This is an async call, so we need to await it in order to preserve proper control flow - Task cookieTask = CopyCookiesFromWebBrowser(webRequest); - cookieTask.Wait(); - } - catch (AggregateException ae) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(FileTransfer.ConnectionError, downloadOptions.Url, downloadOptions.FilePath, 0, ae.InnerException.Message))); - return; - } - - if (!string.IsNullOrEmpty(downloadOptions.Headers)) - { - Dictionary headers = parseHeaders(downloadOptions.Headers); - foreach (string key in headers.Keys) - { - webRequest.Headers[key] = headers[key]; - } - } - - try - { - webRequest.BeginGetResponse(new AsyncCallback(downloadCallback), state); - } - catch (WebException) - { - // eat it - } - // dispatch an event for progress ( 0 ) - lock (state) - { - if (!state.isCancelled) - { - var plugRes = new PluginResult(PluginResult.Status.OK, new FileTransferProgress()); - plugRes.KeepCallback = true; - plugRes.CallbackId = callbackId; - DispatchCommandResult(plugRes, callbackId); - } - } - } - } - - public void abort(string options) - { - Debug.WriteLine("Abort :: " + options); - string[] optionStrings = JSON.JsonHelper.Deserialize(options); - string id = optionStrings[0]; - string callbackId = optionStrings[1]; - - if (id != null && InProcDownloads.ContainsKey(id)) - { - DownloadRequestState state = InProcDownloads[id]; - if (!state.isCancelled) - { // prevent multiple callbacks for the same abort - state.isCancelled = true; - if (!state.request.HaveResponse) - { - state.request.Abort(); - InProcDownloads.Remove(id); - //callbackId = state.options.CallbackId; - //state = null; - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(FileTransfer.AbortError)), - state.options.CallbackId); - } - } - } - else - { - DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION), callbackId); // TODO: is it an IO exception? - } - } - - private void DispatchFileTransferProgress(long bytesLoaded, long bytesTotal, string callbackId, bool keepCallback = true) - { - Debug.WriteLine("DispatchFileTransferProgress : " + callbackId); - // send a progress change event - FileTransferProgress progEvent = new FileTransferProgress(bytesTotal); - progEvent.BytesLoaded = bytesLoaded; - PluginResult plugRes = new PluginResult(PluginResult.Status.OK, progEvent); - plugRes.KeepCallback = keepCallback; - plugRes.CallbackId = callbackId; - DispatchCommandResult(plugRes, callbackId); - } - - /// - /// - /// - /// - private void downloadCallback(IAsyncResult asynchronousResult) - { - DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; - HttpWebRequest request = reqState.request; - - string callbackId = reqState.options.CallbackId; - try - { - HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult); - - // send a progress change event - DispatchFileTransferProgress(0, response.ContentLength, callbackId); - - using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) - { - // create any directories in the path that do not exist - string directoryName = getDirectoryName(reqState.options.FilePath); - if (!string.IsNullOrEmpty(directoryName) && !isoFile.DirectoryExists(directoryName)) - { - isoFile.CreateDirectory(directoryName); - } - - // create the file if not exists - if (!isoFile.FileExists(reqState.options.FilePath)) - { - var file = isoFile.CreateFile(reqState.options.FilePath); - file.Close(); - } - - using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, FileAccess.Write, isoFile)) - { - long totalBytes = response.ContentLength; - int bytesRead = 0; - using (BinaryReader reader = new BinaryReader(response.GetResponseStream())) - { - using (BinaryWriter writer = new BinaryWriter(fileStream)) - { - int BUFFER_SIZE = 1024; - byte[] buffer; - - while (true) - { - buffer = reader.ReadBytes(BUFFER_SIZE); - // fire a progress event ? - bytesRead += buffer.Length; - if (buffer.Length > 0 && !reqState.isCancelled) - { - writer.Write(buffer); - DispatchFileTransferProgress(bytesRead, totalBytes, callbackId); - } - else - { - writer.Close(); - reader.Close(); - fileStream.Close(); - break; - } - System.Threading.Thread.Sleep(1); - } - } - } - } - if (reqState.isCancelled) - { - isoFile.DeleteFile(reqState.options.FilePath); - } - } - - if (reqState.isCancelled) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(AbortError)), - callbackId); - } - else - { - File.FileEntry entry = new File.FileEntry(reqState.options.FilePath); - DispatchCommandResult(new PluginResult(PluginResult.Status.OK, entry), callbackId); - } - } - catch (IsolatedStorageException) - { - // Trying to write the file somewhere within the IsoStorage. - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)), - callbackId); - } - catch (SecurityException) - { - // Trying to write the file somewhere not allowed. - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError)), - callbackId); - } - catch (WebException webex) - { - // TODO: probably need better work here to properly respond with all http status codes back to JS - // Right now am jumping through hoops just to detect 404. - HttpWebResponse response = (HttpWebResponse)webex.Response; - if ((webex.Status == WebExceptionStatus.ProtocolError && response.StatusCode == HttpStatusCode.NotFound) - || webex.Status == WebExceptionStatus.UnknownError) - { - // Weird MSFT detection of 404... seriously... just give us the f(*&#$@ status code as a number ffs!!! - // "Numbers for HTTP status codes? Nah.... let's create our own set of enums/structs to abstract that stuff away." - // FACEPALM - // Or just cast it to an int, whiner ... -jm - int statusCode = (int)response.StatusCode; - string body = ""; - - using (Stream streamResponse = response.GetResponseStream()) - { - using (StreamReader streamReader = new StreamReader(streamResponse)) - { - body = streamReader.ReadToEnd(); - } - } - FileTransferError ftError = new FileTransferError(ConnectionError, null, null, statusCode, body); - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ftError), - callbackId); - } - else - { - lock (reqState) - { - if (!reqState.isCancelled) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(ConnectionError)), - callbackId); - } - else - { - Debug.WriteLine("It happened"); - } - } - } - } - catch (Exception) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(FileNotFoundError)), - callbackId); - } - - //System.Threading.Thread.Sleep(1000); - if (InProcDownloads.ContainsKey(reqState.options.Id)) - { - InProcDownloads.Remove(reqState.options.Id); - } - } - - /// - /// Read file from Isolated Storage and sends it to server - /// - /// - private void uploadCallback(IAsyncResult asynchronousResult) - { - DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; - HttpWebRequest webRequest = reqState.request; - string callbackId = reqState.options.CallbackId; - - try - { - using (Stream requestStream = (webRequest.EndGetRequestStream(asynchronousResult))) - { - string lineStart = "--"; - string lineEnd = Environment.NewLine; - byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes(lineStart + Boundary + lineEnd); - string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"" + lineEnd + lineEnd + "{1}" + lineEnd; - - if (!string.IsNullOrEmpty(reqState.options.Params)) - { - Dictionary paramMap = parseHeaders(reqState.options.Params); - foreach (string key in paramMap.Keys) - { - requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); - string formItem = string.Format(formdataTemplate, key, paramMap[key]); - byte[] formItemBytes = System.Text.Encoding.UTF8.GetBytes(formItem); - requestStream.Write(formItemBytes, 0, formItemBytes.Length); - } - } - using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication()) - { - if (!isoFile.FileExists(reqState.options.FilePath)) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(FileNotFoundError, reqState.options.Server, reqState.options.FilePath, 0))); - return; - } - - byte[] endRequest = System.Text.Encoding.UTF8.GetBytes(lineEnd + lineStart + Boundary + lineStart + lineEnd); - long totalBytesToSend = 0; - - using (FileStream fileStream = new IsolatedStorageFileStream(reqState.options.FilePath, FileMode.Open, isoFile)) - { - string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + lineEnd + "Content-Type: {2}" + lineEnd + lineEnd; - string header = string.Format(headerTemplate, reqState.options.FileKey, reqState.options.FileName, reqState.options.MimeType); - byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header); - - byte[] buffer = new byte[4096]; - int bytesRead = 0; - //sent bytes needs to be reseted before new upload - bytesSent = 0; - totalBytesToSend = fileStream.Length; - - requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); - - requestStream.Write(headerBytes, 0, headerBytes.Length); - - while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) - { - if (!reqState.isCancelled) - { - requestStream.Write(buffer, 0, bytesRead); - bytesSent += bytesRead; - DispatchFileTransferProgress(bytesSent, totalBytesToSend, callbackId); - System.Threading.Thread.Sleep(1); - } - else - { - throw new Exception("UploadCancelledException"); - } - } - } - - requestStream.Write(endRequest, 0, endRequest.Length); - } - } - // webRequest - - webRequest.BeginGetResponse(ReadCallback, reqState); - } - catch (Exception /*ex*/) - { - if (!reqState.isCancelled) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, new FileTransferError(ConnectionError)), callbackId); - } - } - } - - /// - /// Reads response into FileUploadResult - /// - /// - private void ReadCallback(IAsyncResult asynchronousResult) - { - DownloadRequestState reqState = (DownloadRequestState)asynchronousResult.AsyncState; - try - { - HttpWebRequest webRequest = reqState.request; - string callbackId = reqState.options.CallbackId; - - if (InProcDownloads.ContainsKey(reqState.options.Id)) - { - InProcDownloads.Remove(reqState.options.Id); - } - - using (HttpWebResponse response = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult)) - { - using (Stream streamResponse = response.GetResponseStream()) - { - using (StreamReader streamReader = new StreamReader(streamResponse)) - { - string responseString = streamReader.ReadToEnd(); - Deployment.Current.Dispatcher.BeginInvoke(() => - { - DispatchCommandResult(new PluginResult(PluginResult.Status.OK, new FileUploadResult(bytesSent, (long)response.StatusCode, responseString))); - }); - } - } - } - } - catch (WebException webex) - { - // TODO: probably need better work here to properly respond with all http status codes back to JS - // Right now am jumping through hoops just to detect 404. - if ((webex.Status == WebExceptionStatus.ProtocolError && ((HttpWebResponse)webex.Response).StatusCode == HttpStatusCode.NotFound) - || webex.Status == WebExceptionStatus.UnknownError) - { - int statusCode = (int)((HttpWebResponse)webex.Response).StatusCode; - FileTransferError ftError = new FileTransferError(ConnectionError, null, null, statusCode); - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ftError), reqState.options.CallbackId); - } - else - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, - new FileTransferError(ConnectionError)), - reqState.options.CallbackId); - } - } - catch (Exception /*ex*/) - { - FileTransferError transferError = new FileTransferError(ConnectionError, reqState.options.Server, reqState.options.FilePath, 403); - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, transferError), reqState.options.CallbackId); - } - } - - // Gets the full path without the filename - private string getDirectoryName(String filePath) - { - string directoryName; - try - { - directoryName = filePath.Substring(0, filePath.LastIndexOf('/')); - } - catch - { - directoryName = ""; - } - return directoryName; - } - } -} diff --git a/tests/tests.js b/tests/tests.js index c8d9ef3..63c9c1c 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -53,10 +53,9 @@ exports.defineAutoTests = function () { var SERVER_WITH_CREDENTIALS = ""; // flags - var isWindows = cordova.platformId === "windows8" || cordova.platformId === "windows"; - var isWindowsPhone81 = isWindows && WinJS.Utilities.isPhone; - var isWP8 = cordova.platformId === "windowsphone"; + var isWindows = cordova.platformId === "windows"; var isBrowser = cordova.platformId === "browser"; + var isWindowsPhone = isWindows && WinJS.Utilities.isPhone; var isIE = isBrowser && navigator.userAgent.indexOf("Trident") >= 0; var isIos = cordova.platformId === "ios"; var isIot = cordova.platformId === "android" && navigator.userAgent.indexOf("iot") >= 0; @@ -177,10 +176,6 @@ exports.defineAutoTests = function () { ); }; - // according to documentation, wp8 does not support onProgress: - // https://github.com/apache/cordova-plugin-file-transfer/blob/master/doc/index.md#supported-platforms - var wp8OnProgressHandler = function () {}; - var defaultOnProgressHandler = function (event) { if (event.lengthComputable) { expect(event.loaded).toBeGreaterThan(1); @@ -199,7 +194,7 @@ exports.defineAutoTests = function () { }; var getMalformedUrl = function () { - if (cordova.platformId === "android" || cordova.platformId === "amazon-fireos") { + if (cordova.platformId === "android") { // bad protocol causes a MalformedUrlException on Android return "httpssss://example.com"; } else { @@ -330,7 +325,7 @@ exports.defineAutoTests = function () { this.transfer = new FileTransfer(); // assign onprogress handler - this.transfer.onprogress = isWP8 ? wp8OnProgressHandler : defaultOnProgressHandler; + this.transfer.onprogress = defaultOnProgressHandler; // spy on the onprogress handler, but still call through to it spyOn(this.transfer, "onprogress").and.callThrough(); @@ -491,10 +486,7 @@ exports.defineAutoTests = function () { var fileURL = window.location.protocol + "//" + window.location.pathname.replace(/ /g, "%20"); var specContext = this; - if (!/^file:/.exec(fileURL) && cordova.platformId !== "blackberry10") { - if (cordova.platformId === "windowsphone") { - expect(fileURL).toMatch(/^x-wmapp0:/); - } + if (!/^file:/.exec(fileURL)) { done(); return; } @@ -645,12 +637,7 @@ exports.defineAutoTests = function () { expect(error.http_status).not.toBe(401, "Ensure " + fileURL + " is in the white list"); expect(error.http_status).toBe(404); - // wp8 does not make difference between 404 and unknown host - if (isWP8) { - expect(error.code).toBe(FileTransferError.CONNECTION_ERR); - } else { - expect(error.code).toBe(FileTransferError.FILE_NOT_FOUND_ERR); - } + expect(error.code).toBe(FileTransferError.FILE_NOT_FOUND_ERR); done(); }; @@ -738,13 +725,6 @@ exports.defineAutoTests = function () { }); it("filetransfer.spec.17 progress should work with gzip encoding", function (done) { - - // lengthComputable false on bb10 when downloading gzip - if (cordova.platformId === "blackberry10") { - pending(); - return; - } - var fileURL = "http://www.apache.org/"; var specContext = this; @@ -781,8 +761,6 @@ exports.defineAutoTests = function () { if (isWindows) { expect(nativeURL.substring(0, 14)).toBe("ms-appdata:///"); - } else if (isWP8) { - expect(nativeURL.substring(0, 1)).toBe("/"); } else { expect(nativeURL.substring(0, 7)).toBe("file://"); } @@ -834,11 +812,6 @@ exports.defineAutoTests = function () { }); it("filetransfer.spec.33 should properly handle 304", function (done) { - if (isWP8) { - pending(); - return; - } - var downloadFail = function (error) { expect(error.http_status).toBe(304); expect(error.code).toBe(FileTransferError.NOT_MODIFIED_ERR); @@ -854,11 +827,6 @@ exports.defineAutoTests = function () { }, DOWNLOAD_TIMEOUT); it("filetransfer.spec.35 304 should not result in the deletion of a cached file", function (done) { - if (isWP8) { - pending(); - return; - } - var specContext = this; var fileOperationFail = function() { @@ -1103,7 +1071,7 @@ exports.defineAutoTests = function () { // windows store and ios are too fast, win is called before we have a chance to abort // so let's get them busy - while not providing an extra load to the slow Android emulators - var arrayLength = ((isWindows && !isWindowsPhone81) || isIos) ? 3000000 : isIot ? 150000 : 200000; + var arrayLength = ((isWindows && !isWindowsPhone) || isIos) ? 3000000 : isIot ? 150000 : 200000; writeFile(specContext.root, specContext.fileName, new Array(arrayLength).join("aborttest!"), fileWin, done); }, UPLOAD_TIMEOUT); diff --git a/www/blackberry10/.jshintrc b/www/blackberry10/.jshintrc deleted file mode 100644 index 85ccb32..0000000 --- a/www/blackberry10/.jshintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "globals": { - "requestAnimationFrame": true - } -} diff --git a/www/blackberry10/FileTransfer.js b/www/blackberry10/FileTransfer.js deleted file mode 100644 index 76e1682..0000000 --- a/www/blackberry10/FileTransfer.js +++ /dev/null @@ -1,190 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * -*/ - -var argscheck = require('cordova/argscheck'), - FileTransferError = require('./FileTransferError'), - xhrImpl = require('./BB10XHRImplementation'); - - -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; - - // 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); - } - - if (credentials) { - var authHeader = "Authorization"; - var authHeaderValue = "Basic " + window.btoa(credentials); - - header = { - name : authHeader, - value : authHeaderValue - }; - } - } - - return header; -} - -var idCounter = 0; - -/** - * FileTransfer uploads a file to a remote server. - * @constructor - */ -var FileTransfer = function() { - this._id = ++idCounter; - this.onprogress = null; // optional callback -}; - -/** -* Given an absolute file path, uploads a file on the device to a remote server -* using a multipart HTTP request. -* @param filePath {String} Full path of the file on the device -* @param server {String} URL of the server to receive the file -* @param successCallback (Function} Callback to be invoked when upload has completed -* @param errorCallback {Function} Callback to be invoked upon error -* @param options {FileUploadOptions} Optional parameters such as file name and mimetype -* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false -*/ -FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { - argscheck.checkArgs('ssFFO*', 'FileTransfer.upload', arguments); - // check for options - var fileKey = null; - var fileName = null; - var mimeType = null; - var params = null; - var chunkedMode = true; - var headers = null; - var httpMethod = null; - var basicAuthHeader = getBasicAuthHeader(server); - if (basicAuthHeader) { - options = options || {}; - options.headers = options.headers || {}; - options.headers[basicAuthHeader.name] = basicAuthHeader.value; - } - - if (options) { - fileKey = options.fileKey; - fileName = options.fileName; - mimeType = options.mimeType; - headers = options.headers; - httpMethod = options.httpMethod || "POST"; - if (httpMethod.toUpperCase() == "PUT"){ - httpMethod = "PUT"; - } else { - httpMethod = "POST"; - } - if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { - chunkedMode = options.chunkedMode; - } - if (options.params) { - params = options.params; - } - else { - params = {}; - } - } - - var fail = errorCallback && function(e) { - var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); - errorCallback(error); - }; - - var self = this; - var win = function(result) { - if (typeof result.lengthComputable != "undefined") { - if (self.onprogress) { - self.onprogress(result); - } - } else { - if (successCallback) { - successCallback(result); - } - } - }; - xhrImpl.upload(win, fail, [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); -}; - -/** - * Downloads a file form a given URL and saves it to the specified directory. - * @param source {String} URL of the server to receive the file - * @param target {String} Full path of the file on the device - * @param successCallback (Function} Callback to be invoked when upload has completed - * @param errorCallback {Function} Callback to be invoked upon error - * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false - * @param options {FileDownloadOptions} Optional parameters such as headers - */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) { - argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments); - var self = this; - - var basicAuthHeader = getBasicAuthHeader(source); - if (basicAuthHeader) { - options = options || {}; - options.headers = options.headers || {}; - options.headers[basicAuthHeader.name] = basicAuthHeader.value; - } - - var headers = null; - if (options) { - headers = options.headers || null; - } - - var win = function(result) { - if (typeof result.lengthComputable != "undefined") { - if (self.onprogress) { - return self.onprogress(result); - } - } else if (successCallback) { - successCallback(result); - } - }; - - var fail = errorCallback && function(e) { - var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body); - errorCallback(error); - }; - - xhrImpl.download(win, fail, [source, target, trustAllHosts, this._id, headers]); -}; - -/** - * Aborts the ongoing file transfer on this object. The original error - * callback for the file transfer will be called if necessary. - */ -FileTransfer.prototype.abort = function() { - xhrImpl.abort(null, null, [this._id]); -}; - -module.exports = FileTransfer; diff --git a/www/blackberry10/FileTransferProxy.js b/www/blackberry10/FileTransferProxy.js deleted file mode 100644 index a363f07..0000000 --- a/www/blackberry10/FileTransferProxy.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * -*/ - -/* - * FileTransferProxy - * - * Register all FileTransfer exec calls to be handled by proxy - */ - -var xhrFileTransfer = require('cordova-plugin-file-transfer.xhrFileTransfer'); - -module.exports = { - abort: xhrFileTransfer.abort, - download: xhrFileTransfer.download, - upload: xhrFileTransfer.upload -}; - -require('cordova/exec/proxy').add('FileTransfer', module.exports); diff --git a/www/blackberry10/xhrFileTransfer.js b/www/blackberry10/xhrFileTransfer.js deleted file mode 100644 index 53d0d36..0000000 --- a/www/blackberry10/xhrFileTransfer.js +++ /dev/null @@ -1,260 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * -*/ - -/* global Blob:false */ - -var cordova = require('cordova'), - resolve = cordova.require('cordova-plugin-file.resolveLocalFileSystemURIProxy'), - requestAnimationFrame = cordova.require('cordova-plugin-file.bb10RequestAnimationFrame'), - xhr = {}; - -function getParentPath(filePath) { - var pos = filePath.lastIndexOf('/'); - return filePath.substring(0, pos + 1); -} - -function getFileName(filePath) { - var pos = filePath.lastIndexOf('/'); - return filePath.substring(pos + 1); -} - -function checkURL(url) { - return url.indexOf(' ') === -1 ? true : false; -} - -module.exports = { - abort: function (win, fail, args) { - var id = args[0]; - if (xhr[id]) { - xhr[id].abort(); - if (typeof(win) === 'function') { - win(); - } - } else if (typeof(fail) === 'function') { - fail(); - } - }, - - upload: function(win, fail, args) { - var filePath = args[0], - server = args[1], - fileKey = args[2], - fileName = args[3], - mimeType = args[4], - params = args[5], - /*trustAllHosts = args[6],*/ - chunkedMode = args[7], - headers = args[8], - onSuccess = function (data) { - if (typeof(win) === 'function') { - win(data); - } - }, - onFail = function (code) { - delete xhr[fileKey]; - if (typeof(fail) === 'function') { - fail(code); - } - }; - - if (!checkURL(server)) { - onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, server, filePath)); - } - - xhr[fileKey] = new XMLHttpRequest(); - xhr[fileKey].onabort = function () { - onFail(new FileTransferError(FileTransferError.ABORT_ERR, server, filePath, this.status, xhr[fileKey].response)); - }; - - resolve(function(entry) { - requestAnimationFrame(function () { - entry.nativeEntry.file(function(file) { - function uploadFile(blobFile) { - var fd = new FormData(); - - fd.append(fileKey, blobFile, fileName); - for (var prop in params) { - if(params.hasOwnProperty(prop)) { - fd.append(prop, params[prop]); - } - } - - xhr[fileKey].open("POST", server); - xhr[fileKey].onload = function(evt) { - if (xhr[fileKey].status === 200) { - var result = new FileUploadResult(); - result.bytesSent = file.size; - result.responseCode = xhr[fileKey].status; - result.response = xhr[fileKey].response; - delete xhr[fileKey]; - onSuccess(result); - } else if (xhr[fileKey].status === 404) { - onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response)); - } else { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response)); - } - }; - xhr[fileKey].ontimeout = function(evt) { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response)); - }; - xhr[fileKey].onerror = function () { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, this.status, xhr[fileKey].response)); - }; - xhr[fileKey].upload.onprogress = function (evt) { - if (evt.loaded > 0) { - onSuccess(evt); - } - }; - - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr[fileKey].setRequestHeader(header, headers[header]); - } - } - - requestAnimationFrame(function () { - xhr[fileKey].send(fd); - }); - } - - var bytesPerChunk; - if (chunkedMode === true) { - bytesPerChunk = 1024 * 1024; // 1MB chunk sizes. - } else { - bytesPerChunk = file.size; - } - var start = 0; - var end = bytesPerChunk; - while (start < file.size) { - var chunk = file.slice(start, end, mimeType); - uploadFile(chunk); - start = end; - end = start + bytesPerChunk; - } - }, function(error) { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath)); - }); - }); - }, function(error) { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath)); - }, [filePath]); - }, - - download: function (win, fail, args) { - var source = args[0], - target = args[1], - id = args[3], - headers = args[4], - fileWriter, - onSuccess = function (entry) { - if (typeof(win) === 'function') { - win(entry); - } - }, - onFail = function (error) { - var reader; - delete xhr[id]; - if (typeof(fail) === 'function') { - if (error && error.body && typeof(error.body) === 'object') { - reader = new FileReader()._realReader; - reader.onloadend = function () { - error.body = this.result; - fail(error); - }; - reader.onerror = function () { - fail(error); - }; - reader.readAsText(error.body); - } else { - fail(error); - } - } - }; - - if (!checkURL(source)) { - onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, source, target)); - } - - xhr[id] = new XMLHttpRequest(); - - function writeFile(entry) { - entry.createWriter(function (writer) { - fileWriter = writer; - fileWriter.onwriteend = function (evt) { - if (!evt.target.error) { - entry.filesystemName = entry.filesystem.name; - delete xhr[id]; - onSuccess(entry); - } else { - onFail(evt.target.error); - } - }; - fileWriter.onerror = function (evt) { - onFail(evt.target.error); - }; - fileWriter.write(new Blob([xhr[id].response])); - }, function (error) { - onFail(error); - }); - } - - xhr[id].onerror = function (e) { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response)); - }; - - xhr[id].onabort = function (e) { - onFail(new FileTransferError(FileTransferError.ABORT_ERR, source, target, xhr[id].status, xhr[id].response)); - }; - - xhr[id].onload = function () { - if (xhr[id].readyState === xhr[id].DONE) { - if (xhr[id].status === 200 && xhr[id].response) { - resolveLocalFileSystemURI(getParentPath(target), function (dir) { - dir.getFile(getFileName(target), {create: true}, writeFile, function (error) { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response)); - }); - }, function (error) { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response)); - }); - } else if (xhr[id].status === 404) { - onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, source, target, xhr[id].status, xhr[id].response)); - } else { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response)); - } - } - }; - xhr[id].onprogress = function (evt) { - onSuccess(evt); - }; - xhr[id].open("GET", source, true); - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr[id].setRequestHeader(header, headers[header]); - } - } - xhr[id].responseType = "blob"; - requestAnimationFrame(function () { - if (xhr[id]) { - xhr[id].send(); - } - }); - } -}; diff --git a/www/firefoxos/FileTransferProxy.js b/www/firefoxos/FileTransferProxy.js deleted file mode 100644 index 86c46be..0000000 --- a/www/firefoxos/FileTransferProxy.js +++ /dev/null @@ -1,222 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * -*/ - -var FileTransferError = require('./FileTransferError'), - xhr = {}; - -function getParentPath(filePath) { - var pos = filePath.lastIndexOf('/'); - return filePath.substring(0, pos + 1); -} - -function getFileName(filePath) { - var pos = filePath.lastIndexOf('/'); - return filePath.substring(pos + 1); -} - -module.exports = { - abort: function (successCallback, errorCallback, args) { - var id = args[0]; - if (xhr[id]) { - xhr[id].abort(); - if (typeof(successCallback) === 'function') { - successCallback(); - } - } else if (typeof(errorCallback) === 'function') { - errorCallback(); - } - }, - - upload: function(successCallback, errorCallback, args) { - var filePath = args[0], - server = args[1], - fileKey = args[2], - fileName = args[3], - mimeType = args[4], - params = args[5], - /*trustAllHosts = args[6],*/ - /*chunkedMode = args[7],*/ - headers = args[8]; - - xhr[fileKey] = new XMLHttpRequest({mozSystem: true}); - xhr[fileKey].onabort = function() { - onFail(new FileTransferError(FileTransferError.ABORT_ERR, server, filePath, this.status, xhr[fileKey].response)); - }; - - window.resolveLocalFileSystemURL(filePath, function(entry) { - entry.file(function(file) { - var reader = new FileReader(); - - reader.onloadend = function() { - var blob = new Blob([this.result], {type: mimeType}); - var fd = new FormData(); - - fd.append(fileKey, blob, fileName); - - for (var prop in params) { - if (params.hasOwnProperty(prop)) { - fd.append(prop, params[prop]); - } - } - - xhr[fileKey].open("POST", server); - - xhr[fileKey].onload = function(evt) { - if (xhr[fileKey].status === 200) { - var result = new FileUploadResult(); - result.bytesSent = blob.size; - result.responseCode = xhr[fileKey].status; - result.response = xhr[fileKey].response; - delete xhr[fileKey]; - onSuccess(result); - } else if (xhr[fileKey].status === 404) { - onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response)); - } else { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response)); - } - }; - - xhr[fileKey].ontimeout = function() { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, xhr[fileKey].status, xhr[fileKey].response)); - }; - - xhr[fileKey].onerror = function() { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, server, filePath, this.status, xhr[fileKey].response)); - }; - - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr[fileKey].setRequestHeader(header, headers[header]); - } - } - - xhr[fileKey].send(fd); - }; - - reader.readAsArrayBuffer(file); - - }, function() { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath)); - }); - }, function() { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, server, filePath)); - }); - - function onSuccess(data) { - if (typeof(successCallback) === 'function') { - successCallback(data); - } - } - - function onFail(code) { - delete xhr[fileKey]; - if (typeof(errorCallback) === 'function') { - errorCallback(code); - } - } - }, - - download: function (successCallback, errorCallback, args) { - var source = args[0], - target = args[1], - id = args[3], - headers = args[4]; - - xhr[id] = new XMLHttpRequest({mozSystem: true}); - - xhr[id].onload = function () { - if (xhr[id].readyState === xhr[id].DONE) { - if (xhr[id].status === 200 && xhr[id].response) { - window.resolveLocalFileSystemURL(getParentPath(target), function (dir) { - dir.getFile(getFileName(target), {create: true}, writeFile, function (error) { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response)); - }); - }, function () { - onFail(new FileTransferError(FileTransferError.FILE_NOT_FOUND_ERR, source, target, xhr[id].status, xhr[id].response)); - }); - } else if (xhr[id].status === 404) { - onFail(new FileTransferError(FileTransferError.INVALID_URL_ERR, source, target, xhr[id].status, xhr[id].response)); - } else { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response)); - } - } - }; - - function writeFile(entry) { - entry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function (evt) { - if (!evt.target.error) { - entry.filesystemName = entry.filesystem.name; - delete xhr[id]; - onSuccess(entry); - } else { - onFail(evt.target.error); - } - }; - fileWriter.onerror = function (evt) { - onFail(evt.target.error); - }; - fileWriter.write(new Blob([xhr[id].response])); - }, function (error) { - onFail(error); - }); - } - - xhr[id].onerror = function (e) { - onFail(new FileTransferError(FileTransferError.CONNECTION_ERR, source, target, xhr[id].status, xhr[id].response)); - }; - - xhr[id].onabort = function (e) { - onFail(new FileTransferError(FileTransferError.ABORT_ERR, source, target, xhr[id].status, xhr[id].response)); - }; - - xhr[id].open("GET", source, true); - - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr[id].setRequestHeader(header, headers[header]); - } - } - - xhr[id].responseType = "blob"; - - setTimeout(function () { - if (xhr[id]) { - xhr[id].send(); - } - }, 0); - - function onSuccess(entry) { - if (typeof(successCallback) === 'function') { - successCallback(entry); - } - } - - function onFail(error) { - delete xhr[id]; - if (typeof(errorCallback) === 'function') { - errorCallback(error); - } - } - } -}; - -require('cordova/exec/proxy').add('FileTransfer', module.exports); diff --git a/www/wp7/base64.js b/www/wp7/base64.js deleted file mode 100644 index 221e30a..0000000 --- a/www/wp7/base64.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * -*/ - -// jshint ignore: start - -var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', - INVALID_CHARACTER_ERR = (function () { - // fabricate a suitable error object - try { document.createElement('$'); } - catch (error) { return error; } - }()); - - // encoder - // [https://gist.github.com/999166] by [https://github.com/nignag] - window.btoa || ( - window.btoa = function (input) { - for ( - // initialize result and counter - var block, charCode, idx = 0, map = chars, output = ''; - // if the next input index does not exist: - // change the mapping table to "=" - // check if d has no fractional digits - input.charAt(idx | 0) || (map = '=', idx % 1) ; - // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 - output += map.charAt(63 & block >> 8 - idx % 1 * 8) - ) { - charCode = input.charCodeAt(idx += 3 / 4); - if (charCode > 0xFF) throw INVALID_CHARACTER_ERR; - block = block << 8 | charCode; - } - return output; - }); - - // decoder - // [https://gist.github.com/1020396] by [https://github.com/atk] - window.atob || ( - window.atob = function (input) { - input = input.replace(/=+$/, '') - if (input.length % 4 == 1) throw INVALID_CHARACTER_ERR; - for ( - // initialize result and counters - var bc = 0, bs, buffer, idx = 0, output = ''; - // get next character - buffer = input.charAt(idx++) ; - // character found in table? initialize bit storage and add its ascii value; - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - // and if not first of each 4 characters, - // convert the first 8 bits to one ascii character - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 - ) { - // try to find character in table (0-63, not found => -1) - buffer = chars.indexOf(buffer); - } - return output; - }); \ No newline at end of file