/* 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 cn.rongcloud.im.server.network.http; import android.util.Log; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.message.BasicHeader; 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 LOG_TAG = "SimpleMultipartEntity"; 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 ResponseHandlerInterface progressHandler; private int bytesWritten; private int totalSize; public SimpleMultipartEntity(ResponseHandlerInterface 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) { // Can't happen on ByteArrayOutputStream Log.e(LOG_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 Log.w(LOG_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; if (progressHandler != null) { progressHandler.sendProgressMessage(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 Log.e(LOG_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 Log.w(LOG_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."); } }