/* * Android Asynchronous Http Client Copyright (c) 2011 James Smith <james@loopj.com> * http://loopj.com * * 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. */ /* * This code is taken from Rafael Sanches' blog. * http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/ */ package com.letv.commonjar.http; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.message.BasicHeader; import com.letv.commonjar.CLog; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Simplified multipart entity mainly used for sending one or more files. */ class SimpleMultipartEntity implements HttpEntity { private static final String TAG = CLog.makeTag(SimpleMultipartEntity.class); private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; private static final byte[] CR_LF = ("\r\n").getBytes(); private static final byte[] TRANSFER_ENCODING_BINARY = "Content-Transfer-Encoding: binary\r\n".getBytes(); private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private String boundary; private byte[] boundaryLine; private byte[] boundaryEnd; private boolean isRepeatable = false; private List<FilePart> fileParts = new ArrayList<FilePart>(); // The buffer we use for building the message excluding files and the last // boundary private ByteArrayOutputStream out = new ByteArrayOutputStream(); private ResponseInterface progressHandler; private int bytesWritten; private int totalSize; public SimpleMultipartEntity(ResponseInterface progressHandler) { final StringBuilder buf = new StringBuilder(); final Random rand = new Random(); for (int i = 0; i < 30; i++) { buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]); } boundary = buf.toString(); boundaryLine = ("--" + boundary + "\r\n").getBytes(); boundaryEnd = ("--" + boundary + "--\r\n").getBytes(); this.progressHandler = progressHandler; } public void addPart(final String key, final String value, final String contentType) { try { out.write(boundaryLine); out.write(createContentDisposition(key)); out.write(createContentType(contentType)); out.write(CR_LF); out.write(value.getBytes()); out.write(CR_LF); } catch (final IOException e) { // Shall not happen on ByteArrayOutputStream CLog.e(TAG, "addPart ByteArrayOutputStream exception", e); } } public void addPart(final String key, final String value) { addPart(key, value, "text/plain; charset=UTF-8"); } public void addPart(String key, File file) { addPart(key, file, null); } public void addPart(final String key, File file, String type) { if (type == null) { type = APPLICATION_OCTET_STREAM; } fileParts.add(new FilePart(key, file, type)); } public void addPart(String key, String streamName, InputStream inputStream, String type) throws IOException { if (type == null) { type = APPLICATION_OCTET_STREAM; } out.write(boundaryLine); // Headers out.write(createContentDisposition(key, streamName)); out.write(createContentType(type)); out.write(TRANSFER_ENCODING_BINARY); out.write(CR_LF); // Stream (file) final byte[] tmp = new byte[4096]; int l; while ((l = inputStream.read(tmp)) != -1) { out.write(tmp, 0, l); } out.write(CR_LF); out.flush(); try { inputStream.close(); } catch (final IOException e) { // Not important, just log it CLog.e(TAG, "Cannot close input stream", e); } } private byte[] createContentType(String type) { String result = "Content-Type: " + type + "\r\n"; return result.getBytes(); } private byte[] createContentDisposition(final String key) { return ("Content-Disposition: form-data; name=\"" + key + "\"\r\n").getBytes(); } private byte[] createContentDisposition(final String key, final String fileName) { return ("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n").getBytes(); } private void updateProgress(int count) { bytesWritten += count; progressHandler.onProgress(bytesWritten, totalSize); } private class FilePart { public File file; public byte[] header; public FilePart(String key, File file, String type) { header = createHeader(key, file.getName(), type); this.file = file; } private byte[] createHeader(String key, String filename, String type) { ByteArrayOutputStream headerStream = new ByteArrayOutputStream(); try { headerStream.write(boundaryLine); // Headers headerStream.write(createContentDisposition(key, filename)); headerStream.write(createContentType(type)); headerStream.write(TRANSFER_ENCODING_BINARY); headerStream.write(CR_LF); } catch (IOException e) { // Can't happen on ByteArrayOutputStream CLog.e(TAG, "createHeader ByteArrayOutputStream exception", e); } return headerStream.toByteArray(); } public long getTotalLength() { long streamLength = file.length(); return header.length + streamLength; } public void writeTo(OutputStream out) throws IOException { out.write(header); updateProgress(header.length); FileInputStream inputStream = new FileInputStream(file); final byte[] tmp = new byte[4096]; int l; while ((l = inputStream.read(tmp)) != -1) { out.write(tmp, 0, l); updateProgress(l); } out.write(CR_LF); updateProgress(CR_LF.length); out.flush(); try { inputStream.close(); } catch (final IOException e) { // Not important, just log it CLog.e(TAG, "Cannot close input stream", e); } } } // The following methods are from the HttpEntity interface @Override public long getContentLength() { long contentLen = out.size(); for (FilePart filePart : fileParts) { long len = filePart.getTotalLength(); if (len < 0) { return -1; // Should normally not happen } contentLen += len; } contentLen += boundaryEnd.length; return contentLen; } @Override public Header getContentType() { return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary); } @Override public boolean isChunked() { return false; } public void setIsRepeatable(boolean isRepeatable) { this.isRepeatable = isRepeatable; } @Override public boolean isRepeatable() { return isRepeatable; } @Override public boolean isStreaming() { return false; } @Override public void writeTo(final OutputStream outstream) throws IOException { bytesWritten = 0; totalSize = (int) getContentLength(); out.writeTo(outstream); updateProgress(out.size()); for (FilePart filePart : fileParts) { filePart.writeTo(outstream); } outstream.write(boundaryEnd); updateProgress(boundaryEnd.length); } @Override public Header getContentEncoding() { return null; } @Override public void consumeContent() throws IOException, UnsupportedOperationException { if (isStreaming()) { throw new UnsupportedOperationException("Streaming entity does not implement #consumeContent()"); } } @Override public InputStream getContent() throws IOException, UnsupportedOperationException { throw new UnsupportedOperationException("getContent() is not supported. Use writeTo() instead."); } }