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

com.couchbase.lite.support.MultipartReader Maven / Gradle / Ivy

package com.couchbase.lite.support;

import org.apache.http.util.ByteArrayBuffer;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class MultipartReader {

    private enum MultipartReaderState {
        kUninitialized,
        kAtStart,
        kInPrologue,
        kInBody,
        kInHeaders,
        kAtEnd,
        kFailed
    }

    private static final Charset utf8 = Charset.forName("UTF-8");
    private static final byte[] kCRLFCRLF = "\r\n\r\n".getBytes(utf8);
    private static final byte[] kEOM = "--".getBytes(utf8);

    private MultipartReaderState state = null;
    private ByteArrayBuffer buffer = null;
    private String contentType = null;
    private byte[] boundary = null;
    private byte[] boundaryWithoutLeadingCRLF = null;
    private MultipartReaderDelegate delegate = null;
    public Map headers = null;

    public MultipartReader(String contentType, MultipartReaderDelegate delegate) {
        this.contentType = contentType;
        this.delegate = delegate;

        this.buffer = new ByteArrayBuffer(1024);
        this.state = MultipartReaderState.kAtStart;

        parseContentType();
    }

    public byte[] getBoundary() {
        return boundary;
    }

    public byte[] getBoundaryWithoutLeadingCRLF() {
        if (boundaryWithoutLeadingCRLF == null) {
            byte[] rawBoundary = getBoundary();
            boundaryWithoutLeadingCRLF = Arrays.copyOfRange(rawBoundary, 2, rawBoundary.length);
        }
        return boundaryWithoutLeadingCRLF;
    }

    public boolean finished() {
        return state == MultipartReaderState.kAtEnd;
    }

    private static boolean memcmp(byte[] array1, byte[] array2, int len) {
        boolean equals = true;
        for (int i = 0; i < len; i++) {
            if (array1[i] != array2[i]) {
                equals = false;
            }
        }
        return equals;
    }

    public Range searchFor(byte[] pattern, int start) {
        KMPMatch searcher = new KMPMatch();
        int buffLen = Math.min(buffer.length(), buffer.buffer().length);
        int matchIndex = searcher.indexOf(buffer.buffer(), buffLen, pattern, start);
        if (matchIndex != -1) {
            return new Range(matchIndex, pattern.length);
        } else {
            return new Range(matchIndex, 0);
        }
    }

    public void parseHeaders(String headersStr) {

        headers = new HashMap();
        if (headersStr != null && headersStr.length() > 0) {
            headersStr = headersStr.trim();
            StringTokenizer tokenizer = new StringTokenizer(headersStr, "\r\n");
            while (tokenizer.hasMoreTokens()) {
                String header = tokenizer.nextToken();

                if (!header.contains(":")) {
                    throw new IllegalArgumentException("Missing ':' in header line: " + header);
                }
                int colon = header.indexOf(':');
                String key = header.substring(0, colon).trim();
                String value = header.substring(colon+1).trim();
                headers.put(key, value);
            }
        }
    }

    private void deleteUpThrough(int location) {
        // int start = location + 1;  // start at the first byte after the location

        if (location <= 0) return;

        byte[] b = buffer.buffer();
        int len = buffer.length();

        int j = 0;
        int i = location;
        while (i < len) {
            b[j++] = b[i++];
        }
        buffer.setLength(j);
    }

    private void trimBuffer() {
        int bufLen = buffer.length();
        int boundaryLen = getBoundary().length;
        if (bufLen > boundaryLen) {
            // Leave enough bytes in _buffer that we can find an incomplete boundary string
            delegate.appendToPart(buffer.buffer(), 0, bufLen - boundaryLen);
            deleteUpThrough(bufLen - boundaryLen);
        }
    }

    public void appendData(byte[] data) {
        appendData(data, 0, data.length);
    }

    public void appendData(byte[] data, int off, int len) {

        if (buffer == null) {
            return;
        }
        if (len == 0) {
            return;
        }
        buffer.append(data, off, len);

        MultipartReaderState nextState;
        do {
            nextState = MultipartReaderState.kUninitialized;
            int bufLen = buffer.length();
            switch (state) {
                case kAtStart: {
                    // The entire message might start with a boundary without a leading CRLF.
                    byte[] boundaryWithoutLeadingCRLF = getBoundaryWithoutLeadingCRLF();
                    if (bufLen >= boundaryWithoutLeadingCRLF.length) {
                        if (memcmp(buffer.buffer(), boundaryWithoutLeadingCRLF, boundaryWithoutLeadingCRLF.length)) {
                            deleteUpThrough(boundaryWithoutLeadingCRLF.length);
                            nextState = MultipartReaderState.kInHeaders;
                        } else {
                            nextState = MultipartReaderState.kInPrologue;
                        }
                    }
                    break;
                }
                case kInPrologue:
                case kInBody: {
                    // Look for the next part boundary in the data we just added and the ending bytes of
                    // the previous data (in case the boundary string is split across calls)
                    if (bufLen < boundary.length) {
                        break;
                    }
                    int start = Math.max(0, bufLen - data.length - boundary.length);
                    Range r = searchFor(boundary, start);
                    if (r.getLength() > 0) {
                        if (state == MultipartReaderState.kInBody) {
                            delegate.appendToPart(buffer.buffer(), 0, r.getLocation());
                            delegate.finishedPart();
                        }
                        deleteUpThrough(r.getLocation() + r.getLength());
                        nextState = MultipartReaderState.kInHeaders;
                    } else {
                        trimBuffer();
                    }
                    break;
                }
                case kInHeaders: {
                    // First check for the end-of-message string ("--" after separator):
                    if (bufLen >= kEOM.length && memcmp(buffer.buffer(), kEOM, kEOM.length)) {
                        state = MultipartReaderState.kAtEnd;
                        close();
                        return;
                    }
                    // Otherwise look for two CRLFs that delimit the end of the headers:
                    Range r = searchFor(kCRLFCRLF, 0);
                    if (r.getLength() > 0) {
                        String headersString = new String(buffer.buffer(), 0, r.getLocation(), utf8);
                        parseHeaders(headersString);
                        deleteUpThrough(r.getLocation() + r.getLength());
                        delegate.startedPart(headers);
                        nextState = MultipartReaderState.kInBody;
                    }
                    break;
                }
                default: {
                    throw new IllegalStateException("Unexpected data after end of MIME body");
                }
            }

            if (nextState != MultipartReaderState.kUninitialized) {
                state = nextState;
            }

        } while (nextState != MultipartReaderState.kUninitialized && buffer.length() > 0);
    }

    private void close() {
        if (buffer != null) buffer.clear();
        buffer = null;
        boundary = null;
        boundaryWithoutLeadingCRLF = null;
    }

    private void parseContentType() {

        StringTokenizer tokenizer = new StringTokenizer(contentType, ";");
        boolean first = true;
        while (tokenizer.hasMoreTokens()) {
            String param = tokenizer.nextToken().trim();
            if (first == true) {
                if (!param.startsWith("multipart/")) {
                    throw new IllegalArgumentException(contentType + " does not start with multipart/");
                }
                first = false;
            } else {
                if (param.startsWith("boundary=")) {
                    String tempBoundary = param.substring(9);
                    if (tempBoundary.startsWith("\"")) {
                        if (tempBoundary.length() < 2 || !tempBoundary.endsWith("\"")) {
                            throw new IllegalArgumentException(contentType + " is not valid");
                        }
                        tempBoundary = tempBoundary.substring(1, tempBoundary.length() - 1);
                    }
                    if (tempBoundary.length() < 1) {
                        throw new IllegalArgumentException(contentType + " has zero-length boundary");
                    }
                    tempBoundary = String.format("\r\n--%s", tempBoundary);
                    boundary = tempBoundary.getBytes(Charset.forName("UTF-8"));
                    break;
                }
            }
        }
    }
}

/**
 * Knuth-Morris-Pratt Algorithm for Pattern Matching
 */
class KMPMatch {
    /**
     * Finds the first occurrence of the pattern in the text.
     */
    public static int indexOf(byte[] data, int dataLength, byte[] pattern, int dataOffset) {

        int[] failure = computeFailure(pattern);

        int j = 0;
        if (data.length == 0)
            return -1;

        //final int dataLength = data.length;
        final int patternLength = pattern.length;

        for (int i = dataOffset; i < dataLength; i++) {
            while (j > 0 && pattern[j] != data[i]) {
                j = failure[j - 1];
            }
            if (pattern[j] == data[i]) {
                j++;
            }
            if (j == patternLength) {
                return i - patternLength + 1;
            }
        }

        return -1;
    }

    /**
     * Computes the failure function using a boot-strapping process,
     * where the pattern is matched against itself.
     */
    private static int[] computeFailure(byte[] pattern) {
        int[] failure = new int[pattern.length];

        int j = 0;
        for (int i = 1; i < pattern.length; i++) {
            while (j > 0 && pattern[j] != pattern[i]) {
                j = failure[j - 1];
            }
            if (pattern[j] == pattern[i]) {
                j++;
            }
            failure[i] = j;
        }
        return failure;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy