1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,360 @@ 1.4 +/* 1.5 + * ==================================================================== 1.6 + * Licensed to the Apache Software Foundation (ASF) under one 1.7 + * or more contributor license agreements. See the NOTICE file 1.8 + * distributed with this work for additional information 1.9 + * regarding copyright ownership. The ASF licenses this file 1.10 + * to you under the Apache License, Version 2.0 (the 1.11 + * "License"); you may not use this file except in compliance 1.12 + * with the License. You may obtain a copy of the License at 1.13 + * 1.14 + * http://www.apache.org/licenses/LICENSE-2.0 1.15 + * 1.16 + * Unless required by applicable law or agreed to in writing, 1.17 + * software distributed under the License is distributed on an 1.18 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 1.19 + * KIND, either express or implied. See the License for the 1.20 + * specific language governing permissions and limitations 1.21 + * under the License. 1.22 + * ==================================================================== 1.23 + * 1.24 + * This software consists of voluntary contributions made by many 1.25 + * individuals on behalf of the Apache Software Foundation. For more 1.26 + * information on the Apache Software Foundation, please see 1.27 + * <http://www.apache.org/>. 1.28 + * 1.29 + */ 1.30 + 1.31 +package ch.boye.httpclientandroidlib.impl.io; 1.32 + 1.33 +import java.io.IOException; 1.34 +import java.io.InputStream; 1.35 + 1.36 +import ch.boye.httpclientandroidlib.io.BufferInfo; 1.37 +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; 1.38 +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; 1.39 +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; 1.40 +import ch.boye.httpclientandroidlib.params.HttpParams; 1.41 +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; 1.42 +import ch.boye.httpclientandroidlib.protocol.HTTP; 1.43 +import ch.boye.httpclientandroidlib.util.ByteArrayBuffer; 1.44 +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; 1.45 + 1.46 +/** 1.47 + * Abstract base class for session input buffers that stream data from 1.48 + * an arbitrary {@link InputStream}. This class buffers input data in 1.49 + * an internal byte array for optimal input performance. 1.50 + * <p> 1.51 + * {@link #readLine(CharArrayBuffer)} and {@link #readLine()} methods of this 1.52 + * class treat a lone LF as valid line delimiters in addition to CR-LF required 1.53 + * by the HTTP specification. 1.54 + * 1.55 + * <p> 1.56 + * The following parameters can be used to customize the behavior of this 1.57 + * class: 1.58 + * <ul> 1.59 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> 1.60 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> 1.61 + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MIN_CHUNK_LIMIT}</li> 1.62 + * </ul> 1.63 + * @since 4.0 1.64 + */ 1.65 +public abstract class AbstractSessionInputBuffer implements SessionInputBuffer, BufferInfo { 1.66 + 1.67 + private InputStream instream; 1.68 + private byte[] buffer; 1.69 + private int bufferpos; 1.70 + private int bufferlen; 1.71 + 1.72 + private ByteArrayBuffer linebuffer = null; 1.73 + 1.74 + private String charset = HTTP.US_ASCII; 1.75 + private boolean ascii = true; 1.76 + private int maxLineLen = -1; 1.77 + private int minChunkLimit = 512; 1.78 + 1.79 + private HttpTransportMetricsImpl metrics; 1.80 + 1.81 + /** 1.82 + * Initializes this session input buffer. 1.83 + * 1.84 + * @param instream the source input stream. 1.85 + * @param buffersize the size of the internal buffer. 1.86 + * @param params HTTP parameters. 1.87 + */ 1.88 + protected void init(final InputStream instream, int buffersize, final HttpParams params) { 1.89 + if (instream == null) { 1.90 + throw new IllegalArgumentException("Input stream may not be null"); 1.91 + } 1.92 + if (buffersize <= 0) { 1.93 + throw new IllegalArgumentException("Buffer size may not be negative or zero"); 1.94 + } 1.95 + if (params == null) { 1.96 + throw new IllegalArgumentException("HTTP parameters may not be null"); 1.97 + } 1.98 + this.instream = instream; 1.99 + this.buffer = new byte[buffersize]; 1.100 + this.bufferpos = 0; 1.101 + this.bufferlen = 0; 1.102 + this.linebuffer = new ByteArrayBuffer(buffersize); 1.103 + this.charset = HttpProtocolParams.getHttpElementCharset(params); 1.104 + this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII) 1.105 + || this.charset.equalsIgnoreCase(HTTP.ASCII); 1.106 + this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1); 1.107 + this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512); 1.108 + this.metrics = createTransportMetrics(); 1.109 + } 1.110 + 1.111 + /** 1.112 + * @since 4.1 1.113 + */ 1.114 + protected HttpTransportMetricsImpl createTransportMetrics() { 1.115 + return new HttpTransportMetricsImpl(); 1.116 + } 1.117 + 1.118 + /** 1.119 + * @since 4.1 1.120 + */ 1.121 + public int capacity() { 1.122 + return this.buffer.length; 1.123 + } 1.124 + 1.125 + /** 1.126 + * @since 4.1 1.127 + */ 1.128 + public int length() { 1.129 + return this.bufferlen - this.bufferpos; 1.130 + } 1.131 + 1.132 + /** 1.133 + * @since 4.1 1.134 + */ 1.135 + public int available() { 1.136 + return capacity() - length(); 1.137 + } 1.138 + 1.139 + protected int fillBuffer() throws IOException { 1.140 + // compact the buffer if necessary 1.141 + if (this.bufferpos > 0) { 1.142 + int len = this.bufferlen - this.bufferpos; 1.143 + if (len > 0) { 1.144 + System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len); 1.145 + } 1.146 + this.bufferpos = 0; 1.147 + this.bufferlen = len; 1.148 + } 1.149 + int l; 1.150 + int off = this.bufferlen; 1.151 + int len = this.buffer.length - off; 1.152 + l = this.instream.read(this.buffer, off, len); 1.153 + if (l == -1) { 1.154 + return -1; 1.155 + } else { 1.156 + this.bufferlen = off + l; 1.157 + this.metrics.incrementBytesTransferred(l); 1.158 + return l; 1.159 + } 1.160 + } 1.161 + 1.162 + protected boolean hasBufferedData() { 1.163 + return this.bufferpos < this.bufferlen; 1.164 + } 1.165 + 1.166 + public int read() throws IOException { 1.167 + int noRead = 0; 1.168 + while (!hasBufferedData()) { 1.169 + noRead = fillBuffer(); 1.170 + if (noRead == -1) { 1.171 + return -1; 1.172 + } 1.173 + } 1.174 + return this.buffer[this.bufferpos++] & 0xff; 1.175 + } 1.176 + 1.177 + public int read(final byte[] b, int off, int len) throws IOException { 1.178 + if (b == null) { 1.179 + return 0; 1.180 + } 1.181 + if (hasBufferedData()) { 1.182 + int chunk = Math.min(len, this.bufferlen - this.bufferpos); 1.183 + System.arraycopy(this.buffer, this.bufferpos, b, off, chunk); 1.184 + this.bufferpos += chunk; 1.185 + return chunk; 1.186 + } 1.187 + // If the remaining capacity is big enough, read directly from the 1.188 + // underlying input stream bypassing the buffer. 1.189 + if (len > this.minChunkLimit) { 1.190 + int read = this.instream.read(b, off, len); 1.191 + if (read > 0) { 1.192 + this.metrics.incrementBytesTransferred(read); 1.193 + } 1.194 + return read; 1.195 + } else { 1.196 + // otherwise read to the buffer first 1.197 + while (!hasBufferedData()) { 1.198 + int noRead = fillBuffer(); 1.199 + if (noRead == -1) { 1.200 + return -1; 1.201 + } 1.202 + } 1.203 + int chunk = Math.min(len, this.bufferlen - this.bufferpos); 1.204 + System.arraycopy(this.buffer, this.bufferpos, b, off, chunk); 1.205 + this.bufferpos += chunk; 1.206 + return chunk; 1.207 + } 1.208 + } 1.209 + 1.210 + public int read(final byte[] b) throws IOException { 1.211 + if (b == null) { 1.212 + return 0; 1.213 + } 1.214 + return read(b, 0, b.length); 1.215 + } 1.216 + 1.217 + private int locateLF() { 1.218 + for (int i = this.bufferpos; i < this.bufferlen; i++) { 1.219 + if (this.buffer[i] == HTTP.LF) { 1.220 + return i; 1.221 + } 1.222 + } 1.223 + return -1; 1.224 + } 1.225 + 1.226 + /** 1.227 + * Reads a complete line of characters up to a line delimiter from this 1.228 + * session buffer into the given line buffer. The number of chars actually 1.229 + * read is returned as an integer. The line delimiter itself is discarded. 1.230 + * If no char is available because the end of the stream has been reached, 1.231 + * the value <code>-1</code> is returned. This method blocks until input 1.232 + * data is available, end of file is detected, or an exception is thrown. 1.233 + * <p> 1.234 + * This method treats a lone LF as a valid line delimiters in addition 1.235 + * to CR-LF required by the HTTP specification. 1.236 + * 1.237 + * @param charbuffer the line buffer. 1.238 + * @return one line of characters 1.239 + * @exception IOException if an I/O error occurs. 1.240 + */ 1.241 + public int readLine(final CharArrayBuffer charbuffer) throws IOException { 1.242 + if (charbuffer == null) { 1.243 + throw new IllegalArgumentException("Char array buffer may not be null"); 1.244 + } 1.245 + int noRead = 0; 1.246 + boolean retry = true; 1.247 + while (retry) { 1.248 + // attempt to find end of line (LF) 1.249 + int i = locateLF(); 1.250 + if (i != -1) { 1.251 + // end of line found. 1.252 + if (this.linebuffer.isEmpty()) { 1.253 + // the entire line is preset in the read buffer 1.254 + return lineFromReadBuffer(charbuffer, i); 1.255 + } 1.256 + retry = false; 1.257 + int len = i + 1 - this.bufferpos; 1.258 + this.linebuffer.append(this.buffer, this.bufferpos, len); 1.259 + this.bufferpos = i + 1; 1.260 + } else { 1.261 + // end of line not found 1.262 + if (hasBufferedData()) { 1.263 + int len = this.bufferlen - this.bufferpos; 1.264 + this.linebuffer.append(this.buffer, this.bufferpos, len); 1.265 + this.bufferpos = this.bufferlen; 1.266 + } 1.267 + noRead = fillBuffer(); 1.268 + if (noRead == -1) { 1.269 + retry = false; 1.270 + } 1.271 + } 1.272 + if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) { 1.273 + throw new IOException("Maximum line length limit exceeded"); 1.274 + } 1.275 + } 1.276 + if (noRead == -1 && this.linebuffer.isEmpty()) { 1.277 + // indicate the end of stream 1.278 + return -1; 1.279 + } 1.280 + return lineFromLineBuffer(charbuffer); 1.281 + } 1.282 + 1.283 + /** 1.284 + * Reads a complete line of characters up to a line delimiter from this 1.285 + * session buffer. The line delimiter itself is discarded. If no char is 1.286 + * available because the end of the stream has been reached, 1.287 + * <code>null</code> is returned. This method blocks until input data is 1.288 + * available, end of file is detected, or an exception is thrown. 1.289 + * <p> 1.290 + * This method treats a lone LF as a valid line delimiters in addition 1.291 + * to CR-LF required by the HTTP specification. 1.292 + * 1.293 + * @return HTTP line as a string 1.294 + * @exception IOException if an I/O error occurs. 1.295 + */ 1.296 + private int lineFromLineBuffer(final CharArrayBuffer charbuffer) 1.297 + throws IOException { 1.298 + // discard LF if found 1.299 + int l = this.linebuffer.length(); 1.300 + if (l > 0) { 1.301 + if (this.linebuffer.byteAt(l - 1) == HTTP.LF) { 1.302 + l--; 1.303 + this.linebuffer.setLength(l); 1.304 + } 1.305 + // discard CR if found 1.306 + if (l > 0) { 1.307 + if (this.linebuffer.byteAt(l - 1) == HTTP.CR) { 1.308 + l--; 1.309 + this.linebuffer.setLength(l); 1.310 + } 1.311 + } 1.312 + } 1.313 + l = this.linebuffer.length(); 1.314 + if (this.ascii) { 1.315 + charbuffer.append(this.linebuffer, 0, l); 1.316 + } else { 1.317 + // This is VERY memory inefficient, BUT since non-ASCII charsets are 1.318 + // NOT meant to be used anyway, there's no point optimizing it 1.319 + String s = new String(this.linebuffer.buffer(), 0, l, this.charset); 1.320 + l = s.length(); 1.321 + charbuffer.append(s); 1.322 + } 1.323 + this.linebuffer.clear(); 1.324 + return l; 1.325 + } 1.326 + 1.327 + private int lineFromReadBuffer(final CharArrayBuffer charbuffer, int pos) 1.328 + throws IOException { 1.329 + int off = this.bufferpos; 1.330 + int len; 1.331 + this.bufferpos = pos + 1; 1.332 + if (pos > 0 && this.buffer[pos - 1] == HTTP.CR) { 1.333 + // skip CR if found 1.334 + pos--; 1.335 + } 1.336 + len = pos - off; 1.337 + if (this.ascii) { 1.338 + charbuffer.append(this.buffer, off, len); 1.339 + } else { 1.340 + // This is VERY memory inefficient, BUT since non-ASCII charsets are 1.341 + // NOT meant to be used anyway, there's no point optimizing it 1.342 + String s = new String(this.buffer, off, len, this.charset); 1.343 + charbuffer.append(s); 1.344 + len = s.length(); 1.345 + } 1.346 + return len; 1.347 + } 1.348 + 1.349 + public String readLine() throws IOException { 1.350 + CharArrayBuffer charbuffer = new CharArrayBuffer(64); 1.351 + int l = readLine(charbuffer); 1.352 + if (l != -1) { 1.353 + return charbuffer.toString(); 1.354 + } else { 1.355 + return null; 1.356 + } 1.357 + } 1.358 + 1.359 + public HttpTransportMetrics getMetrics() { 1.360 + return this.metrics; 1.361 + } 1.362 + 1.363 +}