/* * PhoneGap is available under *either* the terms of the modified BSD license *or* the * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * * Copyright (c) 2005-2010, Nitobi * Copyright (c) 2010, IBM Corporation */ package com.phonegap.http; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import javax.microedition.io.file.FileConnection; import net.rim.device.api.io.FileNotFoundException; import net.rim.device.api.io.IOUtilities; import net.rim.device.api.io.MIMETypeAssociations; import net.rim.device.api.io.http.HttpProtocolConstants; import net.rim.device.api.ui.UiApplication; import org.json.me.JSONException; import org.json.me.JSONObject; import com.phonegap.PhoneGapExtension; import com.phonegap.api.PluginResult; import com.phonegap.util.Logger; /** * The FileUploader uses an HTTP multipart request to upload files on the * device to a remote server. It currently supports a single file per HTTP * request. */ public class FileUploader { /** * Constants */ private static final String BOUNDARY = "----0x2fc1b3ef7cecbf14L"; private static final String LINE_END = "\r\n"; private static final String TD = "--"; /** * Uploads the specified file to the server URL provided using an HTTP * multipart request. * @param filePath Full path of the file on the file system * @param server URL of the server to receive the file * @param fileKey Name of file request parameter * @param fileName File name to be used on server * @param mimeType Describes file content type * @param params key:value pairs of user-defined parameters * @return FileUploadResult containing result of upload request */ public FileUploadResult upload(String filePath, String server, String fileKey, String fileName, String mimeType, JSONObject params) throws FileNotFoundException, IllegalArgumentException, IOException { Logger.log(this.getClass().getName() + ": uploading " + filePath + " to " + server); FileUploadResult result = new FileUploadResult(); InputStream in = null; OutputStream out = null; FileConnection fconn = null; HttpConnection httpConn = null; try { // open connection to the file try { fconn = (FileConnection)Connector.open(filePath, Connector.READ); } catch (ClassCastException e) { // in case something really funky gets passed in throw new IllegalArgumentException("Invalid file path"); } if (!fconn.exists()) { throw new FileNotFoundException(filePath + " not found"); } // determine mime type by // 1) user-provided type // 2) retrieve from file system // 3) default to JPEG if (mimeType == null) { mimeType = MIMETypeAssociations.getMIMEType(filePath); if (mimeType == null) { mimeType = HttpProtocolConstants.CONTENT_TYPE_IMAGE_JPEG; } } // boundary messages String boundaryMsg = getBoundaryMessage(fileKey, fileName, mimeType); String lastBoundary = getEndBoundary(); // user-defined request parameters String customParams = (params != null) ? getParameterContent(params) : ""; Logger.log(this.getClass().getName() + ": params=" + customParams); // determine content length long fileSize = fconn.fileSize(); Logger.log(this.getClass().getName() + ": " + filePath + " size=" + fileSize + " bytes"); long contentLength = fileSize + (long)boundaryMsg.length() + (long)lastBoundary.length() + (long)customParams.length(); // get HttpConnection httpConn = HttpUtils.getHttpConnection(server); if (httpConn == null) { throw new IOException("Unable to establish connection."); } Logger.log(this.getClass().getName() + ": server URL=" + httpConn.getURL()); // set request headers httpConn.setRequestMethod(HttpConnection.POST); httpConn.setRequestProperty( HttpProtocolConstants.HEADER_USER_AGENT, System.getProperty("browser.useragent")); httpConn.setRequestProperty( HttpProtocolConstants.HEADER_KEEP_ALIVE, "300"); httpConn.setRequestProperty( HttpProtocolConstants.HEADER_CONNECTION, "keep-alive"); httpConn.setRequestProperty( HttpProtocolConstants.HEADER_CONTENT_TYPE, HttpProtocolConstants.CONTENT_TYPE_MULTIPART_FORM_DATA + "; boundary=" + BOUNDARY); httpConn.setRequestProperty( HttpProtocolConstants.HEADER_CONTENT_LENGTH, Long.toString(contentLength)); // set cookie String cookie = HttpUtils.getCookie(server); if (cookie != null) { httpConn.setRequestProperty(HttpProtocolConstants.HEADER_COOKIE, cookie); Logger.log(this.getClass().getName() + ": cookie=" + cookie); } // write... out = httpConn.openDataOutputStream(); // parameters out.write(customParams.getBytes()); // boundary out.write(boundaryMsg.getBytes()); // file data in = fconn.openInputStream(); byte[] data = IOUtilities.streamToBytes(in); out.write(data); in.close(); // end boundary out.write(lastBoundary.getBytes()); // send request and get response in = httpConn.openDataInputStream(); int rc = httpConn.getResponseCode(); result.setResponse(new String(IOUtilities.streamToBytes(in))); result.setResponseCode(rc); result.setBytesSent(contentLength); Logger.log(this.getClass().getName() + ": sent " + contentLength + " bytes"); } finally { try { if (fconn != null) fconn.close(); if (in != null) in.close(); if (out != null) out.close(); if (httpConn != null) httpConn.close(); } catch (IOException e) { Logger.log(this.getClass().getName() + ": " + e); } } return result; } /** * Sends an upload progress notification back to JavaScript engine. * @param result FileUploadResult containing bytes sent of total * @param callbackId identifier of callback function to invoke */ protected void sendProgress(FileUploadResult result, final String callbackId) { JSONObject o = null; try { o = result.toJSONObject(); } catch (JSONException e) { Logger.log(this.getClass().getName() + ": " + e); return; } // send a progress result final PluginResult r = new PluginResult(PluginResult.Status.OK, o); r.setKeepCallback(true); UiApplication.getUiApplication().invokeAndWait( new Runnable() { public void run() { PhoneGapExtension.invokeSuccessCallback(callbackId, r); } } ); } /** * Returns the boundary string that represents the beginning of a file * in a multipart HTTP request. * @param fileKey Name of file request parameter * @param fileName File name to be used on server * @param mimeType Describes file content type * @return string representing the boundary message in a multipart HTTP request */ protected String getBoundaryMessage(String fileKey, String fileName, String mimeType) { return (new StringBuffer()) .append(LINE_END) .append(TD).append(BOUNDARY).append(LINE_END) .append("Content-Disposition: form-data; name=\"").append(fileKey) .append("\"; filename=\"").append(fileName).append("\"").append(LINE_END) .append("Content-Type: ").append(mimeType).append(LINE_END) .append(LINE_END) .toString(); } /** * Returns the boundary string that represents the end of a file in a * multipart HTTP request. * @return string representing the end boundary message in a multipart HTTP request */ protected String getEndBoundary() { return LINE_END + TD + BOUNDARY + TD + LINE_END; } /** * Returns HTTP form content containing specified parameters. */ protected String getParameterContent(JSONObject params) { StringBuffer buf = new StringBuffer(); for (Enumeration e = params.keys(); e.hasMoreElements();) { String key = e.nextElement().toString(); String value = params.optString(key); buf.append(TD).append(BOUNDARY).append(LINE_END) .append("Content-Disposition: form-data; name=\"").append(key).append("\"") .append(LINE_END).append(LINE_END) .append(value).append(LINE_END); } return buf.toString(); } }