diff -r 000000000000 -r 6474c204b198 mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,360 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.ByteArrayBuffer; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract base class for session input buffers that stream data from + * an arbitrary {@link InputStream}. This class buffers input data in + * an internal byte array for optimal input performance. + *

+ * {@link #readLine(CharArrayBuffer)} and {@link #readLine()} methods of this + * class treat a lone LF as valid line delimiters in addition to CR-LF required + * by the HTTP specification. + * + *

+ * The following parameters can be used to customize the behavior of this + * class: + *

+ * @since 4.0 + */ +public abstract class AbstractSessionInputBuffer implements SessionInputBuffer, BufferInfo { + + private InputStream instream; + private byte[] buffer; + private int bufferpos; + private int bufferlen; + + private ByteArrayBuffer linebuffer = null; + + private String charset = HTTP.US_ASCII; + private boolean ascii = true; + private int maxLineLen = -1; + private int minChunkLimit = 512; + + private HttpTransportMetricsImpl metrics; + + /** + * Initializes this session input buffer. + * + * @param instream the source input stream. + * @param buffersize the size of the internal buffer. + * @param params HTTP parameters. + */ + protected void init(final InputStream instream, int buffersize, final HttpParams params) { + if (instream == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("Buffer size may not be negative or zero"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.instream = instream; + this.buffer = new byte[buffersize]; + this.bufferpos = 0; + this.bufferlen = 0; + this.linebuffer = new ByteArrayBuffer(buffersize); + this.charset = HttpProtocolParams.getHttpElementCharset(params); + this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII) + || this.charset.equalsIgnoreCase(HTTP.ASCII); + this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1); + this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512); + this.metrics = createTransportMetrics(); + } + + /** + * @since 4.1 + */ + protected HttpTransportMetricsImpl createTransportMetrics() { + return new HttpTransportMetricsImpl(); + } + + /** + * @since 4.1 + */ + public int capacity() { + return this.buffer.length; + } + + /** + * @since 4.1 + */ + public int length() { + return this.bufferlen - this.bufferpos; + } + + /** + * @since 4.1 + */ + public int available() { + return capacity() - length(); + } + + protected int fillBuffer() throws IOException { + // compact the buffer if necessary + if (this.bufferpos > 0) { + int len = this.bufferlen - this.bufferpos; + if (len > 0) { + System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len); + } + this.bufferpos = 0; + this.bufferlen = len; + } + int l; + int off = this.bufferlen; + int len = this.buffer.length - off; + l = this.instream.read(this.buffer, off, len); + if (l == -1) { + return -1; + } else { + this.bufferlen = off + l; + this.metrics.incrementBytesTransferred(l); + return l; + } + } + + protected boolean hasBufferedData() { + return this.bufferpos < this.bufferlen; + } + + public int read() throws IOException { + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + return this.buffer[this.bufferpos++] & 0xff; + } + + public int read(final byte[] b, int off, int len) throws IOException { + if (b == null) { + return 0; + } + if (hasBufferedData()) { + int chunk = Math.min(len, this.bufferlen - this.bufferpos); + System.arraycopy(this.buffer, this.bufferpos, b, off, chunk); + this.bufferpos += chunk; + return chunk; + } + // If the remaining capacity is big enough, read directly from the + // underlying input stream bypassing the buffer. + if (len > this.minChunkLimit) { + int read = this.instream.read(b, off, len); + if (read > 0) { + this.metrics.incrementBytesTransferred(read); + } + return read; + } else { + // otherwise read to the buffer first + while (!hasBufferedData()) { + int noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + int chunk = Math.min(len, this.bufferlen - this.bufferpos); + System.arraycopy(this.buffer, this.bufferpos, b, off, chunk); + this.bufferpos += chunk; + return chunk; + } + } + + public int read(final byte[] b) throws IOException { + if (b == null) { + return 0; + } + return read(b, 0, b.length); + } + + private int locateLF() { + for (int i = this.bufferpos; i < this.bufferlen; i++) { + if (this.buffer[i] == HTTP.LF) { + return i; + } + } + return -1; + } + + /** + * Reads a complete line of characters up to a line delimiter from this + * session buffer into the given line buffer. The number of chars actually + * read is returned as an integer. The line delimiter itself is discarded. + * If no char is available because the end of the stream has been reached, + * the value -1 is returned. This method blocks until input + * data is available, end of file is detected, or an exception is thrown. + *

+ * This method treats a lone LF as a valid line delimiters in addition + * to CR-LF required by the HTTP specification. + * + * @param charbuffer the line buffer. + * @return one line of characters + * @exception IOException if an I/O error occurs. + */ + public int readLine(final CharArrayBuffer charbuffer) throws IOException { + if (charbuffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + int noRead = 0; + boolean retry = true; + while (retry) { + // attempt to find end of line (LF) + int i = locateLF(); + if (i != -1) { + // end of line found. + if (this.linebuffer.isEmpty()) { + // the entire line is preset in the read buffer + return lineFromReadBuffer(charbuffer, i); + } + retry = false; + int len = i + 1 - this.bufferpos; + this.linebuffer.append(this.buffer, this.bufferpos, len); + this.bufferpos = i + 1; + } else { + // end of line not found + if (hasBufferedData()) { + int len = this.bufferlen - this.bufferpos; + this.linebuffer.append(this.buffer, this.bufferpos, len); + this.bufferpos = this.bufferlen; + } + noRead = fillBuffer(); + if (noRead == -1) { + retry = false; + } + } + if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) { + throw new IOException("Maximum line length limit exceeded"); + } + } + if (noRead == -1 && this.linebuffer.isEmpty()) { + // indicate the end of stream + return -1; + } + return lineFromLineBuffer(charbuffer); + } + + /** + * Reads a complete line of characters up to a line delimiter from this + * session buffer. The line delimiter itself is discarded. If no char is + * available because the end of the stream has been reached, + * null is returned. This method blocks until input data is + * available, end of file is detected, or an exception is thrown. + *

+ * This method treats a lone LF as a valid line delimiters in addition + * to CR-LF required by the HTTP specification. + * + * @return HTTP line as a string + * @exception IOException if an I/O error occurs. + */ + private int lineFromLineBuffer(final CharArrayBuffer charbuffer) + throws IOException { + // discard LF if found + int l = this.linebuffer.length(); + if (l > 0) { + if (this.linebuffer.byteAt(l - 1) == HTTP.LF) { + l--; + this.linebuffer.setLength(l); + } + // discard CR if found + if (l > 0) { + if (this.linebuffer.byteAt(l - 1) == HTTP.CR) { + l--; + this.linebuffer.setLength(l); + } + } + } + l = this.linebuffer.length(); + if (this.ascii) { + charbuffer.append(this.linebuffer, 0, l); + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + String s = new String(this.linebuffer.buffer(), 0, l, this.charset); + l = s.length(); + charbuffer.append(s); + } + this.linebuffer.clear(); + return l; + } + + private int lineFromReadBuffer(final CharArrayBuffer charbuffer, int pos) + throws IOException { + int off = this.bufferpos; + int len; + this.bufferpos = pos + 1; + if (pos > 0 && this.buffer[pos - 1] == HTTP.CR) { + // skip CR if found + pos--; + } + len = pos - off; + if (this.ascii) { + charbuffer.append(this.buffer, off, len); + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + String s = new String(this.buffer, off, len, this.charset); + charbuffer.append(s); + len = s.length(); + } + return len; + } + + public String readLine() throws IOException { + CharArrayBuffer charbuffer = new CharArrayBuffer(64); + int l = readLine(charbuffer); + if (l != -1) { + return charbuffer.toString(); + } else { + return null; + } + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +}