Java tutorial
/* This file was modified from or inspired by Apache Cordova. 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 com.polyvi.xface.extension.filetransfer; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.Iterator; 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.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import com.polyvi.xface.app.XWhiteList; import com.polyvi.xface.extension.XCallbackContext; import com.polyvi.xface.extension.XExtension; import com.polyvi.xface.extension.XExtensionContext; import com.polyvi.xface.extension.XExtensionResult; import com.polyvi.xface.plugin.api.XIWebContext; import com.polyvi.xface.util.XConstant; import com.polyvi.xface.util.XFileUtils; import com.polyvi.xface.util.XLog; import com.polyvi.xface.util.XPathResolver; public class XFileTransferExt extends XExtension { private static final String CLASS_NAME = XFileTransferExt.class.getSimpleName(); private static final String ENCODING_TYPE = "UTF-8"; private static final int CONNECTION_TIME_OUT_MILLISECONDS = 5000; private static final String JSON_EXCEPTION_MISSING_SOURCE_OR_TARGET = "Missing source or target"; private static final String ILLEGAL_ARGUMENT_EXCEPTION_NOT_IN_ROOT_DIR = "filePath is not in root directory"; private static final String ILLEGAL_ARGUMENT_EXCEPTION_NAME_CONTAINS_COLON = "This file has a : in its name"; private static final String JSON_EXCEPTION_MISSING_OBJECT_ID = "Missing objectId"; private static final String ABORT_EXCEPTION_DOWNLOAD_ABORTED = "download aborted"; private static final String ABORT_EXCEPTION_UPLOAD_ABORTED = "upload aborted"; private static final String COMMAND_DOWNLOAD = "download"; private static final String COMMAND_UPLOAD = "upload"; private static final String COMMAND_ABORT = "abort"; private static final int FILE_NOT_FOUND_ERR = 1; private static final int INVALID_URL_ERR = 2; private static final int CONNECTION_ERR = 3; private static final int ABORTED_ERR = 4; private static final String LINE_START = "--"; private static final String LINE_END = "\r\n"; private static final String BOUNDARY = "*****"; private static HashSet<String> abortTriggered = new HashSet<String>(); private SSLSocketFactory mDefaultSSLSocketFactory = null; private HostnameVerifier mDefaultHostnameVerifier = null; private final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; private static class AbortException extends Exception { private static final long serialVersionUID = 1L; public AbortException(String str) { super(str); } } @Override public void init(XExtensionContext extensionContext, XIWebContext webContext) { super.init(extensionContext, webContext); } @Override public void sendAsyncResult(String result) { } @Override public boolean isAsync(String action) { return true; } @Override public XExtensionResult exec(String action, JSONArray args, XCallbackContext callbackCtx) throws JSONException { if (action.equals(COMMAND_DOWNLOAD) || action.equals(COMMAND_UPLOAD)) { String source = null; String target = null; String appWorkSpace = mWebContext.getWorkSpace(); try { source = args.getString(0); target = args.getString(1); } catch (JSONException e) { XLog.d(CLASS_NAME, JSON_EXCEPTION_MISSING_SOURCE_OR_TARGET); return new XExtensionResult(XExtensionResult.Status.JSON_EXCEPTION, JSON_EXCEPTION_MISSING_SOURCE_OR_TARGET); } if (action.equals(COMMAND_DOWNLOAD)) { return download(appWorkSpace, source, target, args, callbackCtx); } else if (action.equals(COMMAND_UPLOAD)) { return upload(appWorkSpace, source, target, args, callbackCtx); } } else if (action.equals(COMMAND_ABORT)) { return abort(args); } return new XExtensionResult(XExtensionResult.Status.INVALID_ACTION); } /** * JSON * @param appWorkSpace ? * @param source ?URL * @param target * @param args ?? * @param callbackCtx nativejs * @return JSON * @throws IOException * @throws JSONException */ private XExtensionResult download(String appWorkSpace, String source, String target, JSONArray args, XCallbackContext callbackCtx) throws JSONException { HttpURLConnection connection = null; try { boolean trustEveryone = args.optBoolean(2); String objectId = args.getString(3); if (target.contains(":")) { JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); XLog.e(CLASS_NAME, ILLEGAL_ARGUMENT_EXCEPTION_NAME_CONTAINS_COLON); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } XWhiteList whiteList = mWebContext.getApplication().getAppInfo().getWhiteList(); if (null != whiteList && !whiteList.isUrlWhiteListed(source)) { XLog.e(CLASS_NAME, "Source URL is not in white list: '" + source + "'"); JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); return new XExtensionResult(XExtensionResult.Status.IO_EXCEPTION, error); } File file = new File(appWorkSpace, target); if (!XFileUtils.isFileAncestorOf(appWorkSpace, file.getCanonicalPath())) { JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); XLog.e(CLASS_NAME, ILLEGAL_ARGUMENT_EXCEPTION_NOT_IN_ROOT_DIR); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } file.getParentFile().mkdirs(); // ? URL url = new URL(source); //TODO:?????? connection = getURLConnection(url, trustEveryone); ; connection.setRequestMethod("GET"); connection.setConnectTimeout(CONNECTION_TIME_OUT_MILLISECONDS); setCookieProperty(connection, source); connection.connect(); XLog.d(CLASS_NAME, "Download file:" + url); InputStream inputStream = connection.getInputStream(); byte[] buffer = new byte[XConstant.BUFFER_LEN]; int bytesRead = 0; long totalBytes = 0; FileTransferProgress progress = new FileTransferProgress(); if (connection.getContentEncoding() == null) { progress.setLengthComputable(true); progress.setTotal(connection.getContentLength()); } FileOutputStream outputStream = new FileOutputStream(file); while ((bytesRead = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, bytesRead); totalBytes += bytesRead; if (objectId != null) { //?js??object ID? progress.setLoaded(totalBytes); XExtensionResult progressResult = new XExtensionResult(XExtensionResult.Status.OK, progress.toJSONObject()); progressResult.setKeepCallback(true); callbackCtx.sendExtensionResult(progressResult); } synchronized (abortTriggered) { if (objectId != null && abortTriggered.contains(objectId)) { abortTriggered.remove(objectId); throw new AbortException(ABORT_EXCEPTION_DOWNLOAD_ABORTED); } } } outputStream.close(); inputStream.close(); XLog.d(CLASS_NAME, "Saved file: " + target); JSONObject entry = XFileUtils.getEntry(appWorkSpace, file); // if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) { ((HttpsURLConnection) connection).setHostnameVerifier(mDefaultHostnameVerifier); HttpsURLConnection.setDefaultSSLSocketFactory(mDefaultSSLSocketFactory); } return new XExtensionResult(XExtensionResult.Status.OK, entry); } catch (AbortException e) { JSONObject error = createFileTransferError(ABORTED_ERR, source, target, connection); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } catch (FileNotFoundException e) { JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } catch (MalformedURLException e) { JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, connection); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } catch (Exception e) { JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.IO_EXCEPTION, error); } finally { if (connection != null) { connection.disconnect(); } } } /** * ? * @param appWorkspace ? * @param source ? * @param target ?? * @param args JSONArray * @param callbackCtx nativejs * * args[2] fileKey ?name file? * args[3] fileName ??? image.jpg? * args[4] mimeType ?mimeimage/jpeg? * args[5] params HTTP????/ * args[6] trustEveryone * args[7] chunkedMode ??????true * @return FileUploadResult */ private XExtensionResult upload(String appWorkspace, String source, String target, JSONArray args, XCallbackContext callbackCtx) { XLog.d(CLASS_NAME, "upload " + source + " to " + target); HttpURLConnection conn = null; try { String fileKey = getArgument(args, 2, "file"); String fileName = getArgument(args, 3, "image.jpg"); String mimeType = getArgument(args, 4, "image/jpeg"); JSONObject params = args.optJSONObject(5); if (params == null) { params = new JSONObject(); } boolean trustEveryone = args.optBoolean(6); boolean chunkedMode = args.optBoolean(7) || args.isNull(7); JSONObject headers = args.optJSONObject(8); if (headers == null && params != null) { headers = params.optJSONObject("headers"); } String objectId = args.getString(9); //------------------ URL url = new URL(target); conn = getURLConnection(url, trustEveryone); conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); setCookieProperty(conn, target); // ?? handleRequestHeader(headers, conn); byte[] extraBytes = extraBytesFromParams(params, fileKey); String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END; String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END; byte[] fileNameBytes = fileName.getBytes(ENCODING_TYPE); FileInputStream fileInputStream = (FileInputStream) getPathFromUri(appWorkspace, source); int maxBufferSize = XConstant.BUFFER_LEN; if (chunkedMode) { conn.setChunkedStreamingMode(maxBufferSize); } else { int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length; XLog.d(CLASS_NAME, "String Length: " + stringLength); int fixedLength = (int) fileInputStream.getChannel().size() + stringLength; XLog.d(CLASS_NAME, "Content Length: " + fixedLength); conn.setFixedLengthStreamingMode(fixedLength); } // ??? OutputStream ouputStream = conn.getOutputStream(); DataOutputStream dos = new DataOutputStream(ouputStream); dos.write(extraBytes); dos.write(fileNameBytes); dos.writeBytes(midParams); XFileUploadResult result = new XFileUploadResult(); FileTransferProgress progress = new FileTransferProgress(); int bytesAvailable = fileInputStream.available(); int bufferSize = Math.min(bytesAvailable, maxBufferSize); byte[] buffer = new byte[bufferSize]; int bytesRead = fileInputStream.read(buffer, 0, bufferSize); long totalBytes = 0; while (bytesRead > 0) { totalBytes += bytesRead; result.setBytesSent(totalBytes); dos.write(buffer, 0, bytesRead); bytesRead = fileInputStream.read(buffer, 0, bufferSize); if (objectId != null) { //?js??object ID? progress.setTotal(bytesAvailable); XLog.d(CLASS_NAME, "total=" + bytesAvailable); progress.setLoaded(totalBytes); progress.setLengthComputable(true); XExtensionResult progressResult = new XExtensionResult(XExtensionResult.Status.OK, progress.toJSONObject()); progressResult.setKeepCallback(true); callbackCtx.sendExtensionResult(progressResult); } synchronized (abortTriggered) { if (objectId != null && abortTriggered.contains(objectId)) { abortTriggered.remove(objectId); throw new AbortException(ABORT_EXCEPTION_UPLOAD_ABORTED); } } } dos.writeBytes(tailParams); fileInputStream.close(); dos.flush(); dos.close(); checkConnection(conn); setUploadResult(result, conn); // if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) { ((HttpsURLConnection) conn).setHostnameVerifier(mDefaultHostnameVerifier); HttpsURLConnection.setDefaultSSLSocketFactory(mDefaultSSLSocketFactory); } XLog.d(CLASS_NAME, "****** About to return a result from upload"); return new XExtensionResult(XExtensionResult.Status.OK, result.toJSONObject()); } catch (AbortException e) { JSONObject error = createFileTransferError(ABORTED_ERR, source, target, conn); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } catch (FileNotFoundException e) { JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } catch (MalformedURLException e) { JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, conn); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.ERROR, error); } catch (IOException e) { JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.IO_EXCEPTION, error); } catch (JSONException e) { XLog.e(CLASS_NAME, e.getMessage()); return new XExtensionResult(XExtensionResult.Status.JSON_EXCEPTION); } catch (Throwable t) { JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); XLog.e(CLASS_NAME, error.toString()); return new XExtensionResult(XExtensionResult.Status.IO_EXCEPTION, error); } finally { if (conn != null) { conn.disconnect(); } } } /** * ? * @param headers ? * @param conn Http */ private void handleRequestHeader(JSONObject headers, HttpURLConnection conn) { if (headers != null) { 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)); } conn.setRequestProperty(headerKey, headerValues.getString(0)); for (int index = 1; index < headerValues.length(); ++index) { conn.addRequestProperty(headerKey, headerValues.getString(index)); } } } catch (JSONException e1) { XLog.d(CLASS_NAME, "No headers to be manipulated!"); } } } /** * ?Http * @param url ?? * @param trustEveryone */ private HttpURLConnection getURLConnection(URL url, boolean trustEveryone) throws IOException { HttpURLConnection conn = null; // ?URL??HTTP if (url.getProtocol().toLowerCase().equals("https")) { // HTTPS. ???? if (!trustEveryone) { conn = (HttpsURLConnection) url.openConnection(); } else { trustAllHosts(); HttpsURLConnection https = (HttpsURLConnection) url.openConnection(); // ?? hostnameVerifier mDefaultHostnameVerifier = https.getHostnameVerifier(); https.setHostnameVerifier(DO_NOT_VERIFY); conn = https; } } // HTTP else { conn = (HttpURLConnection) url.openConnection(); } return conn; } /** * params???Bytes * @param params js?? * @param fileKey ?name * @return Bytes[] */ private byte[] extraBytesFromParams(JSONObject params, String fileKey) throws UnsupportedEncodingException { StringBuilder extraParams = new StringBuilder(); try { for (Iterator iter = params.keys(); iter.hasNext();) { Object key = iter.next(); if (!String.valueOf(key).equals("headers")) { extraParams.append(LINE_START + BOUNDARY + LINE_END); extraParams.append("Content-Disposition: form-data; name=\"" + key.toString() + "\";"); extraParams.append(LINE_END + LINE_END); extraParams.append(params.getString(key.toString())); extraParams.append(LINE_END); } } } catch (JSONException e) { XLog.d(CLASS_NAME, e.getMessage()); } extraParams.append(LINE_START + BOUNDARY + LINE_END); extraParams.append("Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\""); return extraParams.toString().getBytes(ENCODING_TYPE); } /** * ????XFileUploadResult * @param result js * @param conn Http */ private void setUploadResult(XFileUploadResult result, HttpURLConnection conn) { StringBuffer responseString = new StringBuffer(""); DataInputStream inStream = null; try { inStream = new DataInputStream(conn.getInputStream()); String line = null; while ((line = inStream.readLine()) != null) { responseString.append(line); } // XFileUploadResult? result.setResponseCode(conn.getResponseCode()); result.setResponse(responseString.toString()); inStream.close(); } catch (FileNotFoundException e) { XLog.e(CLASS_NAME, e.toString()); } catch (IOException e) { XLog.e(CLASS_NAME, e.toString()); } } /** * SSL?TrustManager???SSL? * HttpsURLConnection???? */ private void trustAllHosts() { TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } } }; // all-trusting TrustManager try { // ?SSL mDefaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); // TrustManager SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } catch (Exception e) { XLog.e(CLASS_NAME, e.getMessage()); } } /** * JSONArray??. * @param args js?JSONArray * @param position ???? * @param defaultString * @return ?? */ private 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; } /** * content://uri?InputStream * * @param path * @return an input stream * @throws FileNotFoundException */ private InputStream getPathFromUri(String appWorkspace, String path) throws FileNotFoundException { XPathResolver pathResolver = new XPathResolver(path, appWorkspace, getContext()); String filepath = pathResolver.resolve(); if (null == filepath) { throw new FileNotFoundException(); } return new FileInputStream(filepath); } /** * FileTransferError * @param errorCode ? * @return JSONObject ?JSON */ private JSONObject createFileTransferError(int errorCode, String source, String target, HttpURLConnection connection) { Integer httpStatus = null; if (connection != null) { try { httpStatus = connection.getResponseCode(); } catch (IOException e) { XLog.e(CLASS_NAME, "Error getting HTTP status code from connection."); } } JSONObject error = null; try { error = new JSONObject(); error.put("code", errorCode); error.put("source", source); error.put("target", target); if (httpStatus != null) { error.put("http_status", httpStatus); } } catch (JSONException e) { XLog.e(CLASS_NAME, e.getMessage()); } return error; } /** * * * @param args ? */ private XExtensionResult abort(JSONArray args) { String objectId; try { objectId = args.getString(0); } catch (JSONException e) { XLog.d(CLASS_NAME, JSON_EXCEPTION_MISSING_OBJECT_ID); return new XExtensionResult(XExtensionResult.Status.JSON_EXCEPTION, "Missing objectId"); } synchronized (abortTriggered) { abortTriggered.add(objectId); } return new XExtensionResult(XExtensionResult.Status.OK); } /** * connectionCookie * @param connection Http * @param propert cookie? */ private void setCookieProperty(HttpURLConnection connection, String propert) { //Add cookie support CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(getContext()); cookieSyncManager.startSync(); String cookie = CookieManager.getInstance().getCookie(propert); if (cookie != null) { connection.setRequestProperty("cookie", cookie); } } /** * ?? * * @param con ? * @throws IOException */ private void checkConnection(HttpURLConnection con) throws MalformedURLException, IOException { int responseCode = con.getResponseCode(); if (HttpURLConnection.HTTP_OK != responseCode) { throw new MalformedURLException("" + responseCode); } } }