All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.basho.riak.client.http.util.Multipart Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
/*
 * 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