mirror of
https://github.com/apache/cordova-plugin-file-transfer.git
synced 2026-02-02 00:00:05 +08:00
fix!: remove deprecated platforms (#270)
This commit is contained in:
12
README.md
12
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
|
||||
|
||||
14
package.json
14
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",
|
||||
|
||||
85
plugin.xml
85
plugin.xml
@@ -56,45 +56,6 @@
|
||||
<source-file src="src/android/FileUploadResult.java" target-dir="src/org/apache/cordova/filetransfer" />
|
||||
</platform>
|
||||
|
||||
<!-- amamzon-fireos -->
|
||||
<platform name="amazon-fireos">
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="FileTransfer" >
|
||||
<param name="android-package" value="org.apache.cordova.filetransfer.FileTransfer"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<config-file target="AndroidManifest.xml" parent="/*">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/amazon/FileTransfer.java" target-dir="src/org/apache/cordova/filetransfer" />
|
||||
<source-file src="src/android/FileProgressResult.java" target-dir="src/org/apache/cordova/filetransfer" />
|
||||
<source-file src="src/android/FileUploadResult.java" target-dir="src/org/apache/cordova/filetransfer" />
|
||||
</platform>
|
||||
|
||||
<!-- ubuntu -->
|
||||
<platform name="ubuntu">
|
||||
<header-file src="src/ubuntu/file-transfer.h" />
|
||||
<source-file src="src/ubuntu/file-transfer.cpp" />
|
||||
</platform>
|
||||
|
||||
<!-- blackberry10 -->
|
||||
<platform name="blackberry10">
|
||||
<config-file target="www/config.xml" parent="/widget">
|
||||
<feature name="FileTransfer" value="FileTransfer"></feature>
|
||||
</config-file>
|
||||
<js-module src="www/blackberry10/FileTransferProxy.js" name="FileTransferProxy" >
|
||||
<runs />
|
||||
</js-module>
|
||||
<js-module src="www/blackberry10/xhrFileTransfer.js" name="xhrFileTransfer" />
|
||||
<!--
|
||||
<js-module src="www/blackberry10/abort.js" name="abortProxy" />
|
||||
<js-module src="www/blackberry10/download.js" name="downloadProxy" />
|
||||
<js-module src="www/blackberry10/upload.js" name="uploadProxy" />
|
||||
-->
|
||||
</platform>
|
||||
|
||||
<!-- ios -->
|
||||
<platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
@@ -108,41 +69,6 @@
|
||||
<framework src="AssetsLibrary.framework" />
|
||||
</platform>
|
||||
|
||||
<!-- wp7 -->
|
||||
<platform name="wp7">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="FileTransfer">
|
||||
<param name="wp-package" value="FileTransfer"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/wp/FileTransfer.cs" />
|
||||
|
||||
<js-module src="www/wp7/base64.js" name="base64">
|
||||
<clobbers target="window.FileTransferBase64" />
|
||||
</js-module>
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- wp8 -->
|
||||
<platform name="wp8">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="FileTransfer">
|
||||
<param name="wp-package" value="FileTransfer"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/wp/FileTransfer.cs" />
|
||||
|
||||
</platform>
|
||||
|
||||
<!-- windows8 -->
|
||||
<platform name="windows8">
|
||||
<js-module src="src/windows/FileTransferProxy.js" name="FileTransferProxy">
|
||||
<runs />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- windows -->
|
||||
<platform name="windows">
|
||||
<js-module src="src/windows/FileTransferProxy.js" name="FileTransferProxy">
|
||||
@@ -150,17 +76,6 @@
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- firefoxOS -->
|
||||
<platform name="firefoxos">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<permission name="systemXHR" privileged="true"/>
|
||||
</config-file>
|
||||
|
||||
<js-module src="www/firefoxos/FileTransferProxy.js" name="FileTransferProxy">
|
||||
<runs/>
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- browser -->
|
||||
<platform name="browser">
|
||||
<js-module src="www/browser/FileTransfer.js" name="BrowserFileTransfer">
|
||||
|
||||
@@ -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<String, RequestContext> activeRequests = new HashMap<String, RequestContext>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <plugins/cordova-plugin-file/file.h>
|
||||
#include <cassert>
|
||||
|
||||
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<QVariant> 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<FileTransferRequest> 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<FileTransferRequest> 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<CPlugin> filePlugin(_plugin->cordova()->getPlugin<File>());
|
||||
|
||||
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<QNetworkReply>(_manager.get(request));
|
||||
|
||||
_reply->connect(_reply.data(), &QNetworkReply::finished, [this, targetURI, uri, filePlugin]() {
|
||||
if (!_scId || _reply->error() != QNetworkReply::NoError)
|
||||
return;
|
||||
|
||||
QPair<bool, QFileInfo> f1(dynamic_cast<File*>(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<File*>(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<CPlugin> filePlugin(_plugin->cordova()->getPlugin<File>());
|
||||
|
||||
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<bool, QFileInfo> f1(dynamic_cast<File*>(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<QNetworkReply>(_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));
|
||||
}
|
||||
@@ -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 <QtCore>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include <cplugin.h>
|
||||
|
||||
class FileTransfer;
|
||||
|
||||
class FileTransferRequest: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
QNetworkAccessManager &_manager;
|
||||
int _scId, _ecId;
|
||||
int _id;
|
||||
QSharedPointer<QNetworkReply> _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<int, QSharedPointer<FileTransferRequest> > _id2request;
|
||||
int lastRequestId;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boundary symbol
|
||||
/// </summary>
|
||||
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<string, DownloadRequestState> InProcDownloads = new Dictionary<string,DownloadRequestState>();
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Uploading response info
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class FileUploadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of sent bytes
|
||||
/// </summary>
|
||||
[DataMember(Name = "bytesSent")]
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Server response code
|
||||
/// </summary>
|
||||
[DataMember(Name = "responseCode")]
|
||||
public long ResponseCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Server response
|
||||
/// </summary>
|
||||
[DataMember(Name = "response", EmitDefaultValue = false)]
|
||||
public string Response { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates FileUploadResult object with response values
|
||||
/// </summary>
|
||||
/// <param name="bytesSent">Amount of sent bytes</param>
|
||||
/// <param name="responseCode">Server response code</param>
|
||||
/// <param name="response">Server response</param>
|
||||
public FileUploadResult(long bytesSent, long responseCode, string response)
|
||||
{
|
||||
this.BytesSent = bytesSent;
|
||||
this.ResponseCode = responseCode;
|
||||
this.Response = response;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents transfer error codes for callback
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class FileTransferError
|
||||
{
|
||||
/// <summary>
|
||||
/// Error code
|
||||
/// </summary>
|
||||
[DataMember(Name = "code", IsRequired = true)]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The source URI
|
||||
/// </summary>
|
||||
[DataMember(Name = "source", IsRequired = true)]
|
||||
public string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target URI
|
||||
/// </summary>
|
||||
///
|
||||
[DataMember(Name = "target", IsRequired = true)]
|
||||
public string Target { get; set; }
|
||||
|
||||
[DataMember(Name = "body", IsRequired = true)]
|
||||
public string Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The http status code response from the remote URI
|
||||
/// </summary>
|
||||
[DataMember(Name = "http_status", IsRequired = true)]
|
||||
public int HttpStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates FileTransferError object
|
||||
/// </summary>
|
||||
/// <param name="errorCode">Error code</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a singular progress event to be passed back to javascript
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class FileTransferProgress
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the length of the response known?
|
||||
/// </summary>
|
||||
[DataMember(Name = "lengthComputable", IsRequired = true)]
|
||||
public bool LengthComputable { get; set; }
|
||||
/// <summary>
|
||||
/// amount of bytes loaded
|
||||
/// </summary>
|
||||
[DataMember(Name = "loaded", IsRequired = true)]
|
||||
public long BytesLoaded { get; set; }
|
||||
/// <summary>
|
||||
/// Total bytes
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request header passed from Javascript to upload/download operations
|
||||
/// </summary>
|
||||
[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
|
||||
/// </summary>
|
||||
/// <param name="browser">The source browser to copy the cookies from</param>
|
||||
/// <param name="webRequest">The destination HttpWebRequest to add the cookie header to</param>
|
||||
/// <returns>Nothing</returns>
|
||||
private async Task CopyCookiesFromWebBrowser(HttpWebRequest webRequest)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upload options
|
||||
/// </summary>
|
||||
//private TransferOptions uploadOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Bytes sent
|
||||
/// </summary>
|
||||
private long bytesSent;
|
||||
|
||||
/// <summary>
|
||||
/// sends a file to a server
|
||||
/// </summary>
|
||||
/// <param name="options">Upload options</param>
|
||||
/// 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<string[]>(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<string, string> 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<string,string> 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<Header[]>(jsonHeaders)
|
||||
.ToDictionary(header => header.Name, header => header.Value);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.WriteLine("Failed to parseHeaders from string :: " + jsonHeaders);
|
||||
}
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
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<string[]>(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<string, string> 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<string[]>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="asynchronousResult"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read file from Isolated Storage and sends it to server
|
||||
/// </summary>
|
||||
/// <param name="asynchronousResult"></param>
|
||||
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<string, string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads response into FileUploadResult
|
||||
/// </summary>
|
||||
/// <param name="asynchronousResult"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"globals": {
|
||||
"requestAnimationFrame": true
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
});
|
||||
Reference in New Issue
Block a user