com.basho.riak.client.http.util.Multipart Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of riak-client Show documentation
Show all versions of riak-client Show documentation
HttpClient-based client for Riak
/*
* This file is provided 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.basho.riak.client.http.util;
import com.basho.riak.client.util.CharsetUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import org.apache.http.util.EncodingUtils;
/**
* Represents a multipart entity as described here:
*
* http://tools.ietf.org/html/rfc2046#section-5.1
*/
public class Multipart {
private static byte[] HEADER_DELIM = CharsetUtils.utf8StringToBytes("\r\n\r\n");
private static int indexOf(byte[] text, byte[] pattern, int fromIndex) {
if (fromIndex >= text.length || fromIndex < 0) {
throw new IllegalArgumentException("index not within range");
}
if (pattern.length == 0) {
throw new IllegalArgumentException("pattern must not be empty");
}
byte first = pattern[0];
int max = text.length - pattern.length;
for (int i = fromIndex; i <= max; i++) {
if (text[i] != first) {
while (i <= max && text[i] != first) {
i++;
}
}
if (i <= max) {
int j = i + 1;
int end = j + pattern.length - 1;
for (int k = 1; j < end && text[j] == pattern[k]; j++, k++);
if (j == end) {
return i;
}
}
}
return -1;
}
/**
* Parses a multipart message or a multipart subpart of a multipart message.
*
* @return A list of the parts parsed into headers and body of this
* multipart message
*/
public static List parse(Map headers, byte[] body) {
if (headers == null || body == null || body.length == 0)
return null;
if (!(body.length >= 2 && body[0] == '\r' && body[1] == '\n')) {
// In order to parse the multipart efficiently, we want to treat the
// first boundary identically to the others, so make sure that the
// first boundary is preceded by a '\r\n' like the others
byte[] newBody = new byte[body.length + 2];
newBody[0] = '\r';
newBody[1] = '\n';
System.arraycopy(body, 0, newBody, 2, body.length);
body = newBody;
}
String boundary = "\r\n--" + getBoundary(headers.get(Constants.HDR_CONTENT_TYPE));
byte[] boundaryBytes = CharsetUtils.asBytes(boundary, CharsetUtils.ISO_8859_1);
int boundarySize = boundary.length();
if ("\r\n--".equals(boundary))
return null;
// While this parsing could be more efficiently done in one pass with a
// hand written FSM, hopefully this method is more readable/intuitive.
List parts = new ArrayList();
int pos = indexOf(body, boundaryBytes, 0);
if (pos != -1) {
while (pos < body.length) {
// first char of part
int start = pos + boundarySize;
// last char of part + 1
int end = indexOf(body, boundaryBytes, start);
// end of header section + 1
int headerEnd = indexOf(body, HEADER_DELIM, pos);
// start of body section
int bodyStart = headerEnd + HEADER_DELIM.length;
// check for end boundary, which is (boundary + "--")
if (body.length >= (start + 2) && body[start] == '-' && body[start+1] == '-') {
break;
}
if (end == -1) {
end = body.length;
}
if (headerEnd == -1) {
headerEnd = body.length;
bodyStart = end;
}
if (bodyStart > end) {
bodyStart = end;
}
Map partHeaders = parseHeaders(EncodingUtils.getAsciiString(copyOfRange(body, start, headerEnd)));
parts.add(new Part(partHeaders, copyOfRange(body, bodyStart, end)));
pos = end;
}
}
return parts;
}
/**
* A Java6 Arrays.copyOfRange style method locally.
*
* @param original the array to copy
* @param start the start of the copy range
* @param end the end of the copy range
* @return a new array populated with the bytes from original[start] to original[end].
*/
private static byte[] copyOfRange(byte[] original, int start, int end) {
final byte[] copy = new byte[end-start];
System.arraycopy(original, start, copy, 0, end-start);
return copy;
}
/**
* Parse a block of header lines as defined here:
*
* http://tools.ietf.org/html/rfc822#section-3.2
*
* @param s
* The header blob
* @return Map of header names to values
*/
public static Map parseHeaders(String s) {
// "unfold" header lines (http://tools.ietf.org/html/rfc822#section-3.1)
s.replaceAll("\r\n\\s+", " ");
String[] headers = s.split("\r\n");
Map parsedHeaders = new HashMap();
for (String header : headers) {
// Split header line into name and value
String[] nv = header.split("\\s*:\\s*", 2);
if (nv.length > 1) {
parsedHeaders.put(nv[0].trim().toLowerCase(), nv[1].trim());
}
}
return parsedHeaders;
}
/**
* Given a content type value, get the "boundary" parameter
*
* @param contentType
* Content type value with boundary parameter. Should be of the
* form "type/subtype; boundary=foobar; param=value"
* @return Value of the boundary parameter
*/
public static String getBoundary(String contentType) {
String[] params = contentType.split("\\s*;\\s*");
for (String param : params) {
String[] nv = param.split("\\s*=\\s*", 2);
if (nv.length > 1) {
if ("boundary".equals(nv[0].toLowerCase()))
return ClientUtils.unquoteString(nv[1]);
}
}
return "";
}
/**
* A single part of a multipart entity
*/
public static class Part {
private Map headers;
private byte[] body = null;
private InputStream stream;
public Part(Map headers, byte[] body) {
this.headers = headers;
if(body != null) {
this.body = body.clone();
}
}
public Part(Map headers, InputStream body) {
this.headers = headers;
stream = body;
}
/**
* Headers defined in the part
*/
public Map getHeaders() {
return headers;
}
/**
* Body of this part
*/
public byte[] getBody() {
if (body == null && stream != null) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int readCount = 0; readCount != -1; readCount = stream.read(buffer)) {
os.write(buffer, 0, readCount);
}
body = os.toByteArray();
} catch (IOException e) { /* nop */}
stream = null;
}
return body;
}
public String getBodyAsString() {
byte[] body = getBody();
if (body == null)
return null;
return CharsetUtils.asString(body, CharsetUtils.getCharset(headers));
}
public InputStream getStream() {
if (stream == null && body != null) {
stream = new ByteArrayInputStream(body);
}
return stream;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy