diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl')
276 files changed, 46101 insertions, 0 deletions
diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/AbstractHttpClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/AbstractHttpClientConnection.java new file mode 100644 index 000000000..3f3c5f406 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/AbstractHttpClientConnection.java @@ -0,0 +1,323 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.impl.entity.EntityDeserializer; +import ch.boye.httpclientandroidlib.impl.entity.EntitySerializer; +import ch.boye.httpclientandroidlib.impl.entity.LaxContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.entity.StrictContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpResponseParser; +import ch.boye.httpclientandroidlib.impl.io.HttpRequestWriter; +import ch.boye.httpclientandroidlib.io.EofSensor; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Abstract client-side HTTP connection capable of transmitting and receiving + * data using arbitrary {@link SessionInputBuffer} and + * {@link SessionOutputBuffer} implementations. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultBHttpClientConnection} + */ +@NotThreadSafe +@Deprecated +public abstract class AbstractHttpClientConnection implements HttpClientConnection { + + private final EntitySerializer entityserializer; + private final EntityDeserializer entitydeserializer; + + private SessionInputBuffer inbuffer = null; + private SessionOutputBuffer outbuffer = null; + private EofSensor eofSensor = null; + private HttpMessageParser<HttpResponse> responseParser = null; + private HttpMessageWriter<HttpRequest> requestWriter = null; + private HttpConnectionMetricsImpl metrics = null; + + /** + * Creates an instance of this class. + * <p> + * This constructor will invoke {@link #createEntityDeserializer()} + * and {@link #createEntitySerializer()} methods in order to initialize + * HTTP entity serializer and deserializer implementations for this + * connection. + */ + public AbstractHttpClientConnection() { + super(); + this.entityserializer = createEntitySerializer(); + this.entitydeserializer = createEntityDeserializer(); + } + + /** + * Asserts if the connection is open. + * + * @throws IllegalStateException if the connection is not open. + */ + protected abstract void assertOpen() throws IllegalStateException; + + /** + * Creates an instance of {@link EntityDeserializer} with the + * {@link LaxContentLengthStrategy} implementation to be used for + * de-serializing entities received over this connection. + * <p> + * This method can be overridden in a super class in order to create + * instances of {@link EntityDeserializer} using a custom + * {@link ch.boye.httpclientandroidlib.entity.ContentLengthStrategy}. + * + * @return HTTP entity deserializer + */ + protected EntityDeserializer createEntityDeserializer() { + return new EntityDeserializer(new LaxContentLengthStrategy()); + } + + /** + * Creates an instance of {@link EntitySerializer} with the + * {@link StrictContentLengthStrategy} implementation to be used for + * serializing HTTP entities sent over this connection. + * <p> + * This method can be overridden in a super class in order to create + * instances of {@link EntitySerializer} using a custom + * {@link ch.boye.httpclientandroidlib.entity.ContentLengthStrategy}. + * + * @return HTTP entity serialzier. + */ + protected EntitySerializer createEntitySerializer() { + return new EntitySerializer(new StrictContentLengthStrategy()); + } + + /** + * Creates an instance of {@link DefaultHttpResponseFactory} to be used + * for creating {@link HttpResponse} objects received by over this + * connection. + * <p> + * This method can be overridden in a super class in order to provide + * a different implementation of the {@link HttpResponseFactory} interface. + * + * @return HTTP response factory. + */ + protected HttpResponseFactory createHttpResponseFactory() { + return DefaultHttpResponseFactory.INSTANCE; + } + + /** + * Creates an instance of {@link HttpMessageParser} to be used for parsing + * HTTP responses received over this connection. + * <p> + * This method can be overridden in a super class in order to provide + * a different implementation of the {@link HttpMessageParser} interface or + * to pass a different implementation of the + * {@link ch.boye.httpclientandroidlib.message.LineParser} to the the + * {@link DefaultHttpResponseParser} constructor. + * + * @param buffer the session input buffer. + * @param responseFactory the HTTP response factory. + * @param params HTTP parameters. + * @return HTTP message parser. + */ + protected HttpMessageParser<HttpResponse> createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + return new DefaultHttpResponseParser(buffer, null, responseFactory, params); + } + + /** + * Creates an instance of {@link HttpMessageWriter} to be used for + * writing out HTTP requests sent over this connection. + * <p> + * This method can be overridden in a super class in order to provide + * a different implementation of the {@link HttpMessageWriter} interface or + * to pass a different implementation of + * {@link ch.boye.httpclientandroidlib.message.LineFormatter} to the the default implementation + * {@link HttpRequestWriter}. + * + * @param buffer the session output buffer + * @param params HTTP parameters + * @return HTTP message writer + */ + protected HttpMessageWriter<HttpRequest> createRequestWriter( + final SessionOutputBuffer buffer, + final HttpParams params) { + return new HttpRequestWriter(buffer, null, params); + } + + /** + * @since 4.1 + */ + protected HttpConnectionMetricsImpl createConnectionMetrics( + final HttpTransportMetrics inTransportMetric, + final HttpTransportMetrics outTransportMetric) { + return new HttpConnectionMetricsImpl(inTransportMetric, outTransportMetric); + } + + /** + * Initializes this connection object with {@link SessionInputBuffer} and + * {@link SessionOutputBuffer} instances to be used for sending and + * receiving data. These session buffers can be bound to any arbitrary + * physical output medium. + * <p> + * This method will invoke {@link #createHttpResponseFactory()}, + * {@link #createRequestWriter(SessionOutputBuffer, HttpParams)} + * and {@link #createResponseParser(SessionInputBuffer, HttpResponseFactory, HttpParams)} + * methods to initialize HTTP request writer and response parser for this + * connection. + * + * @param inbuffer the session input buffer. + * @param outbuffer the session output buffer. + * @param params HTTP parameters. + */ + protected void init( + final SessionInputBuffer inbuffer, + final SessionOutputBuffer outbuffer, + final HttpParams params) { + this.inbuffer = Args.notNull(inbuffer, "Input session buffer"); + this.outbuffer = Args.notNull(outbuffer, "Output session buffer"); + if (inbuffer instanceof EofSensor) { + this.eofSensor = (EofSensor) inbuffer; + } + this.responseParser = createResponseParser( + inbuffer, + createHttpResponseFactory(), + params); + this.requestWriter = createRequestWriter( + outbuffer, params); + this.metrics = createConnectionMetrics( + inbuffer.getMetrics(), + outbuffer.getMetrics()); + } + + public boolean isResponseAvailable(final int timeout) throws IOException { + assertOpen(); + try { + return this.inbuffer.isDataAvailable(timeout); + } catch (final SocketTimeoutException ex) { + return false; + } + } + + public void sendRequestHeader(final HttpRequest request) + throws HttpException, IOException { + Args.notNull(request, "HTTP request"); + assertOpen(); + this.requestWriter.write(request); + this.metrics.incrementRequestCount(); + } + + public void sendRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + Args.notNull(request, "HTTP request"); + assertOpen(); + if (request.getEntity() == null) { + return; + } + this.entityserializer.serialize( + this.outbuffer, + request, + request.getEntity()); + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public void flush() throws IOException { + assertOpen(); + doFlush(); + } + + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + assertOpen(); + final HttpResponse response = this.responseParser.parse(); + if (response.getStatusLine().getStatusCode() >= HttpStatus.SC_OK) { + this.metrics.incrementResponseCount(); + } + return response; + } + + public void receiveResponseEntity(final HttpResponse response) + throws HttpException, IOException { + Args.notNull(response, "HTTP response"); + assertOpen(); + final HttpEntity entity = this.entitydeserializer.deserialize(this.inbuffer, response); + response.setEntity(entity); + } + + protected boolean isEof() { + return this.eofSensor != null && this.eofSensor.isEof(); + } + + public boolean isStale() { + if (!isOpen()) { + return true; + } + if (isEof()) { + return true; + } + try { + this.inbuffer.isDataAvailable(1); + return isEof(); + } catch (final SocketTimeoutException ex) { + return false; + } catch (final IOException ex) { + return true; + } + } + + public HttpConnectionMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/AbstractHttpServerConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/AbstractHttpServerConnection.java new file mode 100644 index 000000000..e9e67e5b1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/AbstractHttpServerConnection.java @@ -0,0 +1,310 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestFactory; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpServerConnection; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.impl.entity.DisallowIdentityContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.entity.EntityDeserializer; +import ch.boye.httpclientandroidlib.impl.entity.EntitySerializer; +import ch.boye.httpclientandroidlib.impl.entity.LaxContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.entity.StrictContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpRequestParser; +import ch.boye.httpclientandroidlib.impl.io.HttpResponseWriter; +import ch.boye.httpclientandroidlib.io.EofSensor; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Abstract server-side HTTP connection capable of transmitting and receiving + * data using arbitrary {@link SessionInputBuffer} and + * {@link SessionOutputBuffer} implementations. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultBHttpServerConnection} + */ +@NotThreadSafe +@Deprecated +public abstract class AbstractHttpServerConnection implements HttpServerConnection { + + private final EntitySerializer entityserializer; + private final EntityDeserializer entitydeserializer; + + private SessionInputBuffer inbuffer = null; + private SessionOutputBuffer outbuffer = null; + private EofSensor eofSensor = null; + private HttpMessageParser<HttpRequest> requestParser = null; + private HttpMessageWriter<HttpResponse> responseWriter = null; + private HttpConnectionMetricsImpl metrics = null; + + /** + * Creates an instance of this class. + * <p> + * This constructor will invoke {@link #createEntityDeserializer()} + * and {@link #createEntitySerializer()} methods in order to initialize + * HTTP entity serializer and deserializer implementations for this + * connection. + */ + public AbstractHttpServerConnection() { + super(); + this.entityserializer = createEntitySerializer(); + this.entitydeserializer = createEntityDeserializer(); + } + + /** + * Asserts if the connection is open. + * + * @throws IllegalStateException if the connection is not open. + */ + protected abstract void assertOpen() throws IllegalStateException; + + /** + * Creates an instance of {@link EntityDeserializer} with the + * {@link LaxContentLengthStrategy} implementation to be used for + * de-serializing entities received over this connection. + * <p> + * This method can be overridden in a super class in order to create + * instances of {@link EntityDeserializer} using a custom + * {@link ch.boye.httpclientandroidlib.entity.ContentLengthStrategy}. + * + * @return HTTP entity deserializer + */ + protected EntityDeserializer createEntityDeserializer() { + return new EntityDeserializer(new DisallowIdentityContentLengthStrategy( + new LaxContentLengthStrategy(0))); + } + + /** + * Creates an instance of {@link EntitySerializer} with the + * {@link StrictContentLengthStrategy} implementation to be used for + * serializing HTTP entities sent over this connection. + * <p> + * This method can be overridden in a super class in order to create + * instances of {@link EntitySerializer} using a custom + * {@link ch.boye.httpclientandroidlib.entity.ContentLengthStrategy}. + * + * @return HTTP entity serialzier. + */ + protected EntitySerializer createEntitySerializer() { + return new EntitySerializer(new StrictContentLengthStrategy()); + } + + /** + * Creates an instance of {@link DefaultHttpRequestFactory} to be used + * for creating {@link HttpRequest} objects received by over this + * connection. + * <p> + * This method can be overridden in a super class in order to provide + * a different implementation of the {@link HttpRequestFactory} interface. + * + * @return HTTP request factory. + */ + protected HttpRequestFactory createHttpRequestFactory() { + return DefaultHttpRequestFactory.INSTANCE; + } + + /** + * Creates an instance of {@link HttpMessageParser} to be used for parsing + * HTTP requests received over this connection. + * <p> + * This method can be overridden in a super class in order to provide + * a different implementation of the {@link HttpMessageParser} interface or + * to pass a different implementation of the + * {@link ch.boye.httpclientandroidlib.message.LineParser} to the + * {@link DefaultHttpRequestParser} constructor. + * + * @param buffer the session input buffer. + * @param requestFactory the HTTP request factory. + * @param params HTTP parameters. + * @return HTTP message parser. + */ + protected HttpMessageParser<HttpRequest> createRequestParser( + final SessionInputBuffer buffer, + final HttpRequestFactory requestFactory, + final HttpParams params) { + return new DefaultHttpRequestParser(buffer, null, requestFactory, params); + } + + /** + * Creates an instance of {@link HttpMessageWriter} to be used for + * writing out HTTP responses sent over this connection. + * <p> + * This method can be overridden in a super class in order to provide + * a different implementation of the {@link HttpMessageWriter} interface or + * to pass a different implementation of + * {@link ch.boye.httpclientandroidlib.message.LineFormatter} to the the default + * implementation {@link HttpResponseWriter}. + * + * @param buffer the session output buffer + * @param params HTTP parameters + * @return HTTP message writer + */ + protected HttpMessageWriter<HttpResponse> createResponseWriter( + final SessionOutputBuffer buffer, + final HttpParams params) { + return new HttpResponseWriter(buffer, null, params); + } + + /** + * @since 4.1 + */ + protected HttpConnectionMetricsImpl createConnectionMetrics( + final HttpTransportMetrics inTransportMetric, + final HttpTransportMetrics outTransportMetric) { + return new HttpConnectionMetricsImpl(inTransportMetric, outTransportMetric); + } + + /** + * Initializes this connection object with {@link SessionInputBuffer} and + * {@link SessionOutputBuffer} instances to be used for sending and + * receiving data. These session buffers can be bound to any arbitrary + * physical output medium. + * <p> + * This method will invoke {@link #createHttpRequestFactory}, + * {@link #createRequestParser(SessionInputBuffer, HttpRequestFactory, HttpParams)} + * and {@link #createResponseWriter(SessionOutputBuffer, HttpParams)} + * methods to initialize HTTP request parser and response writer for this + * connection. + * + * @param inbuffer the session input buffer. + * @param outbuffer the session output buffer. + * @param params HTTP parameters. + */ + protected void init( + final SessionInputBuffer inbuffer, + final SessionOutputBuffer outbuffer, + final HttpParams params) { + this.inbuffer = Args.notNull(inbuffer, "Input session buffer"); + this.outbuffer = Args.notNull(outbuffer, "Output session buffer"); + if (inbuffer instanceof EofSensor) { + this.eofSensor = (EofSensor) inbuffer; + } + this.requestParser = createRequestParser( + inbuffer, + createHttpRequestFactory(), + params); + this.responseWriter = createResponseWriter( + outbuffer, params); + this.metrics = createConnectionMetrics( + inbuffer.getMetrics(), + outbuffer.getMetrics()); + } + + public HttpRequest receiveRequestHeader() + throws HttpException, IOException { + assertOpen(); + final HttpRequest request = this.requestParser.parse(); + this.metrics.incrementRequestCount(); + return request; + } + + public void receiveRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + Args.notNull(request, "HTTP request"); + assertOpen(); + final HttpEntity entity = this.entitydeserializer.deserialize(this.inbuffer, request); + request.setEntity(entity); + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public void flush() throws IOException { + assertOpen(); + doFlush(); + } + + public void sendResponseHeader(final HttpResponse response) + throws HttpException, IOException { + Args.notNull(response, "HTTP response"); + assertOpen(); + this.responseWriter.write(response); + if (response.getStatusLine().getStatusCode() >= 200) { + this.metrics.incrementResponseCount(); + } + } + + public void sendResponseEntity(final HttpResponse response) + throws HttpException, IOException { + if (response.getEntity() == null) { + return; + } + this.entityserializer.serialize( + this.outbuffer, + response, + response.getEntity()); + } + + protected boolean isEof() { + return this.eofSensor != null && this.eofSensor.isEof(); + } + + public boolean isStale() { + if (!isOpen()) { + return true; + } + if (isEof()) { + return true; + } + try { + this.inbuffer.isDataAvailable(1); + return isEof(); + } catch (final IOException ex) { + return true; + } + } + + public HttpConnectionMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/BHttpConnectionBase.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/BHttpConnectionBase.java new file mode 100644 index 000000000..2215aa6e2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/BHttpConnectionBase.java @@ -0,0 +1,393 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpConnection; +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpInetConnection; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.entity.BasicHttpEntity; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.entity.LaxContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.entity.StrictContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.ChunkedInputStream; +import ch.boye.httpclientandroidlib.impl.io.ChunkedOutputStream; +import ch.boye.httpclientandroidlib.impl.io.ContentLengthInputStream; +import ch.boye.httpclientandroidlib.impl.io.ContentLengthOutputStream; +import ch.boye.httpclientandroidlib.impl.io.HttpTransportMetricsImpl; +import ch.boye.httpclientandroidlib.impl.io.IdentityInputStream; +import ch.boye.httpclientandroidlib.impl.io.IdentityOutputStream; +import ch.boye.httpclientandroidlib.impl.io.SessionInputBufferImpl; +import ch.boye.httpclientandroidlib.impl.io.SessionOutputBufferImpl; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.NetUtils; + +/** + * This class serves as a base for all {@link HttpConnection} implementations and provides + * functionality common to both client and server HTTP connections. + * + * @since 4.0 + */ +@NotThreadSafe +public class BHttpConnectionBase implements HttpConnection, HttpInetConnection { + + private final SessionInputBufferImpl inbuffer; + private final SessionOutputBufferImpl outbuffer; + private final HttpConnectionMetricsImpl connMetrics; + private final ContentLengthStrategy incomingContentStrategy; + private final ContentLengthStrategy outgoingContentStrategy; + + private volatile boolean open; + private volatile Socket socket; + + /** + * Creates new instance of BHttpConnectionBase. + * + * @param buffersize buffer size. Must be a positive number. + * @param fragmentSizeHint fragment size hint. + * @param chardecoder decoder to be used for decoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for byte to char conversion. + * @param charencoder encoder to be used for encoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for char to byte conversion. + * @param constraints Message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * @param incomingContentStrategy incoming content length strategy. If <code>null</code> + * {@link LaxContentLengthStrategy#INSTANCE} will be used. + * @param outgoingContentStrategy outgoing content length strategy. If <code>null</code> + * {@link StrictContentLengthStrategy#INSTANCE} will be used. + */ + protected BHttpConnectionBase( + final int buffersize, + final int fragmentSizeHint, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy) { + super(); + Args.positive(buffersize, "Buffer size"); + final HttpTransportMetricsImpl inTransportMetrics = new HttpTransportMetricsImpl(); + final HttpTransportMetricsImpl outTransportMetrics = new HttpTransportMetricsImpl(); + this.inbuffer = new SessionInputBufferImpl(inTransportMetrics, buffersize, -1, + constraints != null ? constraints : MessageConstraints.DEFAULT, chardecoder); + this.outbuffer = new SessionOutputBufferImpl(outTransportMetrics, buffersize, fragmentSizeHint, + charencoder); + this.connMetrics = new HttpConnectionMetricsImpl(inTransportMetrics, outTransportMetrics); + this.incomingContentStrategy = incomingContentStrategy != null ? incomingContentStrategy : + LaxContentLengthStrategy.INSTANCE; + this.outgoingContentStrategy = outgoingContentStrategy != null ? outgoingContentStrategy : + StrictContentLengthStrategy.INSTANCE; + } + + protected void ensureOpen() throws IOException { + Asserts.check(this.open, "Connection is not open"); + if (!this.inbuffer.isBound()) { + this.inbuffer.bind(getSocketInputStream(this.socket)); + } + if (!this.outbuffer.isBound()) { + this.outbuffer.bind(getSocketOutputStream(this.socket)); + } + } + + protected InputStream getSocketInputStream(final Socket socket) throws IOException { + return socket.getInputStream(); + } + + protected OutputStream getSocketOutputStream(final Socket socket) throws IOException { + return socket.getOutputStream(); + } + + /** + * Binds this connection to the given {@link Socket}. This socket will be + * used by the connection to send and receive data. + * <p/> + * After this method's execution the connection status will be reported + * as open and the {@link #isOpen()} will return <code>true</code>. + * + * @param socket the socket. + * @throws IOException in case of an I/O error. + */ + protected void bind(final Socket socket) throws IOException { + Args.notNull(socket, "Socket"); + this.socket = socket; + this.open = true; + this.inbuffer.bind(null); + this.outbuffer.bind(null); + } + + protected SessionInputBuffer getSessionInputBuffer() { + return this.inbuffer; + } + + protected SessionOutputBuffer getSessionOutputBuffer() { + return this.outbuffer; + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public boolean isOpen() { + return this.open; + } + + protected Socket getSocket() { + return this.socket; + } + + protected OutputStream createOutputStream( + final long len, + final SessionOutputBuffer outbuffer) { + if (len == ContentLengthStrategy.CHUNKED) { + return new ChunkedOutputStream(2048, outbuffer); + } else if (len == ContentLengthStrategy.IDENTITY) { + return new IdentityOutputStream(outbuffer); + } else { + return new ContentLengthOutputStream(outbuffer, len); + } + } + + protected OutputStream prepareOutput(final HttpMessage message) throws HttpException { + final long len = this.outgoingContentStrategy.determineLength(message); + return createOutputStream(len, this.outbuffer); + } + + protected InputStream createInputStream( + final long len, + final SessionInputBuffer inbuffer) { + if (len == ContentLengthStrategy.CHUNKED) { + return new ChunkedInputStream(inbuffer); + } else if (len == ContentLengthStrategy.IDENTITY) { + return new IdentityInputStream(inbuffer); + } else { + return new ContentLengthInputStream(inbuffer, len); + } + } + + protected HttpEntity prepareInput(final HttpMessage message) throws HttpException { + final BasicHttpEntity entity = new BasicHttpEntity(); + + final long len = this.incomingContentStrategy.determineLength(message); + final InputStream instream = createInputStream(len, this.inbuffer); + if (len == ContentLengthStrategy.CHUNKED) { + entity.setChunked(true); + entity.setContentLength(-1); + entity.setContent(instream); + } else if (len == ContentLengthStrategy.IDENTITY) { + entity.setChunked(false); + entity.setContentLength(-1); + entity.setContent(instream); + } else { + entity.setChunked(false); + entity.setContentLength(len); + entity.setContent(instream); + } + + final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE); + if (contentTypeHeader != null) { + entity.setContentType(contentTypeHeader); + } + final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING); + if (contentEncodingHeader != null) { + entity.setContentEncoding(contentEncodingHeader); + } + return entity; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(final int timeout) { + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (final SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (final SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + final Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + final Socket sock = this.socket; + try { + this.inbuffer.clear(); + this.outbuffer.flush(); + try { + try { + sock.shutdownOutput(); + } catch (final IOException ignore) { + } + try { + sock.shutdownInput(); + } catch (final IOException ignore) { + } + } catch (final UnsupportedOperationException ignore) { + // if one isn't supported, the other one isn't either + } + } finally { + sock.close(); + } + } + + private int fillInputBuffer(final int timeout) throws IOException { + final int oldtimeout = this.socket.getSoTimeout(); + try { + this.socket.setSoTimeout(timeout); + return this.inbuffer.fillBuffer(); + } finally { + this.socket.setSoTimeout(oldtimeout); + } + } + + protected boolean awaitInput(final int timeout) throws IOException { + if (this.inbuffer.hasBufferedData()) { + return true; + } + fillInputBuffer(timeout); + return this.inbuffer.hasBufferedData(); + } + + public boolean isStale() { + if (!isOpen()) { + return true; + } + try { + final int bytesRead = fillInputBuffer(1); + return bytesRead < 0; + } catch (final SocketTimeoutException ex) { + return false; + } catch (final IOException ex) { + return true; + } + } + + protected void incrementRequestCount() { + this.connMetrics.incrementRequestCount(); + } + + protected void incrementResponseCount() { + this.connMetrics.incrementResponseCount(); + } + + public HttpConnectionMetrics getMetrics() { + return this.connMetrics; + } + + @Override + public String toString() { + if (this.socket != null) { + final StringBuilder buffer = new StringBuilder(); + final SocketAddress remoteAddress = this.socket.getRemoteSocketAddress(); + final SocketAddress localAddress = this.socket.getLocalSocketAddress(); + if (remoteAddress != null && localAddress != null) { + NetUtils.formatAddress(buffer, localAddress); + buffer.append("<->"); + NetUtils.formatAddress(buffer, remoteAddress); + } + return buffer.toString(); + } else { + return "[Not bound]"; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/ConnSupport.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/ConnSupport.java new file mode 100644 index 000000000..ba82a2a7c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/ConnSupport.java @@ -0,0 +1,75 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl; + +import ch.boye.httpclientandroidlib.config.ConnectionConfig; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; + +/** + * Connection support methods. + * + * @since 4.3 + */ +public final class ConnSupport { + + public static CharsetDecoder createDecoder(final ConnectionConfig cconfig) { + if (cconfig == null) { + return null; + } + final Charset charset = cconfig.getCharset(); + final CodingErrorAction malformed = cconfig.getMalformedInputAction(); + final CodingErrorAction unmappable = cconfig.getUnmappableInputAction(); + if (charset != null) { + return charset.newDecoder() + .onMalformedInput(malformed != null ? malformed : CodingErrorAction.REPORT) + .onUnmappableCharacter(unmappable != null ? unmappable: CodingErrorAction.REPORT); + } else { + return null; + } + } + + public static CharsetEncoder createEncoder(final ConnectionConfig cconfig) { + if (cconfig == null) { + return null; + } + final Charset charset = cconfig.getCharset(); + if (charset != null) { + final CodingErrorAction malformed = cconfig.getMalformedInputAction(); + final CodingErrorAction unmappable = cconfig.getUnmappableInputAction(); + return charset.newEncoder() + .onMalformedInput(malformed != null ? malformed : CodingErrorAction.REPORT) + .onUnmappableCharacter(unmappable != null ? unmappable: CodingErrorAction.REPORT); + } else { + return null; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnection.java new file mode 100644 index 000000000..9627e53f2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnection.java @@ -0,0 +1,182 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; + +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpRequestWriterFactory; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpResponseParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link HttpClientConnection}. + * + * @since 4.3 + */ +@NotThreadSafe +public class DefaultBHttpClientConnection extends BHttpConnectionBase + implements HttpClientConnection { + + private final HttpMessageParser<HttpResponse> responseParser; + private final HttpMessageWriter<HttpRequest> requestWriter; + + /** + * Creates new instance of DefaultBHttpClientConnection. + * + * @param buffersize buffer size. Must be a positive number. + * @param fragmentSizeHint fragment size hint. + * @param chardecoder decoder to be used for decoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for byte to char conversion. + * @param charencoder encoder to be used for encoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for char to byte conversion. + * @param constraints Message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * @param incomingContentStrategy incoming content length strategy. If <code>null</code> + * {@link ch.boye.httpclientandroidlib.impl.entity.LaxContentLengthStrategy#INSTANCE} will be used. + * @param outgoingContentStrategy outgoing content length strategy. If <code>null</code> + * {@link ch.boye.httpclientandroidlib.impl.entity.StrictContentLengthStrategy#INSTANCE} will be used. + * @param requestWriterFactory request writer factory. If <code>null</code> + * {@link DefaultHttpRequestWriterFactory#INSTANCE} will be used. + * @param responseParserFactory response parser factory. If <code>null</code> + * {@link DefaultHttpResponseParserFactory#INSTANCE} will be used. + */ + public DefaultBHttpClientConnection( + final int buffersize, + final int fragmentSizeHint, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageWriterFactory<HttpRequest> requestWriterFactory, + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + super(buffersize, fragmentSizeHint, chardecoder, charencoder, + constraints, incomingContentStrategy, outgoingContentStrategy); + this.requestWriter = (requestWriterFactory != null ? requestWriterFactory : + DefaultHttpRequestWriterFactory.INSTANCE).create(getSessionOutputBuffer()); + this.responseParser = (responseParserFactory != null ? responseParserFactory : + DefaultHttpResponseParserFactory.INSTANCE).create(getSessionInputBuffer(), constraints); + } + + public DefaultBHttpClientConnection( + final int buffersize, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints) { + this(buffersize, buffersize, chardecoder, charencoder, constraints, null, null, null, null); + } + + public DefaultBHttpClientConnection(final int buffersize) { + this(buffersize, buffersize, null, null, null, null, null, null, null); + } + + protected void onResponseReceived(final HttpResponse response) { + } + + protected void onRequestSubmitted(final HttpRequest request) { + } + + @Override + public void bind(final Socket socket) throws IOException { + super.bind(socket); + } + + public boolean isResponseAvailable(final int timeout) throws IOException { + ensureOpen(); + try { + return awaitInput(timeout); + } catch (final SocketTimeoutException ex) { + return false; + } + } + + public void sendRequestHeader(final HttpRequest request) + throws HttpException, IOException { + Args.notNull(request, "HTTP request"); + ensureOpen(); + this.requestWriter.write(request); + onRequestSubmitted(request); + incrementRequestCount(); + } + + public void sendRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + Args.notNull(request, "HTTP request"); + ensureOpen(); + final HttpEntity entity = request.getEntity(); + if (entity == null) { + return; + } + final OutputStream outstream = prepareOutput(request); + entity.writeTo(outstream); + outstream.close(); + } + + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + ensureOpen(); + final HttpResponse response = this.responseParser.parse(); + onResponseReceived(response); + if (response.getStatusLine().getStatusCode() >= HttpStatus.SC_OK) { + incrementResponseCount(); + } + return response; + } + + public void receiveResponseEntity( + final HttpResponse response) throws HttpException, IOException { + Args.notNull(response, "HTTP response"); + ensureOpen(); + final HttpEntity entity = prepareInput(response); + response.setEntity(entity); + } + + public void flush() throws IOException { + ensureOpen(); + doFlush(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnectionFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnectionFactory.java new file mode 100644 index 000000000..f337339c3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnectionFactory.java @@ -0,0 +1,103 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import ch.boye.httpclientandroidlib.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; + +import java.io.IOException; +import java.net.Socket; + +/** + * Default factory for {@link ch.boye.httpclientandroidlib.HttpClientConnection}s. + * + * @since 4.3 + */ +@Immutable +public class DefaultBHttpClientConnectionFactory + implements HttpConnectionFactory<DefaultBHttpClientConnection> { + + public static final DefaultBHttpClientConnectionFactory INSTANCE = new DefaultBHttpClientConnectionFactory(); + + private final ConnectionConfig cconfig; + private final ContentLengthStrategy incomingContentStrategy; + private final ContentLengthStrategy outgoingContentStrategy; + private final HttpMessageWriterFactory<HttpRequest> requestWriterFactory; + private final HttpMessageParserFactory<HttpResponse> responseParserFactory; + + public DefaultBHttpClientConnectionFactory( + final ConnectionConfig cconfig, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageWriterFactory<HttpRequest> requestWriterFactory, + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + super(); + this.cconfig = cconfig != null ? cconfig : ConnectionConfig.DEFAULT; + this.incomingContentStrategy = incomingContentStrategy; + this.outgoingContentStrategy = outgoingContentStrategy; + this.requestWriterFactory = requestWriterFactory; + this.responseParserFactory = responseParserFactory; + } + + public DefaultBHttpClientConnectionFactory( + final ConnectionConfig cconfig, + final HttpMessageWriterFactory<HttpRequest> requestWriterFactory, + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + this(cconfig, null, null, requestWriterFactory, responseParserFactory); + } + + public DefaultBHttpClientConnectionFactory(final ConnectionConfig cconfig) { + this(cconfig, null, null, null, null); + } + + public DefaultBHttpClientConnectionFactory() { + this(null, null, null, null, null); + } + + public DefaultBHttpClientConnection createConnection(final Socket socket) throws IOException { + final DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection( + this.cconfig.getBufferSize(), + this.cconfig.getFragmentSizeHint(), + ConnSupport.createDecoder(this.cconfig), + ConnSupport.createEncoder(this.cconfig), + this.cconfig.getMessageConstraints(), + this.incomingContentStrategy, + this.outgoingContentStrategy, + this.requestWriterFactory, + this.responseParserFactory); + conn.bind(socket); + return conn; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnection.java new file mode 100644 index 000000000..6ee4fb740 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnection.java @@ -0,0 +1,174 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpServerConnection; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.entity.DisallowIdentityContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpRequestParserFactory; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpResponseWriterFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link HttpServerConnection}. + * + * @since 4.3 + */ +@NotThreadSafe +public class DefaultBHttpServerConnection extends BHttpConnectionBase + implements HttpServerConnection { + + private final HttpMessageParser<HttpRequest> requestParser; + private final HttpMessageWriter<HttpResponse> responseWriter; + + /** + * Creates new instance of DefaultBHttpServerConnection. + * + * @param buffersize buffer size. Must be a positive number. + * @param fragmentSizeHint fragment size hint. + * @param chardecoder decoder to be used for decoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for byte to char conversion. + * @param charencoder encoder to be used for encoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for char to byte conversion. + * @param constraints Message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * @param incomingContentStrategy incoming content length strategy. If <code>null</code> + * {@link DisallowIdentityContentLengthStrategy#INSTANCE} will be used. + * @param outgoingContentStrategy outgoing content length strategy. If <code>null</code> + * {@link ch.boye.httpclientandroidlib.impl.entity.StrictContentLengthStrategy#INSTANCE} will be used. + * @param requestParserFactory request parser factory. If <code>null</code> + * {@link DefaultHttpRequestParserFactory#INSTANCE} will be used. + * @param responseWriterFactory response writer factory. If <code>null</code> + * {@link DefaultHttpResponseWriterFactory#INSTANCE} will be used. + */ + public DefaultBHttpServerConnection( + final int buffersize, + final int fragmentSizeHint, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageParserFactory<HttpRequest> requestParserFactory, + final HttpMessageWriterFactory<HttpResponse> responseWriterFactory) { + super(buffersize, fragmentSizeHint, chardecoder, charencoder, constraints, + incomingContentStrategy != null ? incomingContentStrategy : + DisallowIdentityContentLengthStrategy.INSTANCE, outgoingContentStrategy); + this.requestParser = (requestParserFactory != null ? requestParserFactory : + DefaultHttpRequestParserFactory.INSTANCE).create(getSessionInputBuffer(), constraints); + this.responseWriter = (responseWriterFactory != null ? responseWriterFactory : + DefaultHttpResponseWriterFactory.INSTANCE).create(getSessionOutputBuffer()); + } + + public DefaultBHttpServerConnection( + final int buffersize, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints) { + this(buffersize, buffersize, chardecoder, charencoder, constraints, null, null, null, null); + } + + public DefaultBHttpServerConnection(final int buffersize) { + this(buffersize, buffersize, null, null, null, null, null, null, null); + } + + protected void onRequestReceived(final HttpRequest request) { + } + + protected void onResponseSubmitted(final HttpResponse response) { + } + + @Override + public void bind(final Socket socket) throws IOException { + super.bind(socket); + } + + public HttpRequest receiveRequestHeader() + throws HttpException, IOException { + ensureOpen(); + final HttpRequest request = this.requestParser.parse(); + onRequestReceived(request); + incrementRequestCount(); + return request; + } + + public void receiveRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + Args.notNull(request, "HTTP request"); + ensureOpen(); + final HttpEntity entity = prepareInput(request); + request.setEntity(entity); + } + + public void sendResponseHeader(final HttpResponse response) + throws HttpException, IOException { + Args.notNull(response, "HTTP response"); + ensureOpen(); + this.responseWriter.write(response); + onResponseSubmitted(response); + if (response.getStatusLine().getStatusCode() >= 200) { + incrementResponseCount(); + } + } + + public void sendResponseEntity(final HttpResponse response) + throws HttpException, IOException { + Args.notNull(response, "HTTP response"); + ensureOpen(); + final HttpEntity entity = response.getEntity(); + if (entity == null) { + return; + } + final OutputStream outstream = prepareOutput(response); + entity.writeTo(outstream); + outstream.close(); + } + + public void flush() throws IOException { + ensureOpen(); + doFlush(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnectionFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnectionFactory.java new file mode 100644 index 000000000..a312320aa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnectionFactory.java @@ -0,0 +1,103 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import ch.boye.httpclientandroidlib.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; + +import java.io.IOException; +import java.net.Socket; + +/** + * Default factory for {@link ch.boye.httpclientandroidlib.HttpServerConnection}s. + * + * @since 4.3 + */ +@Immutable +public class DefaultBHttpServerConnectionFactory + implements HttpConnectionFactory<DefaultBHttpServerConnection> { + + public static final DefaultBHttpServerConnectionFactory INSTANCE = new DefaultBHttpServerConnectionFactory(); + + private final ConnectionConfig cconfig; + private final ContentLengthStrategy incomingContentStrategy; + private final ContentLengthStrategy outgoingContentStrategy; + private final HttpMessageParserFactory<HttpRequest> requestParserFactory; + private final HttpMessageWriterFactory<HttpResponse> responseWriterFactory; + + public DefaultBHttpServerConnectionFactory( + final ConnectionConfig cconfig, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageParserFactory<HttpRequest> requestParserFactory, + final HttpMessageWriterFactory<HttpResponse> responseWriterFactory) { + super(); + this.cconfig = cconfig != null ? cconfig : ConnectionConfig.DEFAULT; + this.incomingContentStrategy = incomingContentStrategy; + this.outgoingContentStrategy = outgoingContentStrategy; + this.requestParserFactory = requestParserFactory; + this.responseWriterFactory = responseWriterFactory; + } + + public DefaultBHttpServerConnectionFactory( + final ConnectionConfig cconfig, + final HttpMessageParserFactory<HttpRequest> requestParserFactory, + final HttpMessageWriterFactory<HttpResponse> responseWriterFactory) { + this(cconfig, null, null, requestParserFactory, responseWriterFactory); + } + + public DefaultBHttpServerConnectionFactory(final ConnectionConfig cconfig) { + this(cconfig, null, null, null, null); + } + + public DefaultBHttpServerConnectionFactory() { + this(null, null, null, null, null); + } + + public DefaultBHttpServerConnection createConnection(final Socket socket) throws IOException { + final DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection( + this.cconfig.getBufferSize(), + this.cconfig.getFragmentSizeHint(), + ConnSupport.createDecoder(this.cconfig), + ConnSupport.createEncoder(this.cconfig), + this.cconfig.getMessageConstraints(), + this.incomingContentStrategy, + this.outgoingContentStrategy, + this.requestParserFactory, + this.responseWriterFactory); + conn.bind(socket); + return conn; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultConnectionReuseStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultConnectionReuseStrategy.java new file mode 100644 index 000000000..b58127e5f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultConnectionReuseStrategy.java @@ -0,0 +1,189 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderIterator; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.TokenIterator; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.message.BasicTokenIterator; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of a strategy deciding about connection re-use. + * The default implementation first checks some basics, for example + * whether the connection is still open or whether the end of the + * request entity can be determined without closing the connection. + * If these checks pass, the tokens in the <code>Connection</code> header will + * be examined. In the absence of a <code>Connection</code> header, the + * non-standard but commonly used <code>Proxy-Connection</code> header takes + * it's role. A token <code>close</code> indicates that the connection cannot + * be reused. If there is no such token, a token <code>keep-alive</code> + * indicates that the connection should be re-used. If neither token is found, + * or if there are no <code>Connection</code> headers, the default policy for + * the HTTP version is applied. Since <code>HTTP/1.1</code>, connections are + * re-used by default. Up until <code>HTTP/1.0</code>, connections are not + * re-used by default. + * + * @since 4.0 + */ +@Immutable +public class DefaultConnectionReuseStrategy implements ConnectionReuseStrategy { + + public static final DefaultConnectionReuseStrategy INSTANCE = new DefaultConnectionReuseStrategy(); + + public DefaultConnectionReuseStrategy() { + super(); + } + + // see interface ConnectionReuseStrategy + public boolean keepAlive(final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + + // Check for a self-terminating entity. If the end of the entity will + // be indicated by closing the connection, there is no keep-alive. + final ProtocolVersion ver = response.getStatusLine().getProtocolVersion(); + final Header teh = response.getFirstHeader(HTTP.TRANSFER_ENCODING); + if (teh != null) { + if (!HTTP.CHUNK_CODING.equalsIgnoreCase(teh.getValue())) { + return false; + } + } else { + if (canResponseHaveBody(response)) { + final Header[] clhs = response.getHeaders(HTTP.CONTENT_LEN); + // Do not reuse if not properly content-length delimited + if (clhs.length == 1) { + final Header clh = clhs[0]; + try { + final int contentLen = Integer.parseInt(clh.getValue()); + if (contentLen < 0) { + return false; + } + } catch (final NumberFormatException ex) { + return false; + } + } else { + return false; + } + } + } + + // Check for the "Connection" header. If that is absent, check for + // the "Proxy-Connection" header. The latter is an unspecified and + // broken but unfortunately common extension of HTTP. + HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE); + if (!hit.hasNext()) { + hit = response.headerIterator("Proxy-Connection"); + } + + // Experimental usage of the "Connection" header in HTTP/1.0 is + // documented in RFC 2068, section 19.7.1. A token "keep-alive" is + // used to indicate that the connection should be persistent. + // Note that the final specification of HTTP/1.1 in RFC 2616 does not + // include this information. Neither is the "Connection" header + // mentioned in RFC 1945, which informally describes HTTP/1.0. + // + // RFC 2616 specifies "close" as the only connection token with a + // specific meaning: it disables persistent connections. + // + // The "Proxy-Connection" header is not formally specified anywhere, + // but is commonly used to carry one token, "close" or "keep-alive". + // The "Connection" header, on the other hand, is defined as a + // sequence of tokens, where each token is a header name, and the + // token "close" has the above-mentioned additional meaning. + // + // To get through this mess, we treat the "Proxy-Connection" header + // in exactly the same way as the "Connection" header, but only if + // the latter is missing. We scan the sequence of tokens for both + // "close" and "keep-alive". As "close" is specified by RFC 2068, + // it takes precedence and indicates a non-persistent connection. + // If there is no "close" but a "keep-alive", we take the hint. + + if (hit.hasNext()) { + try { + final TokenIterator ti = createTokenIterator(hit); + boolean keepalive = false; + while (ti.hasNext()) { + final String token = ti.nextToken(); + if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) { + return false; + } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) { + // continue the loop, there may be a "close" afterwards + keepalive = true; + } + } + if (keepalive) + { + return true; + // neither "close" nor "keep-alive", use default policy + } + + } catch (final ParseException px) { + // invalid connection header means no persistent connection + // we don't have logging in HttpCore, so the exception is lost + return false; + } + } + + // default since HTTP/1.1 is persistent, before it was non-persistent + return !ver.lessEquals(HttpVersion.HTTP_1_0); + } + + + /** + * Creates a token iterator from a header iterator. + * This method can be overridden to replace the implementation of + * the token iterator. + * + * @param hit the header iterator + * + * @return the token iterator + */ + protected TokenIterator createTokenIterator(final HeaderIterator hit) { + return new BasicTokenIterator(hit); + } + + private boolean canResponseHaveBody(final HttpResponse response) { + final int status = response.getStatusLine().getStatusCode(); + return status >= HttpStatus.SC_OK + && status != HttpStatus.SC_NO_CONTENT + && status != HttpStatus.SC_NOT_MODIFIED + && status != HttpStatus.SC_RESET_CONTENT; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpClientConnection.java new file mode 100644 index 000000000..adb597863 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpClientConnection.java @@ -0,0 +1,70 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.net.Socket; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of a client-side HTTP connection. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultBHttpClientConnection} + */ +@NotThreadSafe +@Deprecated +public class DefaultHttpClientConnection extends SocketHttpClientConnection { + + public DefaultHttpClientConnection() { + super(); + } + + @Override + public void bind( + final Socket socket, + final HttpParams params) throws IOException { + Args.notNull(socket, "Socket"); + Args.notNull(params, "HTTP parameters"); + assertNotOpen(); + socket.setTcpNoDelay(params.getBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)); + socket.setSoTimeout(params.getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0)); + socket.setKeepAlive(params.getBooleanParameter(CoreConnectionPNames.SO_KEEPALIVE, false)); + final int linger = params.getIntParameter(CoreConnectionPNames.SO_LINGER, -1); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + super.bind(socket, params); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpRequestFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpRequestFactory.java new file mode 100644 index 000000000..0491e94bd --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpRequestFactory.java @@ -0,0 +1,109 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestFactory; +import ch.boye.httpclientandroidlib.MethodNotSupportedException; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.message.BasicHttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default factory for creating {@link HttpRequest} objects. + * + * @since 4.0 + */ +@Immutable +public class DefaultHttpRequestFactory implements HttpRequestFactory { + + public static final DefaultHttpRequestFactory INSTANCE = new DefaultHttpRequestFactory(); + + private static final String[] RFC2616_COMMON_METHODS = { + "GET" + }; + + private static final String[] RFC2616_ENTITY_ENC_METHODS = { + "POST", + "PUT" + }; + + private static final String[] RFC2616_SPECIAL_METHODS = { + "HEAD", + "OPTIONS", + "DELETE", + "TRACE", + "CONNECT" + }; + + + public DefaultHttpRequestFactory() { + super(); + } + + private static boolean isOneOf(final String[] methods, final String method) { + for (final String method2 : methods) { + if (method2.equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + + public HttpRequest newHttpRequest(final RequestLine requestline) + throws MethodNotSupportedException { + Args.notNull(requestline, "Request line"); + final String method = requestline.getMethod(); + if (isOneOf(RFC2616_COMMON_METHODS, method)) { + return new BasicHttpRequest(requestline); + } else if (isOneOf(RFC2616_ENTITY_ENC_METHODS, method)) { + return new BasicHttpEntityEnclosingRequest(requestline); + } else if (isOneOf(RFC2616_SPECIAL_METHODS, method)) { + return new BasicHttpRequest(requestline); + } else { + throw new MethodNotSupportedException(method + " method not supported"); + } + } + + public HttpRequest newHttpRequest(final String method, final String uri) + throws MethodNotSupportedException { + if (isOneOf(RFC2616_COMMON_METHODS, method)) { + return new BasicHttpRequest(method, uri); + } else if (isOneOf(RFC2616_ENTITY_ENC_METHODS, method)) { + return new BasicHttpEntityEnclosingRequest(method, uri); + } else if (isOneOf(RFC2616_SPECIAL_METHODS, method)) { + return new BasicHttpRequest(method, uri); + } else { + throw new MethodNotSupportedException(method + + " method not supported"); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpResponseFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpResponseFactory.java new file mode 100644 index 000000000..21511b9ff --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpResponseFactory.java @@ -0,0 +1,109 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.ReasonPhraseCatalog; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.message.BasicStatusLine; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default factory for creating {@link HttpResponse} objects. + * + * @since 4.0 + */ +@Immutable +public class DefaultHttpResponseFactory implements HttpResponseFactory { + + public static final DefaultHttpResponseFactory INSTANCE = new DefaultHttpResponseFactory(); + + /** The catalog for looking up reason phrases. */ + protected final ReasonPhraseCatalog reasonCatalog; + + + /** + * Creates a new response factory with the given catalog. + * + * @param catalog the catalog of reason phrases + */ + public DefaultHttpResponseFactory(final ReasonPhraseCatalog catalog) { + this.reasonCatalog = Args.notNull(catalog, "Reason phrase catalog"); + } + + /** + * Creates a new response factory with the default catalog. + * The default catalog is {@link EnglishReasonPhraseCatalog}. + */ + public DefaultHttpResponseFactory() { + this(EnglishReasonPhraseCatalog.INSTANCE); + } + + + // non-javadoc, see interface HttpResponseFactory + public HttpResponse newHttpResponse( + final ProtocolVersion ver, + final int status, + final HttpContext context) { + Args.notNull(ver, "HTTP version"); + final Locale loc = determineLocale(context); + final String reason = this.reasonCatalog.getReason(status, loc); + final StatusLine statusline = new BasicStatusLine(ver, status, reason); + return new BasicHttpResponse(statusline, this.reasonCatalog, loc); + } + + + // non-javadoc, see interface HttpResponseFactory + public HttpResponse newHttpResponse( + final StatusLine statusline, + final HttpContext context) { + Args.notNull(statusline, "Status line"); + return new BasicHttpResponse(statusline, this.reasonCatalog, determineLocale(context)); + } + + /** + * Determines the locale of the response. + * The implementation in this class always returns the default locale. + * + * @param context the context from which to determine the locale, or + * <code>null</code> to use the default locale + * + * @return the locale for the response, never <code>null</code> + */ + protected Locale determineLocale(final HttpContext context) { + return Locale.getDefault(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpServerConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpServerConnection.java new file mode 100644 index 000000000..8162e6107 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/DefaultHttpServerConnection.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.net.Socket; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of a server-side HTTP connection. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultBHttpServerConnection} + */ +@NotThreadSafe +@Deprecated +public class DefaultHttpServerConnection extends SocketHttpServerConnection { + + public DefaultHttpServerConnection() { + super(); + } + + @Override + public void bind(final Socket socket, final HttpParams params) throws IOException { + Args.notNull(socket, "Socket"); + Args.notNull(params, "HTTP parameters"); + assertNotOpen(); + socket.setTcpNoDelay(params.getBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)); + socket.setSoTimeout(params.getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0)); + socket.setKeepAlive(params.getBooleanParameter(CoreConnectionPNames.SO_KEEPALIVE, false)); + final int linger = params.getIntParameter(CoreConnectionPNames.SO_LINGER, -1); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + super.bind(socket, params); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/EnglishReasonPhraseCatalog.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/EnglishReasonPhraseCatalog.java new file mode 100644 index 000000000..51e435c0a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/EnglishReasonPhraseCatalog.java @@ -0,0 +1,224 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.ReasonPhraseCatalog; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * English reason phrases for HTTP status codes. + * All status codes defined in RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and + * RFC2518 (WebDAV) are supported. + * + * @since 4.0 + */ +@Immutable +public class EnglishReasonPhraseCatalog implements ReasonPhraseCatalog { + + // static array with english reason phrases defined below + + /** + * The default instance of this catalog. + * This catalog is thread safe, so there typically + * is no need to create other instances. + */ + public final static EnglishReasonPhraseCatalog INSTANCE = + new EnglishReasonPhraseCatalog(); + + + /** + * Restricted default constructor, for derived classes. + * If you need an instance of this class, use {@link #INSTANCE INSTANCE}. + */ + protected EnglishReasonPhraseCatalog() { + // no body + } + + + /** + * Obtains the reason phrase for a status code. + * + * @param status the status code, in the range 100-599 + * @param loc ignored + * + * @return the reason phrase, or <code>null</code> + */ + public String getReason(final int status, final Locale loc) { + Args.check(status >= 100 && status < 600, "Unknown category for status code " + status); + final int category = status / 100; + final int subcode = status - 100*category; + + String reason = null; + if (REASON_PHRASES[category].length > subcode) { + reason = REASON_PHRASES[category][subcode]; + } + + return reason; + } + + + /** Reason phrases lookup table. */ + private static final String[][] REASON_PHRASES = new String[][]{ + null, + new String[3], // 1xx + new String[8], // 2xx + new String[8], // 3xx + new String[25], // 4xx + new String[8] // 5xx + }; + + + + /** + * Stores the given reason phrase, by status code. + * Helper method to initialize the static lookup table. + * + * @param status the status code for which to define the phrase + * @param reason the reason phrase for this status code + */ + private static void setReason(final int status, final String reason) { + final int category = status / 100; + final int subcode = status - 100*category; + REASON_PHRASES[category][subcode] = reason; + } + + + // ----------------------------------------------------- Static Initializer + + /** Set up status code to "reason phrase" map. */ + static { + // HTTP 1.0 Server status codes -- see RFC 1945 + setReason(HttpStatus.SC_OK, + "OK"); + setReason(HttpStatus.SC_CREATED, + "Created"); + setReason(HttpStatus.SC_ACCEPTED, + "Accepted"); + setReason(HttpStatus.SC_NO_CONTENT, + "No Content"); + setReason(HttpStatus.SC_MOVED_PERMANENTLY, + "Moved Permanently"); + setReason(HttpStatus.SC_MOVED_TEMPORARILY, + "Moved Temporarily"); + setReason(HttpStatus.SC_NOT_MODIFIED, + "Not Modified"); + setReason(HttpStatus.SC_BAD_REQUEST, + "Bad Request"); + setReason(HttpStatus.SC_UNAUTHORIZED, + "Unauthorized"); + setReason(HttpStatus.SC_FORBIDDEN, + "Forbidden"); + setReason(HttpStatus.SC_NOT_FOUND, + "Not Found"); + setReason(HttpStatus.SC_INTERNAL_SERVER_ERROR, + "Internal Server Error"); + setReason(HttpStatus.SC_NOT_IMPLEMENTED, + "Not Implemented"); + setReason(HttpStatus.SC_BAD_GATEWAY, + "Bad Gateway"); + setReason(HttpStatus.SC_SERVICE_UNAVAILABLE, + "Service Unavailable"); + + // HTTP 1.1 Server status codes -- see RFC 2048 + setReason(HttpStatus.SC_CONTINUE, + "Continue"); + setReason(HttpStatus.SC_TEMPORARY_REDIRECT, + "Temporary Redirect"); + setReason(HttpStatus.SC_METHOD_NOT_ALLOWED, + "Method Not Allowed"); + setReason(HttpStatus.SC_CONFLICT, + "Conflict"); + setReason(HttpStatus.SC_PRECONDITION_FAILED, + "Precondition Failed"); + setReason(HttpStatus.SC_REQUEST_TOO_LONG, + "Request Too Long"); + setReason(HttpStatus.SC_REQUEST_URI_TOO_LONG, + "Request-URI Too Long"); + setReason(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, + "Unsupported Media Type"); + setReason(HttpStatus.SC_MULTIPLE_CHOICES, + "Multiple Choices"); + setReason(HttpStatus.SC_SEE_OTHER, + "See Other"); + setReason(HttpStatus.SC_USE_PROXY, + "Use Proxy"); + setReason(HttpStatus.SC_PAYMENT_REQUIRED, + "Payment Required"); + setReason(HttpStatus.SC_NOT_ACCEPTABLE, + "Not Acceptable"); + setReason(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, + "Proxy Authentication Required"); + setReason(HttpStatus.SC_REQUEST_TIMEOUT, + "Request Timeout"); + + setReason(HttpStatus.SC_SWITCHING_PROTOCOLS, + "Switching Protocols"); + setReason(HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, + "Non Authoritative Information"); + setReason(HttpStatus.SC_RESET_CONTENT, + "Reset Content"); + setReason(HttpStatus.SC_PARTIAL_CONTENT, + "Partial Content"); + setReason(HttpStatus.SC_GATEWAY_TIMEOUT, + "Gateway Timeout"); + setReason(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED, + "Http Version Not Supported"); + setReason(HttpStatus.SC_GONE, + "Gone"); + setReason(HttpStatus.SC_LENGTH_REQUIRED, + "Length Required"); + setReason(HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE, + "Requested Range Not Satisfiable"); + setReason(HttpStatus.SC_EXPECTATION_FAILED, + "Expectation Failed"); + + // WebDAV Server-specific status codes + setReason(HttpStatus.SC_PROCESSING, + "Processing"); + setReason(HttpStatus.SC_MULTI_STATUS, + "Multi-Status"); + setReason(HttpStatus.SC_UNPROCESSABLE_ENTITY, + "Unprocessable Entity"); + setReason(HttpStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE, + "Insufficient Space On Resource"); + setReason(HttpStatus.SC_METHOD_FAILURE, + "Method Failure"); + setReason(HttpStatus.SC_LOCKED, + "Locked"); + setReason(HttpStatus.SC_INSUFFICIENT_STORAGE, + "Insufficient Storage"); + setReason(HttpStatus.SC_FAILED_DEPENDENCY, + "Failed Dependency"); + } + + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/HttpConnectionMetricsImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/HttpConnectionMetricsImpl.java new file mode 100644 index 000000000..bfc415082 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/HttpConnectionMetricsImpl.java @@ -0,0 +1,148 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.util.HashMap; +import java.util.Map; + +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; + +/** + * Default implementation of the {@link HttpConnectionMetrics} interface. + * + * @since 4.0 + */ +@NotThreadSafe +public class HttpConnectionMetricsImpl implements HttpConnectionMetrics { + + public static final String REQUEST_COUNT = "http.request-count"; + public static final String RESPONSE_COUNT = "http.response-count"; + public static final String SENT_BYTES_COUNT = "http.sent-bytes-count"; + public static final String RECEIVED_BYTES_COUNT = "http.received-bytes-count"; + + private final HttpTransportMetrics inTransportMetric; + private final HttpTransportMetrics outTransportMetric; + private long requestCount = 0; + private long responseCount = 0; + + /** + * The cache map for all metrics values. + */ + private Map<String, Object> metricsCache; + + public HttpConnectionMetricsImpl( + final HttpTransportMetrics inTransportMetric, + final HttpTransportMetrics outTransportMetric) { + super(); + this.inTransportMetric = inTransportMetric; + this.outTransportMetric = outTransportMetric; + } + + /* ------------------ Public interface method -------------------------- */ + + public long getReceivedBytesCount() { + if (this.inTransportMetric != null) { + return this.inTransportMetric.getBytesTransferred(); + } else { + return -1; + } + } + + public long getSentBytesCount() { + if (this.outTransportMetric != null) { + return this.outTransportMetric.getBytesTransferred(); + } else { + return -1; + } + } + + public long getRequestCount() { + return this.requestCount; + } + + public void incrementRequestCount() { + this.requestCount++; + } + + public long getResponseCount() { + return this.responseCount; + } + + public void incrementResponseCount() { + this.responseCount++; + } + + public Object getMetric(final String metricName) { + Object value = null; + if (this.metricsCache != null) { + value = this.metricsCache.get(metricName); + } + if (value == null) { + if (REQUEST_COUNT.equals(metricName)) { + value = Long.valueOf(requestCount); + } else if (RESPONSE_COUNT.equals(metricName)) { + value = Long.valueOf(responseCount); + } else if (RECEIVED_BYTES_COUNT.equals(metricName)) { + if (this.inTransportMetric != null) { + return Long.valueOf(this.inTransportMetric.getBytesTransferred()); + } else { + return null; + } + } else if (SENT_BYTES_COUNT.equals(metricName)) { + if (this.outTransportMetric != null) { + return Long.valueOf(this.outTransportMetric.getBytesTransferred()); + } else { + return null; + } + } + } + return value; + } + + public void setMetric(final String metricName, final Object obj) { + if (this.metricsCache == null) { + this.metricsCache = new HashMap<String, Object>(); + } + this.metricsCache.put(metricName, obj); + } + + public void reset() { + if (this.outTransportMetric != null) { + this.outTransportMetric.reset(); + } + if (this.inTransportMetric != null) { + this.inTransportMetric.reset(); + } + this.requestCount = 0; + this.responseCount = 0; + this.metricsCache = null; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/NoConnectionReuseStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/NoConnectionReuseStrategy.java new file mode 100644 index 000000000..fdf312844 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/NoConnectionReuseStrategy.java @@ -0,0 +1,53 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * A strategy that never re-uses a connection. + * + * @since 4.0 + */ +@Immutable +public class NoConnectionReuseStrategy implements ConnectionReuseStrategy { + + public static final NoConnectionReuseStrategy INSTANCE = new NoConnectionReuseStrategy(); + + public NoConnectionReuseStrategy() { + super(); + } + + public boolean keepAlive(final HttpResponse response, final HttpContext context) { + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/SocketHttpClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/SocketHttpClientConnection.java new file mode 100644 index 000000000..72f2f7e87 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/SocketHttpClientConnection.java @@ -0,0 +1,283 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; + +import ch.boye.httpclientandroidlib.HttpInetConnection; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.impl.io.SocketInputBuffer; +import ch.boye.httpclientandroidlib.impl.io.SocketOutputBuffer; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Implementation of a client-side HTTP connection that can be bound to an + * arbitrary {@link Socket} for receiving data from and transmitting data to + * a remote server. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultBHttpClientConnection} + */ +@NotThreadSafe +@Deprecated +public class SocketHttpClientConnection + extends AbstractHttpClientConnection implements HttpInetConnection { + + private volatile boolean open; + private volatile Socket socket = null; + + public SocketHttpClientConnection() { + super(); + } + + protected void assertNotOpen() { + Asserts.check(!this.open, "Connection is already open"); + } + + @Override + protected void assertOpen() { + Asserts.check(this.open, "Connection is not open"); + } + + /** + * Creates an instance of {@link SocketInputBuffer} to be used for + * receiving data from the given {@link Socket}. + * <p> + * This method can be overridden in a super class in order to provide + * a custom implementation of {@link SessionInputBuffer} interface. + * + * @see SocketInputBuffer#SocketInputBuffer(Socket, int, HttpParams) + * + * @param socket the socket. + * @param buffersize the buffer size. + * @param params HTTP parameters. + * @return session input buffer. + * @throws IOException in case of an I/O error. + */ + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + return new SocketInputBuffer(socket, buffersize, params); + } + + /** + * Creates an instance of {@link SessionOutputBuffer} to be used for + * sending data to the given {@link Socket}. + * <p> + * This method can be overridden in a super class in order to provide + * a custom implementation of {@link SocketOutputBuffer} interface. + * + * @see SocketOutputBuffer#SocketOutputBuffer(Socket, int, HttpParams) + * + * @param socket the socket. + * @param buffersize the buffer size. + * @param params HTTP parameters. + * @return session output buffer. + * @throws IOException in case of an I/O error. + */ + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + return new SocketOutputBuffer(socket, buffersize, params); + } + + /** + * Binds this connection to the given {@link Socket}. This socket will be + * used by the connection to send and receive data. + * <p> + * This method will invoke {@link #createSessionInputBuffer(Socket, int, HttpParams)} + * and {@link #createSessionOutputBuffer(Socket, int, HttpParams)} methods + * to create session input / output buffers bound to this socket and then + * will invoke {@link #init(SessionInputBuffer, SessionOutputBuffer, HttpParams)} + * method to pass references to those buffers to the underlying HTTP message + * parser and formatter. + * <p> + * After this method's execution the connection status will be reported + * as open and the {@link #isOpen()} will return <code>true</code>. + * + * @param socket the socket. + * @param params HTTP parameters. + * @throws IOException in case of an I/O error. + */ + protected void bind( + final Socket socket, + final HttpParams params) throws IOException { + Args.notNull(socket, "Socket"); + Args.notNull(params, "HTTP parameters"); + this.socket = socket; + + final int buffersize = params.getIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1); + init( + createSessionInputBuffer(socket, buffersize, params), + createSessionOutputBuffer(socket, buffersize, params), + params); + + this.open = true; + } + + public boolean isOpen() { + return this.open; + } + + protected Socket getSocket() { + return this.socket; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(final int timeout) { + assertOpen(); + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (final SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (final SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + final Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + final Socket sock = this.socket; + try { + doFlush(); + try { + try { + sock.shutdownOutput(); + } catch (final IOException ignore) { + } + try { + sock.shutdownInput(); + } catch (final IOException ignore) { + } + } catch (final UnsupportedOperationException ignore) { + // if one isn't supported, the other one isn't either + } + } finally { + sock.close(); + } + } + + private static void formatAddress(final StringBuilder buffer, final SocketAddress socketAddress) { + if (socketAddress instanceof InetSocketAddress) { + final InetSocketAddress addr = ((InetSocketAddress) socketAddress); + buffer.append(addr.getAddress() != null ? addr.getAddress().getHostAddress() : + addr.getAddress()) + .append(':') + .append(addr.getPort()); + } else { + buffer.append(socketAddress); + } + } + + @Override + public String toString() { + if (this.socket != null) { + final StringBuilder buffer = new StringBuilder(); + final SocketAddress remoteAddress = this.socket.getRemoteSocketAddress(); + final SocketAddress localAddress = this.socket.getLocalSocketAddress(); + if (remoteAddress != null && localAddress != null) { + formatAddress(buffer, localAddress); + buffer.append("<->"); + formatAddress(buffer, remoteAddress); + } + return buffer.toString(); + } else { + return super.toString(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/SocketHttpServerConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/SocketHttpServerConnection.java new file mode 100644 index 000000000..0a74b94a3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/SocketHttpServerConnection.java @@ -0,0 +1,271 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; + +import ch.boye.httpclientandroidlib.HttpInetConnection; +import ch.boye.httpclientandroidlib.impl.io.SocketInputBuffer; +import ch.boye.httpclientandroidlib.impl.io.SocketOutputBuffer; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +@Deprecated +public class SocketHttpServerConnection extends + AbstractHttpServerConnection implements HttpInetConnection { + + private volatile boolean open; + private volatile Socket socket = null; + + public SocketHttpServerConnection() { + super(); + } + + protected void assertNotOpen() { + Asserts.check(!this.open, "Connection is already open"); + } + + @Override + protected void assertOpen() { + Asserts.check(this.open, "Connection is not open"); + } + + /** + * Creates an instance of {@link SocketInputBuffer} to be used for + * receiving data from the given {@link Socket}. + * <p> + * This method can be overridden in a super class in order to provide + * a custom implementation of {@link SessionInputBuffer} interface. + * + * @see SocketInputBuffer#SocketInputBuffer(Socket, int, HttpParams) + * + * @param socket the socket. + * @param buffersize the buffer size. + * @param params HTTP parameters. + * @return session input buffer. + * @throws IOException in case of an I/O error. + */ + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + return new SocketInputBuffer(socket, buffersize, params); + } + + /** + * Creates an instance of {@link SessionOutputBuffer} to be used for + * sending data to the given {@link Socket}. + * <p> + * This method can be overridden in a super class in order to provide + * a custom implementation of {@link SocketOutputBuffer} interface. + * + * @see SocketOutputBuffer#SocketOutputBuffer(Socket, int, HttpParams) + * + * @param socket the socket. + * @param buffersize the buffer size. + * @param params HTTP parameters. + * @return session output buffer. + * @throws IOException in case of an I/O error. + */ + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + return new SocketOutputBuffer(socket, buffersize, params); + } + + /** + * Binds this connection to the given {@link Socket}. This socket will be + * used by the connection to send and receive data. + * <p> + * This method will invoke {@link #createSessionInputBuffer(Socket, int, HttpParams)} + * and {@link #createSessionOutputBuffer(Socket, int, HttpParams)} methods + * to create session input / output buffers bound to this socket and then + * will invoke {@link #init(SessionInputBuffer, SessionOutputBuffer, HttpParams)} + * method to pass references to those buffers to the underlying HTTP message + * parser and formatter. + * <p> + * After this method's execution the connection status will be reported + * as open and the {@link #isOpen()} will return <code>true</code>. + * + * @param socket the socket. + * @param params HTTP parameters. + * @throws IOException in case of an I/O error. + */ + protected void bind(final Socket socket, final HttpParams params) throws IOException { + Args.notNull(socket, "Socket"); + Args.notNull(params, "HTTP parameters"); + this.socket = socket; + + final int buffersize = params.getIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1); + init( + createSessionInputBuffer(socket, buffersize, params), + createSessionOutputBuffer(socket, buffersize, params), + params); + + this.open = true; + } + + protected Socket getSocket() { + return this.socket; + } + + public boolean isOpen() { + return this.open; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(final int timeout) { + assertOpen(); + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (final SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (final SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + final Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + this.open = false; + final Socket sock = this.socket; + try { + doFlush(); + try { + try { + sock.shutdownOutput(); + } catch (final IOException ignore) { + } + try { + sock.shutdownInput(); + } catch (final IOException ignore) { + } + } catch (final UnsupportedOperationException ignore) { + // if one isn't supported, the other one isn't either + } + } finally { + sock.close(); + } + } + + private static void formatAddress(final StringBuilder buffer, final SocketAddress socketAddress) { + if (socketAddress instanceof InetSocketAddress) { + final InetSocketAddress addr = ((InetSocketAddress) socketAddress); + buffer.append(addr.getAddress() != null ? addr.getAddress().getHostAddress() : + addr.getAddress()) + .append(':') + .append(addr.getPort()); + } else { + buffer.append(socketAddress); + } + } + + @Override + public String toString() { + if (this.socket != null) { + final StringBuilder buffer = new StringBuilder(); + final SocketAddress remoteAddress = this.socket.getRemoteSocketAddress(); + final SocketAddress localAddress = this.socket.getLocalSocketAddress(); + if (remoteAddress != null && localAddress != null) { + formatAddress(buffer, localAddress); + buffer.append("<->"); + formatAddress(buffer, remoteAddress); + } + return buffer.toString(); + } else { + return super.toString(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/AuthSchemeBase.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/AuthSchemeBase.java new file mode 100644 index 000000000..e1b98db9e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/AuthSchemeBase.java @@ -0,0 +1,169 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.ChallengeState; +import ch.boye.httpclientandroidlib.auth.ContextAwareAuthScheme; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract authentication scheme class that serves as a basis + * for all authentication schemes supported by HttpClient. This class + * defines the generic way of parsing an authentication challenge. It + * does not make any assumptions regarding the format of the challenge + * nor does it impose any specific way of responding to that challenge. + * + * + * @since 4.0 + */ +@NotThreadSafe +public abstract class AuthSchemeBase implements ContextAwareAuthScheme { + + private ChallengeState challengeState; + + /** + * Creates an instance of <tt>AuthSchemeBase</tt> with the given challenge + * state. + * + * @since 4.2 + * + * @deprecated (4.3) do not use. + */ + @Deprecated + public AuthSchemeBase(final ChallengeState challengeState) { + super(); + this.challengeState = challengeState; + } + + public AuthSchemeBase() { + super(); + } + + /** + * Processes the given challenge token. Some authentication schemes + * may involve multiple challenge-response exchanges. Such schemes must be able + * to maintain the state information when dealing with sequential challenges + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(final Header header) throws MalformedChallengeException { + Args.notNull(header, "Header"); + final String authheader = header.getName(); + if (authheader.equalsIgnoreCase(AUTH.WWW_AUTH)) { + this.challengeState = ChallengeState.TARGET; + } else if (authheader.equalsIgnoreCase(AUTH.PROXY_AUTH)) { + this.challengeState = ChallengeState.PROXY; + } else { + throw new MalformedChallengeException("Unexpected header name: " + authheader); + } + + final CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int endIndex = pos; + final String s = buffer.substring(beginIndex, endIndex); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException("Invalid scheme identifier: " + s); + } + + parseChallenge(buffer, pos, buffer.length()); + } + + + @SuppressWarnings("deprecation") + public Header authenticate( + final Credentials credentials, + final HttpRequest request, + final HttpContext context) throws AuthenticationException { + return authenticate(credentials, request); + } + + protected abstract void parseChallenge( + CharArrayBuffer buffer, int beginIndex, int endIndex) throws MalformedChallengeException; + + /** + * Returns <code>true</code> if authenticating against a proxy, <code>false</code> + * otherwise. + */ + public boolean isProxy() { + return this.challengeState != null && this.challengeState == ChallengeState.PROXY; + } + + /** + * Returns {@link ChallengeState} value or <code>null</code> if unchallenged. + * + * @since 4.2 + */ + public ChallengeState getChallengeState() { + return this.challengeState; + } + + @Override + public String toString() { + final String name = getSchemeName(); + if (name != null) { + return name.toUpperCase(Locale.ENGLISH); + } else { + return super.toString(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/BasicScheme.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/BasicScheme.java new file mode 100644 index 000000000..c58b5e582 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/BasicScheme.java @@ -0,0 +1,219 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.nio.charset.Charset; + +import org.mozilla.apache.commons.codec.binary.Base64; +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.ChallengeState; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; +import ch.boye.httpclientandroidlib.util.EncodingUtils; + +/** + * Basic authentication scheme as defined in RFC 2617. + * + * @since 4.0 + */ +@NotThreadSafe +public class BasicScheme extends RFC2617Scheme { + +/* Base64 instance removed by HttpClient for Android script. */ + /** Whether the basic authentication process is complete */ + private boolean complete; + + /** + * @since 4.3 + */ + public BasicScheme(final Charset credentialsCharset) { + super(credentialsCharset); +/* Base64 instance removed by HttpClient for Android script. */ + this.complete = false; + } + + /** + * Creates an instance of <tt>BasicScheme</tt> with the given challenge + * state. + * + * @since 4.2 + * + * @deprecated (4.3) do not use. + */ + @Deprecated + public BasicScheme(final ChallengeState challengeState) { + super(challengeState); +/* Base64 instance removed by HttpClient for Android script. */ + } + + public BasicScheme() { + this(Consts.ASCII); + } + + /** + * Returns textual designation of the basic authentication scheme. + * + * @return <code>basic</code> + */ + public String getSchemeName() { + return "basic"; + } + + /** + * Processes the Basic challenge. + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + @Override + public void processChallenge( + final Header header) throws MalformedChallengeException { + super.processChallenge(header); + this.complete = true; + } + + /** + * Tests if the Basic authentication process has been completed. + * + * @return <tt>true</tt> if Basic authorization has been processed, + * <tt>false</tt> otherwise. + */ + public boolean isComplete() { + return this.complete; + } + + /** + * Returns <tt>false</tt>. Basic authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; + } + + /** + * @deprecated (4.2) Use {@link ch.boye.httpclientandroidlib.auth.ContextAwareAuthScheme#authenticate( + * Credentials, HttpRequest, ch.boye.httpclientandroidlib.protocol.HttpContext)} + */ + @Deprecated + public Header authenticate( + final Credentials credentials, final HttpRequest request) throws AuthenticationException { + return authenticate(credentials, request, new BasicHttpContext()); + } + + /** + * Produces basic authorization header for the given set of {@link Credentials}. + * + * @param credentials The set of credentials to be used for authentication + * @param request The request being authenticated + * @throws ch.boye.httpclientandroidlib.auth.InvalidCredentialsException if authentication + * credentials are not valid or not applicable for this authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a basic authorization string + */ + @Override + public Header authenticate( + final Credentials credentials, + final HttpRequest request, + final HttpContext context) throws AuthenticationException { + + Args.notNull(credentials, "Credentials"); + Args.notNull(request, "HTTP request"); + final StringBuilder tmp = new StringBuilder(); + tmp.append(credentials.getUserPrincipal().getName()); + tmp.append(":"); + tmp.append((credentials.getPassword() == null) ? "null" : credentials.getPassword()); + + final byte[] base64password = Base64.encodeBase64( + EncodingUtils.getBytes(tmp.toString(), getCredentialsCharset(request))); + + final CharArrayBuffer buffer = new CharArrayBuffer(32); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Basic "); + buffer.append(base64password, 0, base64password.length); + + return new BufferedHeader(buffer); + } + + /** + * Returns a basic <tt>Authorization</tt> header value for the given + * {@link Credentials} and charset. + * + * @param credentials The credentials to encode. + * @param charset The charset to use for encoding the credentials + * + * @return a basic authorization header + * + * @deprecated (4.3) use {@link #authenticate(Credentials, HttpRequest, HttpContext)}. + */ + @Deprecated + public static Header authenticate( + final Credentials credentials, + final String charset, + final boolean proxy) { + Args.notNull(credentials, "Credentials"); + Args.notNull(charset, "charset"); + + final StringBuilder tmp = new StringBuilder(); + tmp.append(credentials.getUserPrincipal().getName()); + tmp.append(":"); + tmp.append((credentials.getPassword() == null) ? "null" : credentials.getPassword()); + + final byte[] base64password = Base64.encodeBase64( + EncodingUtils.getBytes(tmp.toString(), charset)); + + final CharArrayBuffer buffer = new CharArrayBuffer(32); + if (proxy) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Basic "); + buffer.append(base64password, 0, base64password.length); + + return new BufferedHeader(buffer); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/BasicSchemeFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/BasicSchemeFactory.java new file mode 100644 index 000000000..c4eb8c6be --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/BasicSchemeFactory.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.auth; + +import java.nio.charset.Charset; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeFactory; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link AuthSchemeProvider} implementation that creates and initializes + * {@link BasicScheme} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class BasicSchemeFactory implements AuthSchemeFactory, AuthSchemeProvider { + + private final Charset charset; + + /** + * @since 4.3 + */ + public BasicSchemeFactory(final Charset charset) { + super(); + this.charset = charset; + } + + public BasicSchemeFactory() { + this(null); + } + + public AuthScheme newInstance(final HttpParams params) { + return new BasicScheme(); + } + + public AuthScheme create(final HttpContext context) { + return new BasicScheme(this.charset); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/DigestScheme.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/DigestScheme.java new file mode 100644 index 000000000..542f6236b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/DigestScheme.java @@ -0,0 +1,489 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.StringTokenizer; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.ChallengeState; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.message.BasicHeaderValueFormatter; +import ch.boye.httpclientandroidlib.message.BasicNameValuePair; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; +import ch.boye.httpclientandroidlib.util.EncodingUtils; + +/** + * Digest authentication scheme as defined in RFC 2617. + * Both MD5 (default) and MD5-sess are supported. + * Currently only qop=auth or no qop is supported. qop=auth-int + * is unsupported. If auth and auth-int are provided, auth is + * used. + * <p/> + * Since the digest username is included as clear text in the generated + * Authentication header, the charset of the username must be compatible + * with the HTTP element charset used by the connection. + * + * @since 4.0 + */ +@NotThreadSafe +public class DigestScheme extends RFC2617Scheme { + + /** + * Hexa values used when creating 32 character long digest in HTTP DigestScheme + * in case of authentication. + * + * @see #encode(byte[]) + */ + private static final char[] HEXADECIMAL = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' + }; + + /** Whether the digest authentication process is complete */ + private boolean complete; + + private static final int QOP_UNKNOWN = -1; + private static final int QOP_MISSING = 0; + private static final int QOP_AUTH_INT = 1; + private static final int QOP_AUTH = 2; + + private String lastNonce; + private long nounceCount; + private String cnonce; + private String a1; + private String a2; + + /** + * @since 4.3 + */ + public DigestScheme(final Charset credentialsCharset) { + super(credentialsCharset); + this.complete = false; + } + + /** + * Creates an instance of <tt>DigestScheme</tt> with the given challenge + * state. + * + * @since 4.2 + * + * @deprecated (4.3) do not use. + */ + @Deprecated + public DigestScheme(final ChallengeState challengeState) { + super(challengeState); + } + + public DigestScheme() { + this(Consts.ASCII); + } + + /** + * Processes the Digest challenge. + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + @Override + public void processChallenge( + final Header header) throws MalformedChallengeException { + super.processChallenge(header); + this.complete = true; + } + + /** + * Tests if the Digest authentication process has been completed. + * + * @return <tt>true</tt> if Digest authorization has been processed, + * <tt>false</tt> otherwise. + */ + public boolean isComplete() { + final String s = getParameter("stale"); + if ("true".equalsIgnoreCase(s)) { + return false; + } else { + return this.complete; + } + } + + /** + * Returns textual designation of the digest authentication scheme. + * + * @return <code>digest</code> + */ + public String getSchemeName() { + return "digest"; + } + + /** + * Returns <tt>false</tt>. Digest authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; + } + + public void overrideParamter(final String name, final String value) { + getParameters().put(name, value); + } + + /** + * @deprecated (4.2) Use {@link ch.boye.httpclientandroidlib.auth.ContextAwareAuthScheme#authenticate( + * Credentials, HttpRequest, ch.boye.httpclientandroidlib.protocol.HttpContext)} + */ + @Deprecated + public Header authenticate( + final Credentials credentials, final HttpRequest request) throws AuthenticationException { + return authenticate(credentials, request, new BasicHttpContext()); + } + + /** + * Produces a digest authorization string for the given set of + * {@link Credentials}, method name and URI. + * + * @param credentials A set of credentials to be used for athentication + * @param request The request being authenticated + * + * @throws ch.boye.httpclientandroidlib.auth.InvalidCredentialsException if authentication credentials + * are not valid or not applicable for this authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a digest authorization string + */ + @Override + public Header authenticate( + final Credentials credentials, + final HttpRequest request, + final HttpContext context) throws AuthenticationException { + + Args.notNull(credentials, "Credentials"); + Args.notNull(request, "HTTP request"); + if (getParameter("realm") == null) { + throw new AuthenticationException("missing realm in challenge"); + } + if (getParameter("nonce") == null) { + throw new AuthenticationException("missing nonce in challenge"); + } + // Add method name and request-URI to the parameter map + getParameters().put("methodname", request.getRequestLine().getMethod()); + getParameters().put("uri", request.getRequestLine().getUri()); + final String charset = getParameter("charset"); + if (charset == null) { + getParameters().put("charset", getCredentialsCharset(request)); + } + return createDigestHeader(credentials, request); + } + + private static MessageDigest createMessageDigest( + final String digAlg) throws UnsupportedDigestAlgorithmException { + try { + return MessageDigest.getInstance(digAlg); + } catch (final Exception e) { + throw new UnsupportedDigestAlgorithmException( + "Unsupported algorithm in HTTP Digest authentication: " + + digAlg); + } + } + + /** + * Creates digest-response header as defined in RFC2617. + * + * @param credentials User credentials + * + * @return The digest-response as String. + */ + private Header createDigestHeader( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + final String uri = getParameter("uri"); + final String realm = getParameter("realm"); + final String nonce = getParameter("nonce"); + final String opaque = getParameter("opaque"); + final String method = getParameter("methodname"); + String algorithm = getParameter("algorithm"); + // If an algorithm is not specified, default to MD5. + if (algorithm == null) { + algorithm = "MD5"; + } + + final Set<String> qopset = new HashSet<String>(8); + int qop = QOP_UNKNOWN; + final String qoplist = getParameter("qop"); + if (qoplist != null) { + final StringTokenizer tok = new StringTokenizer(qoplist, ","); + while (tok.hasMoreTokens()) { + final String variant = tok.nextToken().trim(); + qopset.add(variant.toLowerCase(Locale.ENGLISH)); + } + if (request instanceof HttpEntityEnclosingRequest && qopset.contains("auth-int")) { + qop = QOP_AUTH_INT; + } else if (qopset.contains("auth")) { + qop = QOP_AUTH; + } + } else { + qop = QOP_MISSING; + } + + if (qop == QOP_UNKNOWN) { + throw new AuthenticationException("None of the qop methods is supported: " + qoplist); + } + + String charset = getParameter("charset"); + if (charset == null) { + charset = "ISO-8859-1"; + } + + String digAlg = algorithm; + if (digAlg.equalsIgnoreCase("MD5-sess")) { + digAlg = "MD5"; + } + + final MessageDigest digester; + try { + digester = createMessageDigest(digAlg); + } catch (final UnsupportedDigestAlgorithmException ex) { + throw new AuthenticationException("Unsuppported digest algorithm: " + digAlg); + } + + final String uname = credentials.getUserPrincipal().getName(); + final String pwd = credentials.getPassword(); + + if (nonce.equals(this.lastNonce)) { + nounceCount++; + } else { + nounceCount = 1; + cnonce = null; + lastNonce = nonce; + } + final StringBuilder sb = new StringBuilder(256); + final Formatter formatter = new Formatter(sb, Locale.US); + formatter.format("%08x", nounceCount); + formatter.close(); + final String nc = sb.toString(); + + if (cnonce == null) { + cnonce = createCnonce(); + } + + a1 = null; + a2 = null; + // 3.2.2.2: Calculating digest + if (algorithm.equalsIgnoreCase("MD5-sess")) { + // H( unq(username-value) ":" unq(realm-value) ":" passwd ) + // ":" unq(nonce-value) + // ":" unq(cnonce-value) + + // calculated one per session + sb.setLength(0); + sb.append(uname).append(':').append(realm).append(':').append(pwd); + final String checksum = encode(digester.digest(EncodingUtils.getBytes(sb.toString(), charset))); + sb.setLength(0); + sb.append(checksum).append(':').append(nonce).append(':').append(cnonce); + a1 = sb.toString(); + } else { + // unq(username-value) ":" unq(realm-value) ":" passwd + sb.setLength(0); + sb.append(uname).append(':').append(realm).append(':').append(pwd); + a1 = sb.toString(); + } + + final String hasha1 = encode(digester.digest(EncodingUtils.getBytes(a1, charset))); + + if (qop == QOP_AUTH) { + // Method ":" digest-uri-value + a2 = method + ':' + uri; + } else if (qop == QOP_AUTH_INT) { + // Method ":" digest-uri-value ":" H(entity-body) + HttpEntity entity = null; + if (request instanceof HttpEntityEnclosingRequest) { + entity = ((HttpEntityEnclosingRequest) request).getEntity(); + } + if (entity != null && !entity.isRepeatable()) { + // If the entity is not repeatable, try falling back onto QOP_AUTH + if (qopset.contains("auth")) { + qop = QOP_AUTH; + a2 = method + ':' + uri; + } else { + throw new AuthenticationException("Qop auth-int cannot be used with " + + "a non-repeatable entity"); + } + } else { + final HttpEntityDigester entityDigester = new HttpEntityDigester(digester); + try { + if (entity != null) { + entity.writeTo(entityDigester); + } + entityDigester.close(); + } catch (final IOException ex) { + throw new AuthenticationException("I/O error reading entity content", ex); + } + a2 = method + ':' + uri + ':' + encode(entityDigester.getDigest()); + } + } else { + a2 = method + ':' + uri; + } + + final String hasha2 = encode(digester.digest(EncodingUtils.getBytes(a2, charset))); + + // 3.2.2.1 + + final String digestValue; + if (qop == QOP_MISSING) { + sb.setLength(0); + sb.append(hasha1).append(':').append(nonce).append(':').append(hasha2); + digestValue = sb.toString(); + } else { + sb.setLength(0); + sb.append(hasha1).append(':').append(nonce).append(':').append(nc).append(':') + .append(cnonce).append(':').append(qop == QOP_AUTH_INT ? "auth-int" : "auth") + .append(':').append(hasha2); + digestValue = sb.toString(); + } + + final String digest = encode(digester.digest(EncodingUtils.getAsciiBytes(digestValue))); + + final CharArrayBuffer buffer = new CharArrayBuffer(128); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Digest "); + + final List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(20); + params.add(new BasicNameValuePair("username", uname)); + params.add(new BasicNameValuePair("realm", realm)); + params.add(new BasicNameValuePair("nonce", nonce)); + params.add(new BasicNameValuePair("uri", uri)); + params.add(new BasicNameValuePair("response", digest)); + + if (qop != QOP_MISSING) { + params.add(new BasicNameValuePair("qop", qop == QOP_AUTH_INT ? "auth-int" : "auth")); + params.add(new BasicNameValuePair("nc", nc)); + params.add(new BasicNameValuePair("cnonce", cnonce)); + } + // algorithm cannot be null here + params.add(new BasicNameValuePair("algorithm", algorithm)); + if (opaque != null) { + params.add(new BasicNameValuePair("opaque", opaque)); + } + + for (int i = 0; i < params.size(); i++) { + final BasicNameValuePair param = params.get(i); + if (i > 0) { + buffer.append(", "); + } + final String name = param.getName(); + final boolean noQuotes = ("nc".equals(name) || "qop".equals(name) + || "algorithm".equals(name)); + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(buffer, param, !noQuotes); + } + return new BufferedHeader(buffer); + } + + String getCnonce() { + return cnonce; + } + + String getA1() { + return a1; + } + + String getA2() { + return a2; + } + + /** + * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long + * <CODE>String</CODE> according to RFC 2617. + * + * @param binaryData array containing the digest + * @return encoded MD5, or <CODE>null</CODE> if encoding failed + */ + static String encode(final byte[] binaryData) { + final int n = binaryData.length; + final char[] buffer = new char[n * 2]; + for (int i = 0; i < n; i++) { + final int low = (binaryData[i] & 0x0f); + final int high = ((binaryData[i] & 0xf0) >> 4); + buffer[i * 2] = HEXADECIMAL[high]; + buffer[(i * 2) + 1] = HEXADECIMAL[low]; + } + + return new String(buffer); + } + + + /** + * Creates a random cnonce value based on the current time. + * + * @return The cnonce value as String. + */ + public static String createCnonce() { + final SecureRandom rnd = new SecureRandom(); + final byte[] tmp = new byte[8]; + rnd.nextBytes(tmp); + return encode(tmp); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("DIGEST [complete=").append(complete) + .append(", nonce=").append(lastNonce) + .append(", nc=").append(nounceCount) + .append("]"); + return builder.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/DigestSchemeFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/DigestSchemeFactory.java new file mode 100644 index 000000000..0582bed49 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/DigestSchemeFactory.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.auth; + +import java.nio.charset.Charset; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeFactory; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link AuthSchemeProvider} implementation that creates and initializes + * {@link DigestScheme} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class DigestSchemeFactory implements AuthSchemeFactory, AuthSchemeProvider { + + private final Charset charset; + + /** + * @since 4.3 + */ + public DigestSchemeFactory(final Charset charset) { + super(); + this.charset = charset; + } + + public DigestSchemeFactory() { + this(null); + } + + public AuthScheme newInstance(final HttpParams params) { + return new DigestScheme(); + } + + public AuthScheme create(final HttpContext context) { + return new DigestScheme(this.charset); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/HttpAuthenticator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/HttpAuthenticator.java new file mode 100644 index 000000000..28b8b74d0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/HttpAuthenticator.java @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.auth; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.auth.AuthOption; +import ch.boye.httpclientandroidlib.auth.AuthProtocolState; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.ContextAwareAuthScheme; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * @since 4.3 + */ +public class HttpAuthenticator { + + public HttpClientAndroidLog log; + + public HttpAuthenticator(final HttpClientAndroidLog log) { + super(); + this.log = log != null ? log : new HttpClientAndroidLog(getClass()); + } + + public HttpAuthenticator() { + this(null); + } + + public boolean isAuthenticationRequested( + final HttpHost host, + final HttpResponse response, + final AuthenticationStrategy authStrategy, + final AuthState authState, + final HttpContext context) { + if (authStrategy.isAuthenticationRequested(host, response, context)) { + this.log.debug("Authentication required"); + if (authState.getState() == AuthProtocolState.SUCCESS) { + authStrategy.authFailed(host, authState.getAuthScheme(), context); + } + return true; + } else { + switch (authState.getState()) { + case CHALLENGED: + case HANDSHAKE: + this.log.debug("Authentication succeeded"); + authState.setState(AuthProtocolState.SUCCESS); + authStrategy.authSucceeded(host, authState.getAuthScheme(), context); + break; + case SUCCESS: + break; + default: + authState.setState(AuthProtocolState.UNCHALLENGED); + } + return false; + } + } + + public boolean handleAuthChallenge( + final HttpHost host, + final HttpResponse response, + final AuthenticationStrategy authStrategy, + final AuthState authState, + final HttpContext context) { + try { + if (this.log.isDebugEnabled()) { + this.log.debug(host.toHostString() + " requested authentication"); + } + final Map<String, Header> challenges = authStrategy.getChallenges(host, response, context); + if (challenges.isEmpty()) { + this.log.debug("Response contains no authentication challenges"); + return false; + } + + final AuthScheme authScheme = authState.getAuthScheme(); + switch (authState.getState()) { + case FAILURE: + return false; + case SUCCESS: + authState.reset(); + break; + case CHALLENGED: + case HANDSHAKE: + if (authScheme == null) { + this.log.debug("Auth scheme is null"); + authStrategy.authFailed(host, null, context); + authState.reset(); + authState.setState(AuthProtocolState.FAILURE); + return false; + } + case UNCHALLENGED: + if (authScheme != null) { + final String id = authScheme.getSchemeName(); + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + if (challenge != null) { + this.log.debug("Authorization challenge processed"); + authScheme.processChallenge(challenge); + if (authScheme.isComplete()) { + this.log.debug("Authentication failed"); + authStrategy.authFailed(host, authState.getAuthScheme(), context); + authState.reset(); + authState.setState(AuthProtocolState.FAILURE); + return false; + } else { + authState.setState(AuthProtocolState.HANDSHAKE); + return true; + } + } else { + authState.reset(); + // Retry authentication with a different scheme + } + } + } + final Queue<AuthOption> authOptions = authStrategy.select(challenges, host, response, context); + if (authOptions != null && !authOptions.isEmpty()) { + if (this.log.isDebugEnabled()) { + this.log.debug("Selected authentication options: " + authOptions); + } + authState.setState(AuthProtocolState.CHALLENGED); + authState.update(authOptions); + return true; + } else { + return false; + } + } catch (final MalformedChallengeException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Malformed challenge: " + ex.getMessage()); + } + authState.reset(); + return false; + } + } + + public void generateAuthResponse( + final HttpRequest request, + final AuthState authState, + final HttpContext context) throws HttpException, IOException { + AuthScheme authScheme = authState.getAuthScheme(); + Credentials creds = authState.getCredentials(); + switch (authState.getState()) { + case FAILURE: + return; + case SUCCESS: + ensureAuthScheme(authScheme); + if (authScheme.isConnectionBased()) { + return; + } + break; + case CHALLENGED: + final Queue<AuthOption> authOptions = authState.getAuthOptions(); + if (authOptions != null) { + while (!authOptions.isEmpty()) { + final AuthOption authOption = authOptions.remove(); + authScheme = authOption.getAuthScheme(); + creds = authOption.getCredentials(); + authState.update(authScheme, creds); + if (this.log.isDebugEnabled()) { + this.log.debug("Generating response to an authentication challenge using " + + authScheme.getSchemeName() + " scheme"); + } + try { + final Header header = doAuth(authScheme, creds, request, context); + request.addHeader(header); + break; + } catch (final AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn(authScheme + " authentication error: " + ex.getMessage()); + } + } + } + return; + } else { + ensureAuthScheme(authScheme); + } + } + if (authScheme != null) { + try { + final Header header = doAuth(authScheme, creds, request, context); + request.addHeader(header); + } catch (final AuthenticationException ex) { + if (this.log.isErrorEnabled()) { + this.log.error(authScheme + " authentication error: " + ex.getMessage()); + } + } + } + } + + private void ensureAuthScheme(final AuthScheme authScheme) { + Asserts.notNull(authScheme, "Auth scheme"); + } + + @SuppressWarnings("deprecation") + private Header doAuth( + final AuthScheme authScheme, + final Credentials creds, + final HttpRequest request, + final HttpContext context) throws AuthenticationException { + if (authScheme instanceof ContextAwareAuthScheme) { + return ((ContextAwareAuthScheme) authScheme).authenticate(creds, request, context); + } else { + return authScheme.authenticate(creds, request); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/HttpEntityDigester.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/HttpEntityDigester.java new file mode 100644 index 000000000..053951587 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/HttpEntityDigester.java @@ -0,0 +1,75 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.MessageDigest; + +class HttpEntityDigester extends OutputStream { + + private final MessageDigest digester; + private boolean closed; + private byte[] digest; + + HttpEntityDigester(final MessageDigest digester) { + super(); + this.digester = digester; + this.digester.reset(); + } + + @Override + public void write(final int b) throws IOException { + if (this.closed) { + throw new IOException("Stream has been already closed"); + } + this.digester.update((byte) b); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + if (this.closed) { + throw new IOException("Stream has been already closed"); + } + this.digester.update(b, off, len); + } + + @Override + public void close() throws IOException { + if (this.closed) { + return; + } + this.closed = true; + this.digest = this.digester.digest(); + super.close(); + } + + public byte[] getDigest() { + return this.digest; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngine.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngine.java new file mode 100644 index 000000000..ce2457000 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngine.java @@ -0,0 +1,70 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +/** + * Abstract NTLM authentication engine. The engine can be used to + * generate Type1 messages and Type3 messages in response to a + * Type2 challenge. + * + * @since 4.0 + */ +public interface NTLMEngine { + + /** + * Generates a Type1 message given the domain and workstation. + * + * @param domain Optional Windows domain name. Can be <code>null</code>. + * @param workstation Optional Windows workstation name. Can be + * <code>null</code>. + * @return Type1 message + * @throws NTLMEngineException + */ + String generateType1Msg( + String domain, + String workstation) throws NTLMEngineException; + + /** + * Generates a Type3 message given the user credentials and the + * authentication challenge. + * + * @param username Windows user name + * @param password Password + * @param domain Windows domain name + * @param workstation Windows workstation name + * @param challenge Type2 challenge. + * @return Type3 response. + * @throws NTLMEngineException + */ + String generateType3Msg( + String username, + String password, + String domain, + String workstation, + String challenge) throws NTLMEngineException; + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineException.java new file mode 100644 index 000000000..88ef96912 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineException.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; + +/** + * Signals NTLM protocol failure. + * + * + * @since 4.0 + */ +@Immutable +public class NTLMEngineException extends AuthenticationException { + + private static final long serialVersionUID = 6027981323731768824L; + + public NTLMEngineException() { + super(); + } + + /** + * Creates a new NTLMEngineException with the specified message. + * + * @param message the exception detail message + */ + public NTLMEngineException(final String message) { + super(message); + } + + /** + * Creates a new NTLMEngineException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public NTLMEngineException(final String message, final Throwable cause) { + super(message, cause); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineImpl.java new file mode 100644 index 000000000..195148f6b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMEngineImpl.java @@ -0,0 +1,1672 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.io.UnsupportedEncodingException; +import java.security.Key; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Locale; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.mozilla.apache.commons.codec.binary.Base64; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.util.EncodingUtils; + +/** + * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM + * authentication protocol. + * + * @since 4.1 + */ +@NotThreadSafe +final class NTLMEngineImpl implements NTLMEngine { + + // Flags we use; descriptions according to: + // http://davenport.sourceforge.net/ntlm.html + // and + // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx + protected static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested + protected static final int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field + protected static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message. + protected static final int FLAG_REQUEST_SEAL = 0x00000020; // Request key exchange for message confidentiality in NEGOTIATE message. MUST be used in conjunction with 56BIT. + protected static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key + protected static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both + protected static final int FLAG_DOMAIN_PRESENT = 0x00001000; // Domain is present in message + protected static final int FLAG_WORKSTATION_PRESENT = 0x00002000; // Workstation is present in message + protected static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL. + protected static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security + protected static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version + protected static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present + protected static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange + protected static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange + protected static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL + + + /** Secure random generator */ + private static final java.security.SecureRandom RND_GEN; + static { + java.security.SecureRandom rnd = null; + try { + rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); + } catch (final Exception ignore) { + } + RND_GEN = rnd; + } + + /** Character encoding */ + static final String DEFAULT_CHARSET = "ASCII"; + + /** The character set to use for encoding the credentials */ + private String credentialCharset = DEFAULT_CHARSET; + + /** The signature string as bytes in the default encoding */ + private static final byte[] SIGNATURE; + + static { + final byte[] bytesWithoutNull = EncodingUtils.getBytes("NTLMSSP", "ASCII"); + SIGNATURE = new byte[bytesWithoutNull.length + 1]; + System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length); + SIGNATURE[bytesWithoutNull.length] = (byte) 0x00; + } + + /** + * Returns the response for the given message. + * + * @param message + * the message that was received from the server. + * @param username + * the username to authenticate with. + * @param password + * the password to authenticate with. + * @param host + * The host. + * @param domain + * the NT domain to authenticate in. + * @return The response. + * @throws ch.boye.httpclientandroidlib.HttpException + * If the messages cannot be retrieved. + */ + final String getResponseFor(final String message, final String username, final String password, + final String host, final String domain) throws NTLMEngineException { + + final String response; + if (message == null || message.trim().equals("")) { + response = getType1Message(host, domain); + } else { + final Type2Message t2m = new Type2Message(message); + response = getType3Message(username, password, host, domain, t2m.getChallenge(), t2m + .getFlags(), t2m.getTarget(), t2m.getTargetInfo()); + } + return response; + } + + /** + * Creates the first message (type 1 message) in the NTLM authentication + * sequence. This message includes the user name, domain and host for the + * authentication session. + * + * @param host + * the computer name of the host requesting authentication. + * @param domain + * The domain to authenticate with. + * @return String the message to add to the HTTP request header. + */ + String getType1Message(final String host, final String domain) throws NTLMEngineException { + return new Type1Message(domain, host).getResponse(); + } + + /** + * Creates the type 3 message using the given server nonce. The type 3 + * message includes all the information for authentication, host, domain, + * username and the result of encrypting the nonce sent by the server using + * the user's password as the key. + * + * @param user + * The user name. This should not include the domain name. + * @param password + * The password. + * @param host + * The host that is originating the authentication request. + * @param domain + * The domain to authenticate within. + * @param nonce + * the 8 byte array the server sent. + * @return The type 3 message. + * @throws NTLMEngineException + * If {@link #RC4(byte[],byte[])} fails. + */ + String getType3Message(final String user, final String password, final String host, final String domain, + final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation) + throws NTLMEngineException { + return new Type3Message(domain, host, user, password, nonce, type2Flags, target, + targetInformation).getResponse(); + } + + /** + * @return Returns the credentialCharset. + */ + String getCredentialCharset() { + return credentialCharset; + } + + /** + * @param credentialCharset + * The credentialCharset to set. + */ + void setCredentialCharset(final String credentialCharset) { + this.credentialCharset = credentialCharset; + } + + /** Strip dot suffix from a name */ + private static String stripDotSuffix(final String value) { + if (value == null) { + return null; + } + final int index = value.indexOf("."); + if (index != -1) { + return value.substring(0, index); + } + return value; + } + + /** Convert host to standard form */ + private static String convertHost(final String host) { + return stripDotSuffix(host); + } + + /** Convert domain to standard form */ + private static String convertDomain(final String domain) { + return stripDotSuffix(domain); + } + + private static int readULong(final byte[] src, final int index) throws NTLMEngineException { + if (src.length < index + 4) { + throw new NTLMEngineException("NTLM authentication - buffer too small for DWORD"); + } + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) + | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); + } + + private static int readUShort(final byte[] src, final int index) throws NTLMEngineException { + if (src.length < index + 2) { + throw new NTLMEngineException("NTLM authentication - buffer too small for WORD"); + } + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); + } + + private static byte[] readSecurityBuffer(final byte[] src, final int index) throws NTLMEngineException { + final int length = readUShort(src, index); + final int offset = readULong(src, index + 4); + if (src.length < offset + length) { + throw new NTLMEngineException( + "NTLM authentication - buffer too small for data item"); + } + final byte[] buffer = new byte[length]; + System.arraycopy(src, offset, buffer, 0, length); + return buffer; + } + + /** Calculate a challenge block */ + private static byte[] makeRandomChallenge() throws NTLMEngineException { + if (RND_GEN == null) { + throw new NTLMEngineException("Random generator not available"); + } + final byte[] rval = new byte[8]; + synchronized (RND_GEN) { + RND_GEN.nextBytes(rval); + } + return rval; + } + + /** Calculate a 16-byte secondary key */ + private static byte[] makeSecondaryKey() throws NTLMEngineException { + if (RND_GEN == null) { + throw new NTLMEngineException("Random generator not available"); + } + final byte[] rval = new byte[16]; + synchronized (RND_GEN) { + RND_GEN.nextBytes(rval); + } + return rval; + } + + protected static class CipherGen { + + protected final String domain; + protected final String user; + protected final String password; + protected final byte[] challenge; + protected final String target; + protected final byte[] targetInformation; + + // Information we can generate but may be passed in (for testing) + protected byte[] clientChallenge; + protected byte[] clientChallenge2; + protected byte[] secondaryKey; + protected byte[] timestamp; + + // Stuff we always generate + protected byte[] lmHash = null; + protected byte[] lmResponse = null; + protected byte[] ntlmHash = null; + protected byte[] ntlmResponse = null; + protected byte[] ntlmv2Hash = null; + protected byte[] lmv2Hash = null; + protected byte[] lmv2Response = null; + protected byte[] ntlmv2Blob = null; + protected byte[] ntlmv2Response = null; + protected byte[] ntlm2SessionResponse = null; + protected byte[] lm2SessionResponse = null; + protected byte[] lmUserSessionKey = null; + protected byte[] ntlmUserSessionKey = null; + protected byte[] ntlmv2UserSessionKey = null; + protected byte[] ntlm2SessionResponseUserSessionKey = null; + protected byte[] lanManagerSessionKey = null; + + public CipherGen(final String domain, final String user, final String password, + final byte[] challenge, final String target, final byte[] targetInformation, + final byte[] clientChallenge, final byte[] clientChallenge2, + final byte[] secondaryKey, final byte[] timestamp) { + this.domain = domain; + this.target = target; + this.user = user; + this.password = password; + this.challenge = challenge; + this.targetInformation = targetInformation; + this.clientChallenge = clientChallenge; + this.clientChallenge2 = clientChallenge2; + this.secondaryKey = secondaryKey; + this.timestamp = timestamp; + } + + public CipherGen(final String domain, final String user, final String password, + final byte[] challenge, final String target, final byte[] targetInformation) { + this(domain, user, password, challenge, target, targetInformation, null, null, null, null); + } + + /** Calculate and return client challenge */ + public byte[] getClientChallenge() + throws NTLMEngineException { + if (clientChallenge == null) { + clientChallenge = makeRandomChallenge(); + } + return clientChallenge; + } + + /** Calculate and return second client challenge */ + public byte[] getClientChallenge2() + throws NTLMEngineException { + if (clientChallenge2 == null) { + clientChallenge2 = makeRandomChallenge(); + } + return clientChallenge2; + } + + /** Calculate and return random secondary key */ + public byte[] getSecondaryKey() + throws NTLMEngineException { + if (secondaryKey == null) { + secondaryKey = makeSecondaryKey(); + } + return secondaryKey; + } + + /** Calculate and return the LMHash */ + public byte[] getLMHash() + throws NTLMEngineException { + if (lmHash == null) { + lmHash = lmHash(password); + } + return lmHash; + } + + /** Calculate and return the LMResponse */ + public byte[] getLMResponse() + throws NTLMEngineException { + if (lmResponse == null) { + lmResponse = lmResponse(getLMHash(),challenge); + } + return lmResponse; + } + + /** Calculate and return the NTLMHash */ + public byte[] getNTLMHash() + throws NTLMEngineException { + if (ntlmHash == null) { + ntlmHash = ntlmHash(password); + } + return ntlmHash; + } + + /** Calculate and return the NTLMResponse */ + public byte[] getNTLMResponse() + throws NTLMEngineException { + if (ntlmResponse == null) { + ntlmResponse = lmResponse(getNTLMHash(),challenge); + } + return ntlmResponse; + } + + /** Calculate the LMv2 hash */ + public byte[] getLMv2Hash() + throws NTLMEngineException { + if (lmv2Hash == null) { + lmv2Hash = lmv2Hash(domain, user, getNTLMHash()); + } + return lmv2Hash; + } + + /** Calculate the NTLMv2 hash */ + public byte[] getNTLMv2Hash() + throws NTLMEngineException { + if (ntlmv2Hash == null) { + ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash()); + } + return ntlmv2Hash; + } + + /** Calculate a timestamp */ + public byte[] getTimestamp() { + if (timestamp == null) { + long time = System.currentTimeMillis(); + time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. + time *= 10000; // tenths of a microsecond. + // convert to little-endian byte array. + timestamp = new byte[8]; + for (int i = 0; i < 8; i++) { + timestamp[i] = (byte) time; + time >>>= 8; + } + } + return timestamp; + } + + /** Calculate the NTLMv2Blob */ + public byte[] getNTLMv2Blob() + throws NTLMEngineException { + if (ntlmv2Blob == null) { + ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp()); + } + return ntlmv2Blob; + } + + /** Calculate the NTLMv2Response */ + public byte[] getNTLMv2Response() + throws NTLMEngineException { + if (ntlmv2Response == null) { + ntlmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getNTLMv2Blob()); + } + return ntlmv2Response; + } + + /** Calculate the LMv2Response */ + public byte[] getLMv2Response() + throws NTLMEngineException { + if (lmv2Response == null) { + lmv2Response = lmv2Response(getLMv2Hash(),challenge,getClientChallenge()); + } + return lmv2Response; + } + + /** Get NTLM2SessionResponse */ + public byte[] getNTLM2SessionResponse() + throws NTLMEngineException { + if (ntlm2SessionResponse == null) { + ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(),challenge,getClientChallenge()); + } + return ntlm2SessionResponse; + } + + /** Calculate and return LM2 session response */ + public byte[] getLM2SessionResponse() + throws NTLMEngineException { + if (lm2SessionResponse == null) { + final byte[] clChallenge = getClientChallenge(); + lm2SessionResponse = new byte[24]; + System.arraycopy(clChallenge, 0, lm2SessionResponse, 0, clChallenge.length); + Arrays.fill(lm2SessionResponse, clChallenge.length, lm2SessionResponse.length, (byte) 0x00); + } + return lm2SessionResponse; + } + + /** Get LMUserSessionKey */ + public byte[] getLMUserSessionKey() + throws NTLMEngineException { + if (lmUserSessionKey == null) { + lmUserSessionKey = new byte[16]; + System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8); + Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00); + } + return lmUserSessionKey; + } + + /** Get NTLMUserSessionKey */ + public byte[] getNTLMUserSessionKey() + throws NTLMEngineException { + if (ntlmUserSessionKey == null) { + final MD4 md4 = new MD4(); + md4.update(getNTLMHash()); + ntlmUserSessionKey = md4.getOutput(); + } + return ntlmUserSessionKey; + } + + /** GetNTLMv2UserSessionKey */ + public byte[] getNTLMv2UserSessionKey() + throws NTLMEngineException { + if (ntlmv2UserSessionKey == null) { + final byte[] ntlmv2hash = getNTLMv2Hash(); + final byte[] truncatedResponse = new byte[16]; + System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16); + ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash); + } + return ntlmv2UserSessionKey; + } + + /** Get NTLM2SessionResponseUserSessionKey */ + public byte[] getNTLM2SessionResponseUserSessionKey() + throws NTLMEngineException { + if (ntlm2SessionResponseUserSessionKey == null) { + final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse(); + final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length]; + System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length); + System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length); + ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce,getNTLMUserSessionKey()); + } + return ntlm2SessionResponseUserSessionKey; + } + + /** Get LAN Manager session key */ + public byte[] getLanManagerSessionKey() + throws NTLMEngineException { + if (lanManagerSessionKey == null) { + try { + final byte[] keyBytes = new byte[14]; + System.arraycopy(getLMHash(), 0, keyBytes, 0, 8); + Arrays.fill(keyBytes, 8, keyBytes.length, (byte)0xbd); + final Key lowKey = createDESKey(keyBytes, 0); + final Key highKey = createDESKey(keyBytes, 7); + final byte[] truncatedResponse = new byte[8]; + System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowPart = des.doFinal(truncatedResponse); + des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highPart = des.doFinal(truncatedResponse); + lanManagerSessionKey = new byte[16]; + System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length); + System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length); + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + return lanManagerSessionKey; + } + } + + /** Calculates HMAC-MD5 */ + static byte[] hmacMD5(final byte[] value, final byte[] key) + throws NTLMEngineException { + final HMACMD5 hmacMD5 = new HMACMD5(key); + hmacMD5.update(value); + return hmacMD5.getOutput(); + } + + /** Calculates RC4 */ + static byte[] RC4(final byte[] value, final byte[] key) + throws NTLMEngineException { + try { + final Cipher rc4 = Cipher.getInstance("RC4"); + rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4")); + return rc4.doFinal(value); + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Calculates the NTLM2 Session Response for the given challenge, using the + * specified password and client challenge. + * + * @return The NTLM2 Session Response. This is placed in the NTLM response + * field of the Type 3 message; the LM response field contains the + * client challenge, null-padded to 24 bytes. + */ + static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge, + final byte[] clientChallenge) throws NTLMEngineException { + try { + // Look up MD5 algorithm (was necessary on jdk 1.4.2) + // This used to be needed, but java 1.5.0_07 includes the MD5 + // algorithm (finally) + // Class x = Class.forName("gnu.crypto.hash.MD5"); + // Method updateMethod = x.getMethod("update",new + // Class[]{byte[].class}); + // Method digestMethod = x.getMethod("digest",new Class[0]); + // Object mdInstance = x.newInstance(); + // updateMethod.invoke(mdInstance,new Object[]{challenge}); + // updateMethod.invoke(mdInstance,new Object[]{clientChallenge}); + // byte[] digest = (byte[])digestMethod.invoke(mdInstance,new + // Object[0]); + + final MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(challenge); + md5.update(clientChallenge); + final byte[] digest = md5.digest(); + + final byte[] sessionHash = new byte[8]; + System.arraycopy(digest, 0, sessionHash, 0, 8); + return lmResponse(ntlmHash, sessionHash); + } catch (final Exception e) { + if (e instanceof NTLMEngineException) { + throw (NTLMEngineException) e; + } + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Creates the LM Hash of the user's password. + * + * @param password + * The password. + * + * @return The LM Hash of the given password, used in the calculation of the + * LM Response. + */ + private static byte[] lmHash(final String password) throws NTLMEngineException { + try { + final byte[] oemPassword = password.toUpperCase(Locale.ENGLISH).getBytes("US-ASCII"); + final int length = Math.min(oemPassword.length, 14); + final byte[] keyBytes = new byte[14]; + System.arraycopy(oemPassword, 0, keyBytes, 0, length); + final Key lowKey = createDESKey(keyBytes, 0); + final Key highKey = createDESKey(keyBytes, 7); + final byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII"); + final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowHash = des.doFinal(magicConstant); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highHash = des.doFinal(magicConstant); + final byte[] lmHash = new byte[16]; + System.arraycopy(lowHash, 0, lmHash, 0, 8); + System.arraycopy(highHash, 0, lmHash, 8, 8); + return lmHash; + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Creates the NTLM Hash of the user's password. + * + * @param password + * The password. + * + * @return The NTLM Hash of the given password, used in the calculation of + * the NTLM Response and the NTLMv2 and LMv2 Hashes. + */ + private static byte[] ntlmHash(final String password) throws NTLMEngineException { + try { + final byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); + final MD4 md4 = new MD4(); + md4.update(unicodePassword); + return md4.getOutput(); + } catch (final UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); + } + } + + /** + * Creates the LMv2 Hash of the user's password. + * + * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2 + * Responses. + */ + private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash) + throws NTLMEngineException { + try { + final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, upper case domain! + hmacMD5.update(user.toUpperCase(Locale.ENGLISH).getBytes("UnicodeLittleUnmarked")); + if (domain != null) { + hmacMD5.update(domain.toUpperCase(Locale.ENGLISH).getBytes("UnicodeLittleUnmarked")); + } + return hmacMD5.getOutput(); + } catch (final UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported! " + e.getMessage(), e); + } + } + + /** + * Creates the NTLMv2 Hash of the user's password. + * + * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 + * Responses. + */ + private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) + throws NTLMEngineException { + try { + final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, mixed case target!! + hmacMD5.update(user.toUpperCase(Locale.ENGLISH).getBytes("UnicodeLittleUnmarked")); + if (domain != null) { + hmacMD5.update(domain.getBytes("UnicodeLittleUnmarked")); + } + return hmacMD5.getOutput(); + } catch (final UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported! " + e.getMessage(), e); + } + } + + /** + * Creates the LM Response from the given hash and Type 2 challenge. + * + * @param hash + * The LM or NTLM Hash. + * @param challenge + * The server challenge from the Type 2 message. + * + * @return The response (either LM or NTLM, depending on the provided hash). + */ + private static byte[] lmResponse(final byte[] hash, final byte[] challenge) throws NTLMEngineException { + try { + final byte[] keyBytes = new byte[21]; + System.arraycopy(hash, 0, keyBytes, 0, 16); + final Key lowKey = createDESKey(keyBytes, 0); + final Key middleKey = createDESKey(keyBytes, 7); + final Key highKey = createDESKey(keyBytes, 14); + final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, middleKey); + final byte[] middleResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highResponse = des.doFinal(challenge); + final byte[] lmResponse = new byte[24]; + System.arraycopy(lowResponse, 0, lmResponse, 0, 8); + System.arraycopy(middleResponse, 0, lmResponse, 8, 8); + System.arraycopy(highResponse, 0, lmResponse, 16, 8); + return lmResponse; + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Creates the LMv2 Response from the given hash, client data, and Type 2 + * challenge. + * + * @param hash + * The NTLMv2 Hash. + * @param clientData + * The client data (blob or client challenge). + * @param challenge + * The server challenge from the Type 2 message. + * + * @return The response (either NTLMv2 or LMv2, depending on the client + * data). + */ + private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) + throws NTLMEngineException { + final HMACMD5 hmacMD5 = new HMACMD5(hash); + hmacMD5.update(challenge); + hmacMD5.update(clientData); + final byte[] mac = hmacMD5.getOutput(); + final byte[] lmv2Response = new byte[mac.length + clientData.length]; + System.arraycopy(mac, 0, lmv2Response, 0, mac.length); + System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); + return lmv2Response; + } + + /** + * Creates the NTLMv2 blob from the given target information block and + * client challenge. + * + * @param targetInformation + * The target information block from the Type 2 message. + * @param clientChallenge + * The random 8-byte client challenge. + * + * @return The blob, used in the calculation of the NTLMv2 Response. + */ + private static byte[] createBlob(final byte[] clientChallenge, final byte[] targetInformation, final byte[] timestamp) { + final byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; + final byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + final byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + final byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 + + unknown1.length + targetInformation.length + unknown2.length]; + int offset = 0; + System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); + offset += blobSignature.length; + System.arraycopy(reserved, 0, blob, offset, reserved.length); + offset += reserved.length; + System.arraycopy(timestamp, 0, blob, offset, timestamp.length); + offset += timestamp.length; + System.arraycopy(clientChallenge, 0, blob, offset, 8); + offset += 8; + System.arraycopy(unknown1, 0, blob, offset, unknown1.length); + offset += unknown1.length; + System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); + offset += targetInformation.length; + System.arraycopy(unknown2, 0, blob, offset, unknown2.length); + offset += unknown2.length; + return blob; + } + + /** + * Creates a DES encryption key from the given key material. + * + * @param bytes + * A byte array containing the DES key material. + * @param offset + * The offset in the given byte array at which the 7-byte key + * material starts. + * + * @return A DES encryption key created from the key material starting at + * the specified offset in the given byte array. + */ + private static Key createDESKey(final byte[] bytes, final int offset) { + final byte[] keyBytes = new byte[7]; + System.arraycopy(bytes, offset, keyBytes, 0, 7); + final byte[] material = new byte[8]; + material[0] = keyBytes[0]; + material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); + material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); + material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); + material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); + material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); + material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); + material[7] = (byte) (keyBytes[6] << 1); + oddParity(material); + return new SecretKeySpec(material, "DES"); + } + + /** + * Applies odd parity to the given byte array. + * + * @param bytes + * The data whose parity bits are to be adjusted for odd parity. + */ + private static void oddParity(final byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + final byte b = bytes[i]; + final boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) + ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; + if (needsParity) { + bytes[i] |= (byte) 0x01; + } else { + bytes[i] &= (byte) 0xfe; + } + } + } + + /** NTLM message generation, base class */ + static class NTLMMessage { + /** The current response */ + private byte[] messageContents = null; + + /** The current output position */ + private int currentOutputPosition = 0; + + /** Constructor to use when message contents are not yet known */ + NTLMMessage() { + } + + /** Constructor to use when message contents are known */ + NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException { + messageContents = Base64.decodeBase64(EncodingUtils.getBytes(messageBody, + DEFAULT_CHARSET)); + // Look for NTLM message + if (messageContents.length < SIGNATURE.length) { + throw new NTLMEngineException("NTLM message decoding error - packet too short"); + } + int i = 0; + while (i < SIGNATURE.length) { + if (messageContents[i] != SIGNATURE[i]) { + throw new NTLMEngineException( + "NTLM message expected - instead got unrecognized bytes"); + } + i++; + } + + // Check to be sure there's a type 2 message indicator next + final int type = readULong(SIGNATURE.length); + if (type != expectedType) { + throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) + + " message expected - instead got type " + Integer.toString(type)); + } + + currentOutputPosition = messageContents.length; + } + + /** + * Get the length of the signature and flags, so calculations can adjust + * offsets accordingly. + */ + protected int getPreambleLength() { + return SIGNATURE.length + 4; + } + + /** Get the message length */ + protected int getMessageLength() { + return currentOutputPosition; + } + + /** Read a byte from a position within the message buffer */ + protected byte readByte(final int position) throws NTLMEngineException { + if (messageContents.length < position + 1) { + throw new NTLMEngineException("NTLM: Message too short"); + } + return messageContents[position]; + } + + /** Read a bunch of bytes from a position in the message buffer */ + protected void readBytes(final byte[] buffer, final int position) throws NTLMEngineException { + if (messageContents.length < position + buffer.length) { + throw new NTLMEngineException("NTLM: Message too short"); + } + System.arraycopy(messageContents, position, buffer, 0, buffer.length); + } + + /** Read a ushort from a position within the message buffer */ + protected int readUShort(final int position) throws NTLMEngineException { + return NTLMEngineImpl.readUShort(messageContents, position); + } + + /** Read a ulong from a position within the message buffer */ + protected int readULong(final int position) throws NTLMEngineException { + return NTLMEngineImpl.readULong(messageContents, position); + } + + /** Read a security buffer from a position within the message buffer */ + protected byte[] readSecurityBuffer(final int position) throws NTLMEngineException { + return NTLMEngineImpl.readSecurityBuffer(messageContents, position); + } + + /** + * Prepares the object to create a response of the given length. + * + * @param maxlength + * the maximum length of the response to prepare, not + * including the type and the signature (which this method + * adds). + */ + protected void prepareResponse(final int maxlength, final int messageType) { + messageContents = new byte[maxlength]; + currentOutputPosition = 0; + addBytes(SIGNATURE); + addULong(messageType); + } + + /** + * Adds the given byte to the response. + * + * @param b + * the byte to add. + */ + protected void addByte(final byte b) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + + /** + * Adds the given bytes to the response. + * + * @param bytes + * the bytes to add. + */ + protected void addBytes(final byte[] bytes) { + if (bytes == null) { + return; + } + for (final byte b : bytes) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + } + + /** Adds a USHORT to the response */ + protected void addUShort(final int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + } + + /** Adds a ULong to the response */ + protected void addULong(final int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + addByte((byte) (value >> 16 & 0xff)); + addByte((byte) (value >> 24 & 0xff)); + } + + /** + * Returns the response that has been generated after shrinking the + * array if required and base64 encodes the response. + * + * @return The response as above. + */ + String getResponse() { + final byte[] resp; + if (messageContents.length > currentOutputPosition) { + final byte[] tmp = new byte[currentOutputPosition]; + System.arraycopy(messageContents, 0, tmp, 0, currentOutputPosition); + resp = tmp; + } else { + resp = messageContents; + } + return EncodingUtils.getAsciiString(Base64.encodeBase64(resp)); + } + + } + + /** Type 1 message assembly class */ + static class Type1Message extends NTLMMessage { + protected byte[] hostBytes; + protected byte[] domainBytes; + + /** Constructor. Include the arguments the message will need */ + Type1Message(final String domain, final String host) throws NTLMEngineException { + super(); + try { + // Strip off domain name from the host! + final String unqualifiedHost = convertHost(host); + // Use only the base domain name! + final String unqualifiedDomain = convertDomain(domain); + + hostBytes = unqualifiedHost != null? unqualifiedHost.getBytes("ASCII") : null; + domainBytes = unqualifiedDomain != null ? unqualifiedDomain + .toUpperCase(Locale.ENGLISH).getBytes("ASCII") : null; + } catch (final UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode unsupported: " + e.getMessage(), e); + } + } + + /** + * Getting the response involves building the message before returning + * it + */ + @Override + String getResponse() { + // Now, build the message. Calculate its length first, including + // signature or type. + final int finalLength = 32 + 8 /*+ hostBytes.length + domainBytes.length */; + + // Set up the response. This will initialize the signature, message + // type, and flags. + prepareResponse(finalLength, 1); + + // Flags. These are the complete set of flags we support. + addULong( + //FLAG_WORKSTATION_PRESENT | + //FLAG_DOMAIN_PRESENT | + + // Required flags + //FLAG_REQUEST_LAN_MANAGER_KEY | + FLAG_REQUEST_NTLMv1 | + FLAG_REQUEST_NTLM2_SESSION | + + // Protocol version request + FLAG_REQUEST_VERSION | + + // Recommended privacy settings + FLAG_REQUEST_ALWAYS_SIGN | + //FLAG_REQUEST_SEAL | + //FLAG_REQUEST_SIGN | + + // These must be set according to documentation, based on use of SEAL above + FLAG_REQUEST_128BIT_KEY_EXCH | + FLAG_REQUEST_56BIT_ENCRYPTION | + //FLAG_REQUEST_EXPLICIT_KEY_EXCH | + + FLAG_REQUEST_UNICODE_ENCODING); + + // Domain length (two times). + addUShort(/*domainBytes.length*/0); + addUShort(/*domainBytes.length*/0); + + // Domain offset. + addULong(/*hostBytes.length +*/ 32 + 8); + + // Host length (two times). + addUShort(/*hostBytes.length*/0); + addUShort(/*hostBytes.length*/0); + + // Host offset (always 32 + 8). + addULong(32 + 8); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(0x0f00); + + + // Host (workstation) String. + //addBytes(hostBytes); + + // Domain String. + //addBytes(domainBytes); + + + return super.getResponse(); + } + + } + + /** Type 2 message class */ + static class Type2Message extends NTLMMessage { + protected byte[] challenge; + protected String target; + protected byte[] targetInfo; + protected int flags; + + Type2Message(final String message) throws NTLMEngineException { + super(message, 2); + + // Type 2 message is laid out as follows: + // First 8 bytes: NTLMSSP[0] + // Next 4 bytes: Ulong, value 2 + // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset) + // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235 + // Next 8 bytes, starting at offset 24: Challenge + // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros) + // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset) + // Next 2 bytes, major/minor version number (e.g. 0x05 0x02) + // Next 8 bytes, build number + // Next 2 bytes, protocol version number (e.g. 0x00 0x0f) + // Next, various text fields, and a ushort of value 0 at the end + + // Parse out the rest of the info we need from the message + // The nonce is the 8 bytes starting from the byte in position 24. + challenge = new byte[8]; + readBytes(challenge, 24); + + flags = readULong(20); + + if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) { + throw new NTLMEngineException( + "NTLM type 2 message has flags that make no sense: " + + Integer.toString(flags)); + } + + // Do the target! + target = null; + // The TARGET_DESIRED flag is said to not have understood semantics + // in Type2 messages, so use the length of the packet to decide + // how to proceed instead + if (getMessageLength() >= 12 + 8) { + final byte[] bytes = readSecurityBuffer(12); + if (bytes.length != 0) { + try { + target = new String(bytes, "UnicodeLittleUnmarked"); + } catch (final UnsupportedEncodingException e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + } + + // Do the target info! + targetInfo = null; + // TARGET_DESIRED flag cannot be relied on, so use packet length + if (getMessageLength() >= 40 + 8) { + final byte[] bytes = readSecurityBuffer(40); + if (bytes.length != 0) { + targetInfo = bytes; + } + } + } + + /** Retrieve the challenge */ + byte[] getChallenge() { + return challenge; + } + + /** Retrieve the target */ + String getTarget() { + return target; + } + + /** Retrieve the target info */ + byte[] getTargetInfo() { + return targetInfo; + } + + /** Retrieve the response flags */ + int getFlags() { + return flags; + } + + } + + /** Type 3 message assembly class */ + static class Type3Message extends NTLMMessage { + // Response flags from the type2 message + protected int type2Flags; + + protected byte[] domainBytes; + protected byte[] hostBytes; + protected byte[] userBytes; + + protected byte[] lmResp; + protected byte[] ntResp; + protected byte[] sessionKey; + + + /** Constructor. Pass the arguments we will need */ + Type3Message(final String domain, final String host, final String user, final String password, final byte[] nonce, + final int type2Flags, final String target, final byte[] targetInformation) + throws NTLMEngineException { + // Save the flags + this.type2Flags = type2Flags; + + // Strip off domain name from the host! + final String unqualifiedHost = convertHost(host); + // Use only the base domain name! + final String unqualifiedDomain = convertDomain(domain); + + // Create a cipher generator class. Use domain BEFORE it gets modified! + final CipherGen gen = new CipherGen(unqualifiedDomain, user, password, nonce, target, targetInformation); + + // Use the new code to calculate the responses, including v2 if that + // seems warranted. + byte[] userSessionKey; + try { + // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet + // been tested + if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) && + targetInformation != null && target != null) { + // NTLMv2 + ntResp = gen.getNTLMv2Response(); + lmResp = gen.getLMv2Response(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLMv2UserSessionKey(); + } + } else { + // NTLMv1 + if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) { + // NTLM2 session stuff is requested + ntResp = gen.getNTLM2SessionResponse(); + lmResp = gen.getLM2SessionResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLM2SessionResponseUserSessionKey(); + } + } else { + ntResp = gen.getNTLMResponse(); + lmResp = gen.getLMResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLMUserSessionKey(); + } + } + } + } catch (final NTLMEngineException e) { + // This likely means we couldn't find the MD4 hash algorithm - + // fail back to just using LM + ntResp = new byte[0]; + lmResp = gen.getLMResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getLMUserSessionKey(); + } + } + + if ((type2Flags & FLAG_REQUEST_SIGN) != 0) { + if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) { + sessionKey = RC4(gen.getSecondaryKey(), userSessionKey); + } else { + sessionKey = userSessionKey; + } + } else { + sessionKey = null; + } + + try { + hostBytes = unqualifiedHost != null ? unqualifiedHost + .getBytes("UnicodeLittleUnmarked") : null; + domainBytes = unqualifiedDomain != null ? unqualifiedDomain + .toUpperCase(Locale.ENGLISH).getBytes("UnicodeLittleUnmarked") : null; + userBytes = user.getBytes("UnicodeLittleUnmarked"); + } catch (final UnsupportedEncodingException e) { + throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); + } + } + + /** Assemble the response */ + @Override + String getResponse() { + final int ntRespLen = ntResp.length; + final int lmRespLen = lmResp.length; + + final int domainLen = domainBytes != null ? domainBytes.length : 0; + final int hostLen = hostBytes != null ? hostBytes.length: 0; + final int userLen = userBytes.length; + final int sessionKeyLen; + if (sessionKey != null) { + sessionKeyLen = sessionKey.length; + } else { + sessionKeyLen = 0; + } + + // Calculate the layout within the packet + final int lmRespOffset = 72; // allocate space for the version + final int ntRespOffset = lmRespOffset + lmRespLen; + final int domainOffset = ntRespOffset + ntRespLen; + final int userOffset = domainOffset + domainLen; + final int hostOffset = userOffset + userLen; + final int sessionKeyOffset = hostOffset + hostLen; + final int finalLength = sessionKeyOffset + sessionKeyLen; + + // Start the response. Length includes signature and type + prepareResponse(finalLength, 3); + + // LM Resp Length (twice) + addUShort(lmRespLen); + addUShort(lmRespLen); + + // LM Resp Offset + addULong(lmRespOffset); + + // NT Resp Length (twice) + addUShort(ntRespLen); + addUShort(ntRespLen); + + // NT Resp Offset + addULong(ntRespOffset); + + // Domain length (twice) + addUShort(domainLen); + addUShort(domainLen); + + // Domain offset. + addULong(domainOffset); + + // User Length (twice) + addUShort(userLen); + addUShort(userLen); + + // User offset + addULong(userOffset); + + // Host length (twice) + addUShort(hostLen); + addUShort(hostLen); + + // Host offset + addULong(hostOffset); + + // Session key length (twice) + addUShort(sessionKeyLen); + addUShort(sessionKeyLen); + + // Session key offset + addULong(sessionKeyOffset); + + // Flags. + addULong( + //FLAG_WORKSTATION_PRESENT | + //FLAG_DOMAIN_PRESENT | + + // Required flags + (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) | + (type2Flags & FLAG_REQUEST_NTLMv1) | + (type2Flags & FLAG_REQUEST_NTLM2_SESSION) | + + // Protocol version request + FLAG_REQUEST_VERSION | + + // Recommended privacy settings + (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) | + (type2Flags & FLAG_REQUEST_SEAL) | + (type2Flags & FLAG_REQUEST_SIGN) | + + // These must be set according to documentation, based on use of SEAL above + (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) | + (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) | + (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) | + + (type2Flags & FLAG_TARGETINFO_PRESENT) | + (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) | + (type2Flags & FLAG_REQUEST_TARGET) + ); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(0x0f00); + + // Add the actual data + addBytes(lmResp); + addBytes(ntResp); + addBytes(domainBytes); + addBytes(userBytes); + addBytes(hostBytes); + if (sessionKey != null) { + addBytes(sessionKey); + } + + return super.getResponse(); + } + } + + static void writeULong(final byte[] buffer, final int value, final int offset) { + buffer[offset] = (byte) (value & 0xff); + buffer[offset + 1] = (byte) (value >> 8 & 0xff); + buffer[offset + 2] = (byte) (value >> 16 & 0xff); + buffer[offset + 3] = (byte) (value >> 24 & 0xff); + } + + static int F(final int x, final int y, final int z) { + return ((x & y) | (~x & z)); + } + + static int G(final int x, final int y, final int z) { + return ((x & y) | (x & z) | (y & z)); + } + + static int H(final int x, final int y, final int z) { + return (x ^ y ^ z); + } + + static int rotintlft(final int val, final int numbits) { + return ((val << numbits) | (val >>> (32 - numbits))); + } + + /** + * Cryptography support - MD4. The following class was based loosely on the + * RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java. + * Code correctness was verified by looking at MD4.java from the jcifs + * library (http://jcifs.samba.org). It was massaged extensively to the + * final form found here by Karl Wright (kwright@metacarta.com). + */ + static class MD4 { + protected int A = 0x67452301; + protected int B = 0xefcdab89; + protected int C = 0x98badcfe; + protected int D = 0x10325476; + protected long count = 0L; + protected byte[] dataBuffer = new byte[64]; + + MD4() { + } + + void update(final byte[] input) { + // We always deal with 512 bits at a time. Correspondingly, there is + // a buffer 64 bytes long that we write data into until it gets + // full. + int curBufferPos = (int) (count & 63L); + int inputIndex = 0; + while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { + // We have enough data to do the next step. Do a partial copy + // and a transform, updating inputIndex and curBufferPos + // accordingly + final int transferAmt = dataBuffer.length - curBufferPos; + System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); + count += transferAmt; + curBufferPos = 0; + inputIndex += transferAmt; + processBuffer(); + } + + // If there's anything left, copy it into the buffer and leave it. + // We know there's not enough left to process. + if (inputIndex < input.length) { + final int transferAmt = input.length - inputIndex; + System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); + count += transferAmt; + curBufferPos += transferAmt; + } + } + + byte[] getOutput() { + // Feed pad/length data into engine. This must round out the input + // to a multiple of 512 bits. + final int bufferIndex = (int) (count & 63L); + final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); + final byte[] postBytes = new byte[padLen + 8]; + // Leading 0x80, specified amount of zero padding, then length in + // bits. + postBytes[0] = (byte) 0x80; + // Fill out the last 8 bytes with the length + for (int i = 0; i < 8; i++) { + postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); + } + + // Update the engine + update(postBytes); + + // Calculate final result + final byte[] result = new byte[16]; + writeULong(result, A, 0); + writeULong(result, B, 4); + writeULong(result, C, 8); + writeULong(result, D, 12); + return result; + } + + protected void processBuffer() { + // Convert current buffer to 16 ulongs + final int[] d = new int[16]; + + for (int i = 0; i < 16; i++) { + d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) + + ((dataBuffer[i * 4 + 2] & 0xff) << 16) + + ((dataBuffer[i * 4 + 3] & 0xff) << 24); + } + + // Do a round of processing + final int AA = A; + final int BB = B; + final int CC = C; + final int DD = D; + round1(d); + round2(d); + round3(d); + A += AA; + B += BB; + C += CC; + D += DD; + + } + + protected void round1(final int[] d) { + A = rotintlft((A + F(B, C, D) + d[0]), 3); + D = rotintlft((D + F(A, B, C) + d[1]), 7); + C = rotintlft((C + F(D, A, B) + d[2]), 11); + B = rotintlft((B + F(C, D, A) + d[3]), 19); + + A = rotintlft((A + F(B, C, D) + d[4]), 3); + D = rotintlft((D + F(A, B, C) + d[5]), 7); + C = rotintlft((C + F(D, A, B) + d[6]), 11); + B = rotintlft((B + F(C, D, A) + d[7]), 19); + + A = rotintlft((A + F(B, C, D) + d[8]), 3); + D = rotintlft((D + F(A, B, C) + d[9]), 7); + C = rotintlft((C + F(D, A, B) + d[10]), 11); + B = rotintlft((B + F(C, D, A) + d[11]), 19); + + A = rotintlft((A + F(B, C, D) + d[12]), 3); + D = rotintlft((D + F(A, B, C) + d[13]), 7); + C = rotintlft((C + F(D, A, B) + d[14]), 11); + B = rotintlft((B + F(C, D, A) + d[15]), 19); + } + + protected void round2(final int[] d) { + A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); + + } + + protected void round3(final int[] d) { + A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); + + } + + } + + /** + * Cryptography support - HMACMD5 - algorithmically based on various web + * resources by Karl Wright + */ + static class HMACMD5 { + protected byte[] ipad; + protected byte[] opad; + protected MessageDigest md5; + + HMACMD5(final byte[] input) throws NTLMEngineException { + byte[] key = input; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (final Exception ex) { + // Umm, the algorithm doesn't exist - throw an + // NTLMEngineException! + throw new NTLMEngineException( + "Error getting md5 message digest implementation: " + ex.getMessage(), ex); + } + + // Initialize the pad buffers with the key + ipad = new byte[64]; + opad = new byte[64]; + + int keyLength = key.length; + if (keyLength > 64) { + // Use MD5 of the key instead, as described in RFC 2104 + md5.update(key); + key = md5.digest(); + keyLength = key.length; + } + int i = 0; + while (i < keyLength) { + ipad[i] = (byte) (key[i] ^ (byte) 0x36); + opad[i] = (byte) (key[i] ^ (byte) 0x5c); + i++; + } + while (i < 64) { + ipad[i] = (byte) 0x36; + opad[i] = (byte) 0x5c; + i++; + } + + // Very important: update the digest with the ipad buffer + md5.reset(); + md5.update(ipad); + + } + + /** Grab the current digest. This is the "answer". */ + byte[] getOutput() { + final byte[] digest = md5.digest(); + md5.update(opad); + return md5.digest(digest); + } + + /** Update by adding a complete array */ + void update(final byte[] input) { + md5.update(input); + } + + /** Update the algorithm */ + void update(final byte[] input, final int offset, final int length) { + md5.update(input, offset, length); + } + + } + + public String generateType1Msg( + final String domain, + final String workstation) throws NTLMEngineException { + return getType1Message(workstation, domain); + } + + public String generateType3Msg( + final String username, + final String password, + final String domain, + final String workstation, + final String challenge) throws NTLMEngineException { + final Type2Message t2m = new Type2Message(challenge); + return getType3Message( + username, + password, + workstation, + domain, + t2m.getChallenge(), + t2m.getFlags(), + t2m.getTarget(), + t2m.getTargetInfo()); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMScheme.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMScheme.java new file mode 100644 index 000000000..68873954b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMScheme.java @@ -0,0 +1,164 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.InvalidCredentialsException; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.auth.NTCredentials; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * NTLM is a proprietary authentication scheme developed by Microsoft + * and optimized for Windows platforms. + * + * @since 4.0 + */ +@NotThreadSafe +public class NTLMScheme extends AuthSchemeBase { + + enum State { + UNINITIATED, + CHALLENGE_RECEIVED, + MSG_TYPE1_GENERATED, + MSG_TYPE2_RECEVIED, + MSG_TYPE3_GENERATED, + FAILED, + } + + private final NTLMEngine engine; + + private State state; + private String challenge; + + public NTLMScheme(final NTLMEngine engine) { + super(); + Args.notNull(engine, "NTLM engine"); + this.engine = engine; + this.state = State.UNINITIATED; + this.challenge = null; + } + + /** + * @since 4.3 + */ + public NTLMScheme() { + this(new NTLMEngineImpl()); + } + + public String getSchemeName() { + return "ntlm"; + } + + public String getParameter(final String name) { + // String parameters not supported + return null; + } + + public String getRealm() { + // NTLM does not support the concept of an authentication realm + return null; + } + + public boolean isConnectionBased() { + return true; + } + + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, + final int beginIndex, final int endIndex) throws MalformedChallengeException { + this.challenge = buffer.substringTrimmed(beginIndex, endIndex); + if (this.challenge.length() == 0) { + if (this.state == State.UNINITIATED) { + this.state = State.CHALLENGE_RECEIVED; + } else { + this.state = State.FAILED; + } + } else { + if (this.state.compareTo(State.MSG_TYPE1_GENERATED) < 0) { + this.state = State.FAILED; + throw new MalformedChallengeException("Out of sequence NTLM response message"); + } else if (this.state == State.MSG_TYPE1_GENERATED) { + this.state = State.MSG_TYPE2_RECEVIED; + } + } + } + + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + NTCredentials ntcredentials = null; + try { + ntcredentials = (NTCredentials) credentials; + } catch (final ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for NTLM authentication: " + + credentials.getClass().getName()); + } + String response = null; + if (this.state == State.FAILED) { + throw new AuthenticationException("NTLM authentication failed"); + } else if (this.state == State.CHALLENGE_RECEIVED) { + response = this.engine.generateType1Msg( + ntcredentials.getDomain(), + ntcredentials.getWorkstation()); + this.state = State.MSG_TYPE1_GENERATED; + } else if (this.state == State.MSG_TYPE2_RECEVIED) { + response = this.engine.generateType3Msg( + ntcredentials.getUserName(), + ntcredentials.getPassword(), + ntcredentials.getDomain(), + ntcredentials.getWorkstation(), + this.challenge); + this.state = State.MSG_TYPE3_GENERATED; + } else { + throw new AuthenticationException("Unexpected state: " + this.state); + } + final CharArrayBuffer buffer = new CharArrayBuffer(32); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": NTLM "); + buffer.append(response); + return new BufferedHeader(buffer); + } + + public boolean isComplete() { + return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMSchemeFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMSchemeFactory.java new file mode 100644 index 000000000..0484df643 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/NTLMSchemeFactory.java @@ -0,0 +1,56 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.auth; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeFactory; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link AuthSchemeProvider} implementation that creates and initializes + * {@link NTLMScheme} instances configured to use the default {@link NTLMEngine} + * implementation. + * + * @since 4.1 + */ +@Immutable +@SuppressWarnings("deprecation") +public class NTLMSchemeFactory implements AuthSchemeFactory, AuthSchemeProvider { + + public AuthScheme newInstance(final HttpParams params) { + return new NTLMScheme(); + } + + public AuthScheme create(final HttpContext context) { + return new NTLMScheme(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/RFC2617Scheme.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/RFC2617Scheme.java new file mode 100644 index 000000000..93d1bf843 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/RFC2617Scheme.java @@ -0,0 +1,151 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.ChallengeState; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.auth.params.AuthPNames; +import ch.boye.httpclientandroidlib.message.BasicHeaderValueParser; +import ch.boye.httpclientandroidlib.message.HeaderValueParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract authentication scheme class that lays foundation for all + * RFC 2617 compliant authentication schemes and provides capabilities common + * to all authentication schemes defined in RFC 2617. + * + * @since 4.0 + */ +@SuppressWarnings("deprecation") +@NotThreadSafe // AuthSchemeBase, params +public abstract class RFC2617Scheme extends AuthSchemeBase { + + private final Map<String, String> params; + private final Charset credentialsCharset; + + /** + * Creates an instance of <tt>RFC2617Scheme</tt> with the given challenge + * state. + * + * @since 4.2 + * + * @deprecated (4.3) do not use. + */ + @Deprecated + public RFC2617Scheme(final ChallengeState challengeState) { + super(challengeState); + this.params = new HashMap<String, String>(); + this.credentialsCharset = Consts.ASCII; + } + + /** + * @since 4.3 + */ + public RFC2617Scheme(final Charset credentialsCharset) { + super(); + this.params = new HashMap<String, String>(); + this.credentialsCharset = credentialsCharset != null ? credentialsCharset : Consts.ASCII; + } + + public RFC2617Scheme() { + this(Consts.ASCII); + } + + + /** + * @since 4.3 + */ + public Charset getCredentialsCharset() { + return credentialsCharset; + } + + String getCredentialsCharset(final HttpRequest request) { + String charset = (String) request.getParams().getParameter(AuthPNames.CREDENTIAL_CHARSET); + if (charset == null) { + charset = getCredentialsCharset().name(); + } + return charset; + } + + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, final int pos, final int len) throws MalformedChallengeException { + final HeaderValueParser parser = BasicHeaderValueParser.INSTANCE; + final ParserCursor cursor = new ParserCursor(pos, buffer.length()); + final HeaderElement[] elements = parser.parseElements(buffer, cursor); + if (elements.length == 0) { + throw new MalformedChallengeException("Authentication challenge is empty"); + } + this.params.clear(); + for (final HeaderElement element : elements) { + this.params.put(element.getName().toLowerCase(Locale.ENGLISH), element.getValue()); + } + } + + /** + * Returns authentication parameters map. Keys in the map are lower-cased. + * + * @return the map of authentication parameters + */ + protected Map<String, String> getParameters() { + return this.params; + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + public String getParameter(final String name) { + if (name == null) { + return null; + } + return this.params.get(name.toLowerCase(Locale.ENGLISH)); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return the authentication realm + */ + public String getRealm() { + return getParameter("realm"); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/SpnegoTokenGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/SpnegoTokenGenerator.java new file mode 100644 index 000000000..5ab0348ad --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/SpnegoTokenGenerator.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import java.io.IOException; + +/** + * Abstract SPNEGO token generator. Implementations should take an Kerberos ticket and transform + * into a SPNEGO token. + * <p> + * Implementations of this interface are expected to be thread-safe. + * + * @since 4.1 + * + * @deprecated (4.2) subclass {@link KerberosScheme} and override + * {@link KerberosScheme#generateGSSToken(byte[], org.ietf.jgss.Oid, String)} + */ +@Deprecated +public interface SpnegoTokenGenerator { + + byte [] generateSpnegoDERObject(byte [] kerberosTicket) throws IOException; + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/UnsupportedDigestAlgorithmException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/UnsupportedDigestAlgorithmException.java new file mode 100644 index 000000000..71a6f892f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/UnsupportedDigestAlgorithmException.java @@ -0,0 +1,69 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.auth; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Authentication credentials required to respond to a authentication + * challenge are invalid + * + * + * @since 4.0 + */ +@Immutable +public class UnsupportedDigestAlgorithmException extends RuntimeException { + + private static final long serialVersionUID = 319558534317118022L; + + /** + * Creates a new UnsupportedAuthAlgoritmException with a <tt>null</tt> detail message. + */ + public UnsupportedDigestAlgorithmException() { + super(); + } + + /** + * Creates a new UnsupportedAuthAlgoritmException with the specified message. + * + * @param message the exception detail message + */ + public UnsupportedDigestAlgorithmException(final String message) { + super(message); + } + + /** + * Creates a new UnsupportedAuthAlgoritmException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public UnsupportedDigestAlgorithmException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/package-info.java new file mode 100644 index 000000000..9e35c852d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/auth/package-info.java @@ -0,0 +1,32 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of standard and common HTTP authentication + * schemes. + */ +package ch.boye.httpclientandroidlib.impl.auth; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java new file mode 100644 index 000000000..67798a683 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java @@ -0,0 +1,164 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.HashMap; +import java.util.Map; + +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.pool.ConnPoolControl; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <p>The <code>AIMDBackoffManager</code> applies an additive increase, + * multiplicative decrease (AIMD) to managing a dynamic limit to + * the number of connections allowed to a given host. You may want + * to experiment with the settings for the cooldown periods and the + * backoff factor to get the adaptive behavior you want.</p> + * + * <p>Generally speaking, shorter cooldowns will lead to more steady-state + * variability but faster reaction times, while longer cooldowns + * will lead to more stable equilibrium behavior but slower reaction + * times.</p> + * + * <p>Similarly, higher backoff factors promote greater + * utilization of available capacity at the expense of fairness + * among clients. Lower backoff factors allow equal distribution of + * capacity among clients (fairness) to happen faster, at the + * expense of having more server capacity unused in the short term.</p> + * + * @since 4.2 + */ +public class AIMDBackoffManager implements BackoffManager { + + private final ConnPoolControl<HttpRoute> connPerRoute; + private final Clock clock; + private final Map<HttpRoute,Long> lastRouteProbes; + private final Map<HttpRoute,Long> lastRouteBackoffs; + private long coolDown = 5 * 1000L; + private double backoffFactor = 0.5; + private int cap = 2; // Per RFC 2616 sec 8.1.4 + + /** + * Creates an <code>AIMDBackoffManager</code> to manage + * per-host connection pool sizes represented by the + * given {@link ConnPoolControl}. + * @param connPerRoute per-host routing maximums to + * be managed + */ + public AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute) { + this(connPerRoute, new SystemClock()); + } + + AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute, final Clock clock) { + this.clock = clock; + this.connPerRoute = connPerRoute; + this.lastRouteProbes = new HashMap<HttpRoute,Long>(); + this.lastRouteBackoffs = new HashMap<HttpRoute,Long>(); + } + + public void backOff(final HttpRoute route) { + synchronized(connPerRoute) { + final int curr = connPerRoute.getMaxPerRoute(route); + final Long lastUpdate = getLastUpdate(lastRouteBackoffs, route); + final long now = clock.getCurrentTime(); + if (now - lastUpdate.longValue() < coolDown) { + return; + } + connPerRoute.setMaxPerRoute(route, getBackedOffPoolSize(curr)); + lastRouteBackoffs.put(route, Long.valueOf(now)); + } + } + + private int getBackedOffPoolSize(final int curr) { + if (curr <= 1) { + return 1; + } + return (int)(Math.floor(backoffFactor * curr)); + } + + public void probe(final HttpRoute route) { + synchronized(connPerRoute) { + final int curr = connPerRoute.getMaxPerRoute(route); + final int max = (curr >= cap) ? cap : curr + 1; + final Long lastProbe = getLastUpdate(lastRouteProbes, route); + final Long lastBackoff = getLastUpdate(lastRouteBackoffs, route); + final long now = clock.getCurrentTime(); + if (now - lastProbe.longValue() < coolDown || now - lastBackoff.longValue() < coolDown) { + return; + } + connPerRoute.setMaxPerRoute(route, max); + lastRouteProbes.put(route, Long.valueOf(now)); + } + } + + private Long getLastUpdate(final Map<HttpRoute,Long> updates, final HttpRoute route) { + Long lastUpdate = updates.get(route); + if (lastUpdate == null) { + lastUpdate = Long.valueOf(0L); + } + return lastUpdate; + } + + /** + * Sets the factor to use when backing off; the new + * per-host limit will be roughly the current max times + * this factor. <code>Math.floor</code> is applied in the + * case of non-integer outcomes to ensure we actually + * decrease the pool size. Pool sizes are never decreased + * below 1, however. Defaults to 0.5. + * @param d must be between 0.0 and 1.0, exclusive. + */ + public void setBackoffFactor(final double d) { + Args.check(d > 0.0 && d < 1.0, "Backoff factor must be 0.0 < f < 1.0"); + backoffFactor = d; + } + + /** + * Sets the amount of time, in milliseconds, to wait between + * adjustments in pool sizes for a given host, to allow + * enough time for the adjustments to take effect. Defaults + * to 5000L (5 seconds). + * @param l must be positive + */ + public void setCooldownMillis(final long l) { + Args.positive(coolDown, "Cool down"); + coolDown = l; + } + + /** + * Sets the absolute maximum per-host connection pool size to + * probe up to; defaults to 2 (the default per-host max). + * @param cap must be >= 1 + */ + public void setPerHostConnectionCap(final int cap) { + Args.positive(cap, "Per host connection cap"); + this.cap = cap; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java new file mode 100644 index 000000000..a02c1a03b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java @@ -0,0 +1,189 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeRegistry; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.params.AuthPolicy; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Base class for {@link AuthenticationHandler} implementations. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.client.AuthenticationStrategy} + */ +@Deprecated +@Immutable +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = + Collections.unmodifiableList(Arrays.asList(new String[] { + AuthPolicy.SPNEGO, + AuthPolicy.NTLM, + AuthPolicy.DIGEST, + AuthPolicy.BASIC + })); + + public AbstractAuthenticationHandler() { + super(); + } + + protected Map<String, Header> parseChallenges( + final Header[] headers) throws MalformedChallengeException { + + final Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (final Header header : headers) { + final CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int endIndex = pos; + final String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + /** + * Returns default list of auth scheme names in their order of preference. + * + * @return list of auth scheme names + */ + protected List<String> getAuthPreferences() { + return DEFAULT_SCHEME_PRIORITY; + } + + /** + * Returns default list of auth scheme names in their order of preference + * based on the HTTP response and the current execution context. + * + * @param response HTTP response. + * @param context HTTP execution context. + * + * @since 4.1 + */ + protected List<String> getAuthPreferences( + final HttpResponse response, + final HttpContext context) { + return getAuthPreferences(); + } + + public AuthScheme selectScheme( + final Map<String, Header> challenges, + final HttpResponse response, + final HttpContext context) throws AuthenticationException { + + final AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute( + ClientContext.AUTHSCHEME_REGISTRY); + Asserts.notNull(registry, "AuthScheme registry"); + Collection<String> authPrefs = getAuthPreferences(response, context); + if (authPrefs == null) { + authPrefs = DEFAULT_SCHEME_PRIORITY; + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + + authPrefs); + } + + AuthScheme authScheme = null; + for (final String id: authPrefs) { + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + + if (challenge != null) { + if (this.log.isDebugEnabled()) { + this.log.debug(id + " authentication scheme selected"); + } + try { + authScheme = registry.getAuthScheme(id, response.getParams()); + break; + } catch (final IllegalStateException e) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + if (authScheme == null) { + // If none selected, something is wrong + throw new AuthenticationException( + "Unable to respond to any of these challenges: " + + challenges); + } + return authScheme; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java new file mode 100644 index 000000000..f862c9886 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java @@ -0,0 +1,990 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestInterceptor; +import ch.boye.httpclientandroidlib.HttpResponseInterceptor; +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthSchemeRegistry; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.RequestDirector; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.params.AuthPolicy; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.params.CookiePolicy; +import ch.boye.httpclientandroidlib.client.params.HttpClientParamConfig; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManagerFactory; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.cookie.CookieSpecRegistry; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.auth.BasicSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.DigestSchemeFactory; +/* KerberosSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.auth.NTLMSchemeFactory; +/* SPNegoSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.conn.BasicClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.DefaultHttpRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.SchemeRegistryFactory; +import ch.boye.httpclientandroidlib.impl.cookie.BestMatchSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.BrowserCompatSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.IgnoreSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.NetscapeDraftSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2109SpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2965SpecFactory; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.BasicHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.DefaultedHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Base class for {@link ch.boye.httpclientandroidlib.client.HttpClient} implementations. + * This class acts as a facade to a number of special purpose handler or + * strategy implementations responsible for handling of a particular aspect + * of the HTTP protocol such as redirect or authentication handling or + * making decision about connection persistence and keep alive duration. + * This enables the users to selectively replace default implementation + * of those aspects with custom, application specific ones. This class + * also provides factory methods to instantiate those objects: + * <ul> + * <li>{@link HttpRequestExecutor}</li> object used to transmit messages + * over HTTP connections. The {@link #createRequestExecutor()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link BasicHttpProcessor}</li> object to manage a list of protocol + * interceptors and apply cross-cutting protocol logic to all incoming + * and outgoing HTTP messages. The {@link #createHttpProcessor()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link HttpRequestRetryHandler}</li> object used to decide whether + * or not a failed HTTP request is safe to retry automatically. + * The {@link #createHttpRequestRetryHandler()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link ClientConnectionManager}</li> object used to manage + * persistent HTTP connections. + * <li>{@link ConnectionReuseStrategy}</li> object used to decide whether + * or not a HTTP connection can be kept alive and re-used for subsequent + * HTTP requests. The {@link #createConnectionReuseStrategy()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link ConnectionKeepAliveStrategy}</li> object used to decide how + * long a persistent HTTP connection can be kept alive. + * The {@link #createConnectionKeepAliveStrategy()} must be + * implemented by concrete super classes to instantiate this object. + * <li>{@link CookieSpecRegistry}</li> object used to maintain a list of + * supported cookie specifications. + * The {@link #createCookieSpecRegistry()} must be implemented by concrete + * super classes to instantiate this object. + * <li>{@link CookieStore}</li> object used to maintain a collection of + * cookies. The {@link #createCookieStore()} must be implemented by + * concrete super classes to instantiate this object. + * <li>{@link AuthSchemeRegistry}</li> object used to maintain a list of + * supported authentication schemes. + * The {@link #createAuthSchemeRegistry()} must be implemented by concrete + * super classes to instantiate this object. + * <li>{@link CredentialsProvider}</li> object used to maintain + * a collection user credentials. The {@link #createCredentialsProvider()} + * must be implemented by concrete super classes to instantiate + * this object. + * <li>{@link AuthenticationStrategy}</li> object used to authenticate + * against the target host. + * The {@link #createTargetAuthenticationStrategy()} must be implemented + * by concrete super classes to instantiate this object. + * <li>{@link AuthenticationStrategy}</li> object used to authenticate + * against the proxy host. + * The {@link #createProxyAuthenticationStrategy()} must be implemented + * by concrete super classes to instantiate this object. + * <li>{@link HttpRoutePlanner}</li> object used to calculate a route + * for establishing a connection to the target host. The route + * may involve multiple intermediate hops. + * The {@link #createHttpRoutePlanner()} must be implemented + * by concrete super classes to instantiate this object. + * <li>{@link RedirectStrategy}</li> object used to determine if an HTTP + * request should be redirected to a new location in response to an HTTP + * response received from the target server. + * <li>{@link UserTokenHandler}</li> object used to determine if the + * execution context is user identity specific. + * The {@link #createUserTokenHandler()} must be implemented by + * concrete super classes to instantiate this object. + * </ul> + * <p> + * This class also maintains a list of protocol interceptors intended + * for processing outgoing requests and incoming responses and provides + * methods for managing those interceptors. New protocol interceptors can be + * introduced to the protocol processor chain or removed from it if needed. + * Internally protocol interceptors are stored in a simple + * {@link java.util.ArrayList}. They are executed in the same natural order + * as they are added to the list. + * <p> + * AbstractHttpClient is thread safe. It is recommended that the same + * instance of this class is reused for multiple request executions. + * When an instance of DefaultHttpClient is no longer needed and is about + * to go out of scope the connection manager associated with it must be + * shut down by calling {@link ClientConnectionManager#shutdown()}! + * + * @since 4.0 + * + * @deprecated (4.3) use {@link HttpClientBuilder}. + */ +@ThreadSafe +@Deprecated +public abstract class AbstractHttpClient extends CloseableHttpClient { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** The parameters. */ + @GuardedBy("this") + private HttpParams defaultParams; + + /** The request executor. */ + @GuardedBy("this") + private HttpRequestExecutor requestExec; + + /** The connection manager. */ + @GuardedBy("this") + private ClientConnectionManager connManager; + + /** The connection re-use strategy. */ + @GuardedBy("this") + private ConnectionReuseStrategy reuseStrategy; + + /** The connection keep-alive strategy. */ + @GuardedBy("this") + private ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The cookie spec registry. */ + @GuardedBy("this") + private CookieSpecRegistry supportedCookieSpecs; + + /** The authentication scheme registry. */ + @GuardedBy("this") + private AuthSchemeRegistry supportedAuthSchemes; + + /** The HTTP protocol processor and its immutable copy. */ + @GuardedBy("this") + private BasicHttpProcessor mutableProcessor; + + @GuardedBy("this") + private ImmutableHttpProcessor protocolProcessor; + + /** The request retry handler. */ + @GuardedBy("this") + private HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + @GuardedBy("this") + private RedirectStrategy redirectStrategy; + + /** The target authentication handler. */ + @GuardedBy("this") + private AuthenticationStrategy targetAuthStrategy; + + /** The proxy authentication handler. */ + @GuardedBy("this") + private AuthenticationStrategy proxyAuthStrategy; + + /** The cookie store. */ + @GuardedBy("this") + private CookieStore cookieStore; + + /** The credentials provider. */ + @GuardedBy("this") + private CredentialsProvider credsProvider; + + /** The route planner. */ + @GuardedBy("this") + private HttpRoutePlanner routePlanner; + + /** The user token handler. */ + @GuardedBy("this") + private UserTokenHandler userTokenHandler; + + /** The connection backoff strategy. */ + @GuardedBy("this") + private ConnectionBackoffStrategy connectionBackoffStrategy; + + /** The backoff manager. */ + @GuardedBy("this") + private BackoffManager backoffManager; + + /** + * Creates a new HTTP client. + * + * @param conman the connection manager + * @param params the parameters + */ + protected AbstractHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(); + defaultParams = params; + connManager = conman; + } // constructor + + + protected abstract HttpParams createHttpParams(); + + + protected abstract BasicHttpProcessor createHttpProcessor(); + + + protected HttpContext createHttpContext() { + final HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.SCHEME_REGISTRY, + getConnectionManager().getSchemeRegistry()); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.COOKIE_STORE, + getCookieStore()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + + + protected ClientConnectionManager createClientConnectionManager() { + final SchemeRegistry registry = SchemeRegistryFactory.createDefault(); + + ClientConnectionManager connManager = null; + final HttpParams params = getParams(); + + ClientConnectionManagerFactory factory = null; + + final String className = (String) params.getParameter( + ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME); + if (className != null) { + try { + final Class<?> clazz = Class.forName(className); + factory = (ClientConnectionManagerFactory) clazz.newInstance(); + } catch (final ClassNotFoundException ex) { + throw new IllegalStateException("Invalid class name: " + className); + } catch (final IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } catch (final InstantiationException ex) { + throw new InstantiationError(ex.getMessage()); + } + } + if (factory != null) { + connManager = factory.newInstance(params, registry); + } else { + connManager = new BasicClientConnectionManager(registry); + } + + return connManager; + } + + + protected AuthSchemeRegistry createAuthSchemeRegistry() { + final AuthSchemeRegistry registry = new AuthSchemeRegistry(); + registry.register( + AuthPolicy.BASIC, + new BasicSchemeFactory()); + registry.register( + AuthPolicy.DIGEST, + new DigestSchemeFactory()); + registry.register( + AuthPolicy.NTLM, + new NTLMSchemeFactory()); + /* SPNegoSchemeFactory removed by HttpClient for Android script. */ + /* KerberosSchemeFactory removed by HttpClient for Android script. */ + return registry; + } + + + protected CookieSpecRegistry createCookieSpecRegistry() { + final CookieSpecRegistry registry = new CookieSpecRegistry(); + registry.register( + CookiePolicy.BEST_MATCH, + new BestMatchSpecFactory()); + registry.register( + CookiePolicy.BROWSER_COMPATIBILITY, + new BrowserCompatSpecFactory()); + registry.register( + CookiePolicy.NETSCAPE, + new NetscapeDraftSpecFactory()); + registry.register( + CookiePolicy.RFC_2109, + new RFC2109SpecFactory()); + registry.register( + CookiePolicy.RFC_2965, + new RFC2965SpecFactory()); + registry.register( + CookiePolicy.IGNORE_COOKIES, + new IgnoreSpecFactory()); + return registry; + } + + protected HttpRequestExecutor createRequestExecutor() { + return new HttpRequestExecutor(); + } + + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + return new DefaultConnectionReuseStrategy(); + } + + protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() { + return new DefaultConnectionKeepAliveStrategy(); + } + + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(); + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + protected RedirectHandler createRedirectHandler() { + return new DefaultRedirectHandler(); + } + + protected AuthenticationStrategy createTargetAuthenticationStrategy() { + return new TargetAuthenticationStrategy(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + protected AuthenticationHandler createTargetAuthenticationHandler() { + return new DefaultTargetAuthenticationHandler(); + } + + protected AuthenticationStrategy createProxyAuthenticationStrategy() { + return new ProxyAuthenticationStrategy(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + protected AuthenticationHandler createProxyAuthenticationHandler() { + return new DefaultProxyAuthenticationHandler(); + } + + protected CookieStore createCookieStore() { + return new BasicCookieStore(); + } + + protected CredentialsProvider createCredentialsProvider() { + return new BasicCredentialsProvider(); + } + + protected HttpRoutePlanner createHttpRoutePlanner() { + return new DefaultHttpRoutePlanner(getConnectionManager().getSchemeRegistry()); + } + + protected UserTokenHandler createUserTokenHandler() { + return new DefaultUserTokenHandler(); + } + + // non-javadoc, see interface HttpClient + public synchronized final HttpParams getParams() { + if (defaultParams == null) { + defaultParams = createHttpParams(); + } + return defaultParams; + } + + /** + * Replaces the parameters. + * The implementation here does not update parameters of dependent objects. + * + * @param params the new default parameters + */ + public synchronized void setParams(final HttpParams params) { + defaultParams = params; + } + + + public synchronized final ClientConnectionManager getConnectionManager() { + if (connManager == null) { + connManager = createClientConnectionManager(); + } + return connManager; + } + + + public synchronized final HttpRequestExecutor getRequestExecutor() { + if (requestExec == null) { + requestExec = createRequestExecutor(); + } + return requestExec; + } + + + public synchronized final AuthSchemeRegistry getAuthSchemes() { + if (supportedAuthSchemes == null) { + supportedAuthSchemes = createAuthSchemeRegistry(); + } + return supportedAuthSchemes; + } + + public synchronized void setAuthSchemes(final AuthSchemeRegistry registry) { + supportedAuthSchemes = registry; + } + + public synchronized final ConnectionBackoffStrategy getConnectionBackoffStrategy() { + return connectionBackoffStrategy; + } + + public synchronized void setConnectionBackoffStrategy(final ConnectionBackoffStrategy strategy) { + connectionBackoffStrategy = strategy; + } + + public synchronized final CookieSpecRegistry getCookieSpecs() { + if (supportedCookieSpecs == null) { + supportedCookieSpecs = createCookieSpecRegistry(); + } + return supportedCookieSpecs; + } + + public synchronized final BackoffManager getBackoffManager() { + return backoffManager; + } + + public synchronized void setBackoffManager(final BackoffManager manager) { + backoffManager = manager; + } + + public synchronized void setCookieSpecs(final CookieSpecRegistry registry) { + supportedCookieSpecs = registry; + } + + public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() { + if (reuseStrategy == null) { + reuseStrategy = createConnectionReuseStrategy(); + } + return reuseStrategy; + } + + + public synchronized void setReuseStrategy(final ConnectionReuseStrategy strategy) { + this.reuseStrategy = strategy; + } + + + public synchronized final ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() { + if (keepAliveStrategy == null) { + keepAliveStrategy = createConnectionKeepAliveStrategy(); + } + return keepAliveStrategy; + } + + + public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy strategy) { + this.keepAliveStrategy = strategy; + } + + + public synchronized final HttpRequestRetryHandler getHttpRequestRetryHandler() { + if (retryHandler == null) { + retryHandler = createHttpRequestRetryHandler(); + } + return retryHandler; + } + + public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler handler) { + this.retryHandler = handler; + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + public synchronized final RedirectHandler getRedirectHandler() { + return createRedirectHandler(); + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + public synchronized void setRedirectHandler(final RedirectHandler handler) { + this.redirectStrategy = new DefaultRedirectStrategyAdaptor(handler); + } + + /** + * @since 4.1 + */ + public synchronized final RedirectStrategy getRedirectStrategy() { + if (redirectStrategy == null) { + redirectStrategy = new DefaultRedirectStrategy(); + } + return redirectStrategy; + } + + /** + * @since 4.1 + */ + public synchronized void setRedirectStrategy(final RedirectStrategy strategy) { + this.redirectStrategy = strategy; + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized final AuthenticationHandler getTargetAuthenticationHandler() { + return createTargetAuthenticationHandler(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized void setTargetAuthenticationHandler(final AuthenticationHandler handler) { + this.targetAuthStrategy = new AuthenticationStrategyAdaptor(handler); + } + + /** + * @since 4.2 + */ + public synchronized final AuthenticationStrategy getTargetAuthenticationStrategy() { + if (targetAuthStrategy == null) { + targetAuthStrategy = createTargetAuthenticationStrategy(); + } + return targetAuthStrategy; + } + + /** + * @since 4.2 + */ + public synchronized void setTargetAuthenticationStrategy(final AuthenticationStrategy strategy) { + this.targetAuthStrategy = strategy; + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized final AuthenticationHandler getProxyAuthenticationHandler() { + return createProxyAuthenticationHandler(); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + public synchronized void setProxyAuthenticationHandler(final AuthenticationHandler handler) { + this.proxyAuthStrategy = new AuthenticationStrategyAdaptor(handler); + } + + /** + * @since 4.2 + */ + public synchronized final AuthenticationStrategy getProxyAuthenticationStrategy() { + if (proxyAuthStrategy == null) { + proxyAuthStrategy = createProxyAuthenticationStrategy(); + } + return proxyAuthStrategy; + } + + /** + * @since 4.2 + */ + public synchronized void setProxyAuthenticationStrategy(final AuthenticationStrategy strategy) { + this.proxyAuthStrategy = strategy; + } + + public synchronized final CookieStore getCookieStore() { + if (cookieStore == null) { + cookieStore = createCookieStore(); + } + return cookieStore; + } + + public synchronized void setCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + public synchronized final CredentialsProvider getCredentialsProvider() { + if (credsProvider == null) { + credsProvider = createCredentialsProvider(); + } + return credsProvider; + } + + public synchronized void setCredentialsProvider(final CredentialsProvider credsProvider) { + this.credsProvider = credsProvider; + } + + public synchronized final HttpRoutePlanner getRoutePlanner() { + if (this.routePlanner == null) { + this.routePlanner = createHttpRoutePlanner(); + } + return this.routePlanner; + } + + public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + } + + public synchronized final UserTokenHandler getUserTokenHandler() { + if (this.userTokenHandler == null) { + this.userTokenHandler = createUserTokenHandler(); + } + return this.userTokenHandler; + } + + public synchronized void setUserTokenHandler(final UserTokenHandler handler) { + this.userTokenHandler = handler; + } + + protected synchronized final BasicHttpProcessor getHttpProcessor() { + if (mutableProcessor == null) { + mutableProcessor = createHttpProcessor(); + } + return mutableProcessor; + } + + private synchronized HttpProcessor getProtocolProcessor() { + if (protocolProcessor == null) { + // Get mutable HTTP processor + final BasicHttpProcessor proc = getHttpProcessor(); + // and create an immutable copy of it + final int reqc = proc.getRequestInterceptorCount(); + final HttpRequestInterceptor[] reqinterceptors = new HttpRequestInterceptor[reqc]; + for (int i = 0; i < reqc; i++) { + reqinterceptors[i] = proc.getRequestInterceptor(i); + } + final int resc = proc.getResponseInterceptorCount(); + final HttpResponseInterceptor[] resinterceptors = new HttpResponseInterceptor[resc]; + for (int i = 0; i < resc; i++) { + resinterceptors[i] = proc.getResponseInterceptor(i); + } + protocolProcessor = new ImmutableHttpProcessor(reqinterceptors, resinterceptors); + } + return protocolProcessor; + } + + public synchronized int getResponseInterceptorCount() { + return getHttpProcessor().getResponseInterceptorCount(); + } + + public synchronized HttpResponseInterceptor getResponseInterceptor(final int index) { + return getHttpProcessor().getResponseInterceptor(index); + } + + public synchronized HttpRequestInterceptor getRequestInterceptor(final int index) { + return getHttpProcessor().getRequestInterceptor(index); + } + + public synchronized int getRequestInterceptorCount() { + return getHttpProcessor().getRequestInterceptorCount(); + } + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + protocolProcessor = null; + } + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp, final int index) { + getHttpProcessor().addInterceptor(itcp, index); + protocolProcessor = null; + } + + public synchronized void clearResponseInterceptors() { + getHttpProcessor().clearResponseInterceptors(); + protocolProcessor = null; + } + + public synchronized void removeResponseInterceptorByClass(final Class<? extends HttpResponseInterceptor> clazz) { + getHttpProcessor().removeResponseInterceptorByClass(clazz); + protocolProcessor = null; + } + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + protocolProcessor = null; + } + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp, final int index) { + getHttpProcessor().addInterceptor(itcp, index); + protocolProcessor = null; + } + + public synchronized void clearRequestInterceptors() { + getHttpProcessor().clearRequestInterceptors(); + protocolProcessor = null; + } + + public synchronized void removeRequestInterceptorByClass(final Class<? extends HttpRequestInterceptor> clazz) { + getHttpProcessor().removeRequestInterceptorByClass(clazz); + protocolProcessor = null; + } + + @Override + protected final CloseableHttpResponse doExecute(final HttpHost target, final HttpRequest request, + final HttpContext context) + throws IOException, ClientProtocolException { + + Args.notNull(request, "HTTP request"); + // a null target may be acceptable, this depends on the route planner + // a null context is acceptable, default context created below + + HttpContext execContext = null; + RequestDirector director = null; + HttpRoutePlanner routePlanner = null; + ConnectionBackoffStrategy connectionBackoffStrategy = null; + BackoffManager backoffManager = null; + + // Initialize the request execution context making copies of + // all shared objects that are potentially threading unsafe. + synchronized (this) { + + final HttpContext defaultContext = createHttpContext(); + if (context == null) { + execContext = defaultContext; + } else { + execContext = new DefaultedHttpContext(context, defaultContext); + } + final HttpParams params = determineParams(request); + final RequestConfig config = HttpClientParamConfig.getRequestConfig(params); + execContext.setAttribute(ClientContext.REQUEST_CONFIG, config); + + // Create a director for this request + director = createClientRequestDirector( + getRequestExecutor(), + getConnectionManager(), + getConnectionReuseStrategy(), + getConnectionKeepAliveStrategy(), + getRoutePlanner(), + getProtocolProcessor(), + getHttpRequestRetryHandler(), + getRedirectStrategy(), + getTargetAuthenticationStrategy(), + getProxyAuthenticationStrategy(), + getUserTokenHandler(), + params); + routePlanner = getRoutePlanner(); + connectionBackoffStrategy = getConnectionBackoffStrategy(); + backoffManager = getBackoffManager(); + } + + try { + if (connectionBackoffStrategy != null && backoffManager != null) { + final HttpHost targetForRoute = (target != null) ? target + : (HttpHost) determineParams(request).getParameter( + ClientPNames.DEFAULT_HOST); + final HttpRoute route = routePlanner.determineRoute(targetForRoute, request, execContext); + + final CloseableHttpResponse out; + try { + out = CloseableHttpResponseProxy.newProxy( + director.execute(target, request, execContext)); + } catch (final RuntimeException re) { + if (connectionBackoffStrategy.shouldBackoff(re)) { + backoffManager.backOff(route); + } + throw re; + } catch (final Exception e) { + if (connectionBackoffStrategy.shouldBackoff(e)) { + backoffManager.backOff(route); + } + if (e instanceof HttpException) { + throw (HttpException)e; + } + if (e instanceof IOException) { + throw (IOException)e; + } + throw new UndeclaredThrowableException(e); + } + if (connectionBackoffStrategy.shouldBackoff(out)) { + backoffManager.backOff(route); + } else { + backoffManager.probe(route); + } + return out; + } else { + return CloseableHttpResponseProxy.newProxy( + director.execute(target, request, execContext)); + } + } catch(final HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } + + /** + * @deprecated (4.1) do not use + */ + @Deprecated + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + return new DefaultRequestDirector( + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectHandler, + targetAuthHandler, + proxyAuthHandler, + userTokenHandler, + params); + } + + /** + * @deprecated (4.2) do not use + */ + @Deprecated + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + return new DefaultRequestDirector( + log, + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectStrategy, + targetAuthHandler, + proxyAuthHandler, + userTokenHandler, + params); + } + + + /** + * @since 4.2 + */ + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationStrategy targetAuthStrategy, + final AuthenticationStrategy proxyAuthStrategy, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + return new DefaultRequestDirector( + log, + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectStrategy, + targetAuthStrategy, + proxyAuthStrategy, + userTokenHandler, + params); + } + + /** + * Obtains parameters for executing a request. + * The default implementation in this class creates a new + * {@link ClientParamsStack} from the request parameters + * and the client parameters. + * <br/> + * This method is called by the default implementation of + * {@link #execute(HttpHost,HttpRequest,HttpContext)} + * to obtain the parameters for the + * {@link DefaultRequestDirector}. + * + * @param req the request that will be executed + * + * @return the parameters to use + */ + protected HttpParams determineParams(final HttpRequest req) { + return new ClientParamsStack + (null, getParams(), req.getParams(), null); + } + + + public void close() { + getConnectionManager().shutdown(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java new file mode 100644 index 000000000..a65ce7e95 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java @@ -0,0 +1,172 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.LinkedList; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthOption; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.AuthenticationException; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthCache; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.params.AuthPolicy; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * @deprecated (4.2) do not use + */ +@Immutable +@Deprecated +class AuthenticationStrategyAdaptor implements AuthenticationStrategy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final AuthenticationHandler handler; + + public AuthenticationStrategyAdaptor(final AuthenticationHandler handler) { + super(); + this.handler = handler; + } + + public boolean isAuthenticationRequested( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) { + return this.handler.isAuthenticationRequested(response, context); + } + + public Map<String, Header> getChallenges( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + return this.handler.getChallenges(response, context); + } + + public Queue<AuthOption> select( + final Map<String, Header> challenges, + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(challenges, "Map of auth challenges"); + Args.notNull(authhost, "Host"); + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + + final Queue<AuthOption> options = new LinkedList<AuthOption>(); + final CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute( + ClientContext.CREDS_PROVIDER); + if (credsProvider == null) { + this.log.debug("Credentials provider not set in the context"); + return options; + } + + final AuthScheme authScheme; + try { + authScheme = this.handler.selectScheme(challenges, response, context); + } catch (final AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn(ex.getMessage(), ex); + } + return options; + } + final String id = authScheme.getSchemeName(); + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + authScheme.processChallenge(challenge); + + final AuthScope authScope = new AuthScope( + authhost.getHostName(), + authhost.getPort(), + authScheme.getRealm(), + authScheme.getSchemeName()); + + final Credentials credentials = credsProvider.getCredentials(authScope); + if (credentials != null) { + options.add(new AuthOption(authScheme, credentials)); + } + return options; + } + + public void authSucceeded( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE); + if (isCachable(authScheme)) { + if (authCache == null) { + authCache = new BasicAuthCache(); + context.setAttribute(ClientContext.AUTH_CACHE, authCache); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Caching '" + authScheme.getSchemeName() + + "' auth scheme for " + authhost); + } + authCache.put(authhost, authScheme); + } + } + + public void authFailed( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + final AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE); + if (authCache == null) { + return; + } + if (this.log.isDebugEnabled()) { + this.log.debug("Removing from cache '" + authScheme.getSchemeName() + + "' auth scheme for " + authhost); + } + authCache.remove(authhost); + } + + private boolean isCachable(final AuthScheme authScheme) { + if (authScheme == null || !authScheme.isComplete()) { + return false; + } + final String schemeName = authScheme.getSchemeName(); + return schemeName.equalsIgnoreCase(AuthPolicy.BASIC) || + schemeName.equalsIgnoreCase(AuthPolicy.DIGEST); + } + + public AuthenticationHandler getHandler() { + return this.handler; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java new file mode 100644 index 000000000..38b7df6d2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Queue; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthOption; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.client.AuthCache; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +@Immutable +abstract class AuthenticationStrategyImpl implements AuthenticationStrategy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = + Collections.unmodifiableList(Arrays.asList(AuthSchemes.SPNEGO, + AuthSchemes.KERBEROS, + AuthSchemes.NTLM, + AuthSchemes.DIGEST, + AuthSchemes.BASIC)); + + private final int challengeCode; + private final String headerName; + + AuthenticationStrategyImpl(final int challengeCode, final String headerName) { + super(); + this.challengeCode = challengeCode; + this.headerName = headerName; + } + + public boolean isAuthenticationRequested( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + final int status = response.getStatusLine().getStatusCode(); + return status == this.challengeCode; + } + + public Map<String, Header> getChallenges( + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(response, "HTTP response"); + final Header[] headers = response.getHeaders(this.headerName); + final Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (final Header header : headers) { + final CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + final int endIndex = pos; + final String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + abstract Collection<String> getPreferredAuthSchemes(RequestConfig config); + + public Queue<AuthOption> select( + final Map<String, Header> challenges, + final HttpHost authhost, + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(challenges, "Map of auth challenges"); + Args.notNull(authhost, "Host"); + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + final Queue<AuthOption> options = new LinkedList<AuthOption>(); + final Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry(); + if (registry == null) { + this.log.debug("Auth scheme registry not set in the context"); + return options; + } + final CredentialsProvider credsProvider = clientContext.getCredentialsProvider(); + if (credsProvider == null) { + this.log.debug("Credentials provider not set in the context"); + return options; + } + final RequestConfig config = clientContext.getRequestConfig(); + Collection<String> authPrefs = getPreferredAuthSchemes(config); + if (authPrefs == null) { + authPrefs = DEFAULT_SCHEME_PRIORITY; + } + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + authPrefs); + } + + for (final String id: authPrefs) { + final Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + if (challenge != null) { + final AuthSchemeProvider authSchemeProvider = registry.lookup(id); + if (authSchemeProvider == null) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + continue; + } + final AuthScheme authScheme = authSchemeProvider.create(context); + authScheme.processChallenge(challenge); + + final AuthScope authScope = new AuthScope( + authhost.getHostName(), + authhost.getPort(), + authScheme.getRealm(), + authScheme.getSchemeName()); + + final Credentials credentials = credsProvider.getCredentials(authScope); + if (credentials != null) { + options.add(new AuthOption(authScheme, credentials)); + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + return options; + } + + public void authSucceeded( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + Args.notNull(authhost, "Host"); + Args.notNull(authScheme, "Auth scheme"); + Args.notNull(context, "HTTP context"); + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + if (isCachable(authScheme)) { + AuthCache authCache = clientContext.getAuthCache(); + if (authCache == null) { + authCache = new BasicAuthCache(); + clientContext.setAuthCache(authCache); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Caching '" + authScheme.getSchemeName() + + "' auth scheme for " + authhost); + } + authCache.put(authhost, authScheme); + } + } + + protected boolean isCachable(final AuthScheme authScheme) { + if (authScheme == null || !authScheme.isComplete()) { + return false; + } + final String schemeName = authScheme.getSchemeName(); + return schemeName.equalsIgnoreCase(AuthSchemes.BASIC) || + schemeName.equalsIgnoreCase(AuthSchemes.DIGEST); + } + + public void authFailed( + final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) { + Args.notNull(authhost, "Host"); + Args.notNull(context, "HTTP context"); + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + final AuthCache authCache = clientContext.getAuthCache(); + if (authCache != null) { + if (this.log.isDebugEnabled()) { + this.log.debug("Clearing cached auth scheme for " + authhost); + } + authCache.remove(authhost); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java new file mode 100644 index 000000000..70c0e3404 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java @@ -0,0 +1,190 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * {@link HttpClient} implementation that can automatically retry the request in case of + * a non-2xx response using the {@link ServiceUnavailableRetryStrategy} interface. + * + * @since 4.2 + * + * @deprecated (4.3) use {@link HttpClientBuilder}. + */ +@Deprecated +@ThreadSafe +public class AutoRetryHttpClient implements HttpClient { + + private final HttpClient backend; + + private final ServiceUnavailableRetryStrategy retryStrategy; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + public AutoRetryHttpClient( + final HttpClient client, final ServiceUnavailableRetryStrategy retryStrategy) { + super(); + Args.notNull(client, "HttpClient"); + Args.notNull(retryStrategy, "ServiceUnavailableRetryStrategy"); + this.backend = client; + this.retryStrategy = retryStrategy; + } + + /** + * Constructs a {@code AutoRetryHttpClient} with default caching settings that + * stores cache entries in memory and uses a vanilla + * {@link DefaultHttpClient} for backend requests. + */ + public AutoRetryHttpClient() { + this(new DefaultHttpClient(), new DefaultServiceUnavailableRetryStrategy()); + } + + /** + * Constructs a {@code AutoRetryHttpClient} with the given caching options that + * stores cache entries in memory and uses a vanilla + * {@link DefaultHttpClient} for backend requests. + * + * @param config + * retry configuration module options + */ + public AutoRetryHttpClient(final ServiceUnavailableRetryStrategy config) { + this(new DefaultHttpClient(), config); + } + + /** + * Constructs a {@code AutoRetryHttpClient} with default caching settings that + * stores cache entries in memory and uses the given {@link HttpClient} for + * backend requests. + * + * @param client + * used to make origin requests + */ + public AutoRetryHttpClient(final HttpClient client) { + this(client, new DefaultServiceUnavailableRetryStrategy()); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request) + throws IOException { + final HttpContext defaultContext = null; + return execute(target, request, defaultContext); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException { + return execute(target, request, responseHandler, null); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException { + final HttpResponse resp = execute(target, request, context); + return responseHandler.handleResponse(resp); + } + + public HttpResponse execute(final HttpUriRequest request) throws IOException { + final HttpContext context = null; + return execute(request, context); + } + + public HttpResponse execute(final HttpUriRequest request, final HttpContext context) + throws IOException { + final URI uri = request.getURI(); + final HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), + uri.getScheme()); + return execute(httpHost, request, context); + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException { + return execute(request, responseHandler, null); + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException { + final HttpResponse resp = execute(request, context); + return responseHandler.handleResponse(resp); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request, + final HttpContext context) throws IOException { + for (int c = 1;; c++) { + final HttpResponse response = backend.execute(target, request, context); + try { + if (retryStrategy.retryRequest(response, c, context)) { + EntityUtils.consume(response.getEntity()); + final long nextInterval = retryStrategy.getRetryInterval(); + try { + log.trace("Wait for " + nextInterval); + Thread.sleep(nextInterval); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + } else { + return response; + } + } catch (final RuntimeException ex) { + try { + EntityUtils.consume(response.getEntity()); + } catch (final IOException ioex) { + log.warn("I/O error consuming response content", ioex); + } + throw ex; + } + } + } + + public ClientConnectionManager getConnectionManager() { + return backend.getConnectionManager(); + } + + public HttpParams getParams() { + return backend.getParams(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java new file mode 100644 index 000000000..810f73a0a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java @@ -0,0 +1,105 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.HashMap; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.client.AuthCache; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.UnsupportedSchemeException; +import ch.boye.httpclientandroidlib.impl.conn.DefaultSchemePortResolver; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link AuthCache}. + * + * @since 4.0 + */ +@NotThreadSafe +public class BasicAuthCache implements AuthCache { + + private final HashMap<HttpHost, AuthScheme> map; + private final SchemePortResolver schemePortResolver; + + /** + * Default constructor. + * + * @since 4.3 + */ + public BasicAuthCache(final SchemePortResolver schemePortResolver) { + super(); + this.map = new HashMap<HttpHost, AuthScheme>(); + this.schemePortResolver = schemePortResolver != null ? schemePortResolver : + DefaultSchemePortResolver.INSTANCE; + } + + public BasicAuthCache() { + this(null); + } + + protected HttpHost getKey(final HttpHost host) { + if (host.getPort() <= 0) { + final int port; + try { + port = schemePortResolver.resolve(host); + } catch (final UnsupportedSchemeException ignore) { + return host; + } + return new HttpHost(host.getHostName(), port, host.getSchemeName()); + } else { + return host; + } + } + + public void put(final HttpHost host, final AuthScheme authScheme) { + Args.notNull(host, "HTTP host"); + this.map.put(getKey(host), authScheme); + } + + public AuthScheme get(final HttpHost host) { + Args.notNull(host, "HTTP host"); + return this.map.get(getKey(host)); + } + + public void remove(final HttpHost host) { + Args.notNull(host, "HTTP host"); + this.map.remove(getKey(host)); + } + + public void clear() { + this.map.clear(); + } + + @Override + public String toString() { + return this.map.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java new file mode 100644 index 000000000..624a8f81d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java @@ -0,0 +1,144 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieIdentityComparator; + +/** + * Default implementation of {@link CookieStore} + * + * + * @since 4.0 + */ +@ThreadSafe +public class BasicCookieStore implements CookieStore, Serializable { + + private static final long serialVersionUID = -7581093305228232025L; + + @GuardedBy("this") + private final TreeSet<Cookie> cookies; + + public BasicCookieStore() { + super(); + this.cookies = new TreeSet<Cookie>(new CookieIdentityComparator()); + } + + /** + * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies. + * If the given cookie has already expired it will not be added, but existing + * values will still be removed. + * + * @param cookie the {@link Cookie cookie} to be added + * + * @see #addCookies(Cookie[]) + * + */ + public synchronized void addCookie(final Cookie cookie) { + if (cookie != null) { + // first remove any old cookie that is equivalent + cookies.remove(cookie); + if (!cookie.isExpired(new Date())) { + cookies.add(cookie); + } + } + } + + /** + * Adds an array of {@link Cookie HTTP cookies}. Cookies are added individually and + * in the given array order. If any of the given cookies has already expired it will + * not be added, but existing values will still be removed. + * + * @param cookies the {@link Cookie cookies} to be added + * + * @see #addCookie(Cookie) + * + */ + public synchronized void addCookies(final Cookie[] cookies) { + if (cookies != null) { + for (final Cookie cooky : cookies) { + this.addCookie(cooky); + } + } + } + + /** + * Returns an immutable array of {@link Cookie cookies} that this HTTP + * state currently contains. + * + * @return an array of {@link Cookie cookies}. + */ + public synchronized List<Cookie> getCookies() { + //create defensive copy so it won't be concurrently modified + return new ArrayList<Cookie>(cookies); + } + + /** + * Removes all of {@link Cookie cookies} in this HTTP state + * that have expired by the specified {@link java.util.Date date}. + * + * @return true if any cookies were purged. + * + * @see Cookie#isExpired(Date) + */ + public synchronized boolean clearExpired(final Date date) { + if (date == null) { + return false; + } + boolean removed = false; + for (final Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (it.next().isExpired(date)) { + it.remove(); + removed = true; + } + } + return removed; + } + + /** + * Clears all cookies. + */ + public synchronized void clear() { + cookies.clear(); + } + + @Override + public synchronized String toString() { + return cookies.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java new file mode 100644 index 000000000..99c5cccc2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java @@ -0,0 +1,109 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link CredentialsProvider}. + * + * @since 4.0 + */ +@ThreadSafe +public class BasicCredentialsProvider implements CredentialsProvider { + + private final ConcurrentHashMap<AuthScope, Credentials> credMap; + + /** + * Default constructor. + */ + public BasicCredentialsProvider() { + super(); + this.credMap = new ConcurrentHashMap<AuthScope, Credentials>(); + } + + public void setCredentials( + final AuthScope authscope, + final Credentials credentials) { + Args.notNull(authscope, "Authentication scope"); + credMap.put(authscope, credentials); + } + + /** + * Find matching {@link Credentials credentials} for the given authentication scope. + * + * @param map the credentials hash map + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + */ + private static Credentials matchCredentials( + final Map<AuthScope, Credentials> map, + final AuthScope authscope) { + // see if we get a direct hit + Credentials creds = map.get(authscope); + if (creds == null) { + // Nope. + // Do a full scan + int bestMatchFactor = -1; + AuthScope bestMatch = null; + for (final AuthScope current: map.keySet()) { + final int factor = authscope.match(current); + if (factor > bestMatchFactor) { + bestMatchFactor = factor; + bestMatch = current; + } + } + if (bestMatch != null) { + creds = map.get(bestMatch); + } + } + return creds; + } + + public Credentials getCredentials(final AuthScope authscope) { + Args.notNull(authscope, "Authentication scope"); + return matchCredentials(this.credMap, authscope); + } + + public void clear() { + this.credMap.clear(); + } + + @Override + public String toString() { + return credMap.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java new file mode 100644 index 000000000..13ae4f856 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java @@ -0,0 +1,73 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.HttpResponseException; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * A {@link ResponseHandler} that returns the response body as a String + * for successful (2xx) responses. If the response code was >= 300, the response + * body is consumed and an {@link HttpResponseException} is thrown. + * <p/> + * If this is used with + * {@link ch.boye.httpclientandroidlib.client.HttpClient#execute( + * ch.boye.httpclientandroidlib.client.methods.HttpUriRequest, ResponseHandler)}, + * HttpClient may handle redirects (3xx responses) internally. + * + * @since 4.0 + */ +@Immutable +public class BasicResponseHandler implements ResponseHandler<String> { + + /** + * Returns the response body as a String if the response was successful (a + * 2xx status code). If no response body exists, this returns null. If the + * response was unsuccessful (>= 300 status code), throws an + * {@link HttpResponseException}. + */ + public String handleResponse(final HttpResponse response) + throws HttpResponseException, IOException { + final StatusLine statusLine = response.getStatusLine(); + final HttpEntity entity = response.getEntity(); + if (statusLine.getStatusCode() >= 300) { + EntityUtils.consume(entity); + throw new HttpResponseException(statusLine.getStatusCode(), + statusLine.getReasonPhrase()); + } + return entity == null ? null : EntityUtils.toString(entity); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java new file mode 100644 index 000000000..37f5daedd --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java @@ -0,0 +1,269 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.params.AbstractHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Represents a stack of parameter collections. + * When retrieving a parameter, the stack is searched in a fixed order + * and the first match returned. Setting parameters via the stack is + * not supported. To minimize overhead, the stack has a fixed size and + * does not maintain an internal array. + * The supported stack entries, sorted by increasing priority, are: + * <ol> + * <li>Application parameters: + * expected to be the same for all clients used by an application. + * These provide "global", that is application-wide, defaults. + * </li> + * <li>Client parameters: + * specific to an instance of + * {@link ch.boye.httpclientandroidlib.client.HttpClient HttpClient}. + * These provide client specific defaults. + * </li> + * <li>Request parameters: + * specific to a single request execution. + * For overriding client and global defaults. + * </li> + * <li>Override parameters: + * specific to an instance of + * {@link ch.boye.httpclientandroidlib.client.HttpClient HttpClient}. + * These can be used to set parameters that cannot be overridden + * on a per-request basis. + * </li> + * </ol> + * Each stack entry may be <code>null</code>. That is preferable over + * an empty params collection, since it avoids searching the empty collection + * when looking up parameters. + * + * @since 4.0 + * + * @deprecated (4.3) use configuration classes provided 'ch.boye.httpclientandroidlib.config' + * and 'ch.boye.httpclientandroidlib.client.config' + */ +@NotThreadSafe +@Deprecated +public class ClientParamsStack extends AbstractHttpParams { + + /** The application parameter collection, or <code>null</code>. */ + protected final HttpParams applicationParams; + + /** The client parameter collection, or <code>null</code>. */ + protected final HttpParams clientParams; + + /** The request parameter collection, or <code>null</code>. */ + protected final HttpParams requestParams; + + /** The override parameter collection, or <code>null</code>. */ + protected final HttpParams overrideParams; + + + /** + * Creates a new parameter stack from elements. + * The arguments will be stored as-is, there is no copying to + * prevent modification. + * + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(final HttpParams aparams, final HttpParams cparams, + final HttpParams rparams, final HttpParams oparams) { + applicationParams = aparams; + clientParams = cparams; + requestParams = rparams; + overrideParams = oparams; + } + + + /** + * Creates a copy of a parameter stack. + * The new stack will have the exact same entries as the argument stack. + * There is no copying of parameters. + * + * @param stack the stack to copy + */ + public ClientParamsStack(final ClientParamsStack stack) { + this(stack.getApplicationParams(), + stack.getClientParams(), + stack.getRequestParams(), + stack.getOverrideParams()); + } + + + /** + * Creates a modified copy of a parameter stack. + * The new stack will contain the explicitly passed elements. + * For elements where the explicit argument is <code>null</code>, + * the corresponding element from the argument stack is used. + * There is no copying of parameters. + * + * @param stack the stack to modify + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(final ClientParamsStack stack, + final HttpParams aparams, final HttpParams cparams, + final HttpParams rparams, final HttpParams oparams) { + this((aparams != null) ? aparams : stack.getApplicationParams(), + (cparams != null) ? cparams : stack.getClientParams(), + (rparams != null) ? rparams : stack.getRequestParams(), + (oparams != null) ? oparams : stack.getOverrideParams()); + } + + + /** + * Obtains the application parameters of this stack. + * + * @return the application parameters, or <code>null</code> + */ + public final HttpParams getApplicationParams() { + return applicationParams; + } + + /** + * Obtains the client parameters of this stack. + * + * @return the client parameters, or <code>null</code> + */ + public final HttpParams getClientParams() { + return clientParams; + } + + /** + * Obtains the request parameters of this stack. + * + * @return the request parameters, or <code>null</code> + */ + public final HttpParams getRequestParams() { + return requestParams; + } + + /** + * Obtains the override parameters of this stack. + * + * @return the override parameters, or <code>null</code> + */ + public final HttpParams getOverrideParams() { + return overrideParams; + } + + + /** + * Obtains a parameter from this stack. + * See class comment for search order. + * + * @param name the name of the parameter to obtain + * + * @return the highest-priority value for that parameter, or + * <code>null</code> if it is not set anywhere in this stack + */ + public Object getParameter(final String name) { + Args.notNull(name, "Parameter name"); + + Object result = null; + + if (overrideParams != null) { + result = overrideParams.getParameter(name); + } + if ((result == null) && (requestParams != null)) { + result = requestParams.getParameter(name); + } + if ((result == null) && (clientParams != null)) { + result = clientParams.getParameter(name); + } + if ((result == null) && (applicationParams != null)) { + result = applicationParams.getParameter(name); + } + return result; + } + + /** + * Does <i>not</i> set a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * @param value ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public HttpParams setParameter(final String name, final Object value) + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Setting parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> remove a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public boolean removeParameter(final String name) { + throw new UnsupportedOperationException + ("Removing parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> copy parameters. + * Parameter stacks are lightweight objects, expected to be instantiated + * as needed and to be used only in a very specific context. On top of + * that, they are read-only. The typical copy operation to prevent + * accidental modification of parameters passed by the application to + * a framework object is therefore pointless and disabled. + * Create a new stack if you really need a copy. + * <br/> + * Derived classes may change this behavior. + * + * @return <code>this</code> parameter stack + */ + public HttpParams copy() { + return this; + } + + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/Clock.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/Clock.java new file mode 100644 index 000000000..14eeec4a0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/Clock.java @@ -0,0 +1,43 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +/** + * Interface used to enable easier testing of time-related behavior. + * + * @since 4.2 + * + */ +interface Clock { + + /** + * Returns the current time, expressed as the number of + * milliseconds since the epoch. + * @return current time + */ + long getCurrentTime(); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java new file mode 100644 index 000000000..c2fc5a5de --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java @@ -0,0 +1,244 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URI; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * Base implementation of {@link HttpClient} that also implements {@link Closeable}. + * + * @since 4.3 + */ +@ThreadSafe +public abstract class CloseableHttpClient implements HttpClient, Closeable { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + protected abstract CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, + HttpContext context) throws IOException, ClientProtocolException; + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + return doExecute(target, request, context); + } + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpUriRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + Args.notNull(request, "HTTP request"); + return doExecute(determineTarget(request), request, context); + } + + private static HttpHost determineTarget(final HttpUriRequest request) throws ClientProtocolException { + // A null target may be acceptable if there is a default target. + // Otherwise, the null target is detected in the director. + HttpHost target = null; + + final URI requestURI = request.getURI(); + if (requestURI.isAbsolute()) { + target = URIUtils.extractHost(requestURI); + if (target == null) { + throw new ClientProtocolException("URI does not specify a valid host name: " + + requestURI); + } + } + return target; + } + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpUriRequest request) throws IOException, ClientProtocolException { + return execute(request, (HttpContext) null); + } + + /** + * {@inheritDoc} + */ + public CloseableHttpResponse execute( + final HttpHost target, + final HttpRequest request) throws IOException, ClientProtocolException { + return doExecute(target, request, (HttpContext) null); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(request, responseHandler, null); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param request the request to execute + * @param responseHandler the response handler + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + final HttpHost target = determineTarget(request); + return execute(target, request, responseHandler, context); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + /** + * Executes a request using the default context and processes the + * response using the given response handler. The content entity associated + * with the response is fully consumed and the underlying connection is + * released back to the connection manager automatically in all cases + * relieving individual {@link ResponseHandler}s from having to manage + * resource deallocation internally. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param responseHandler the response handler + * @param context the context to use for the execution, or + * <code>null</code> to use the default context + * + * @return the response object as generated by the response handler. + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + Args.notNull(responseHandler, "Response handler"); + + final HttpResponse response = execute(target, request, context); + + final T result; + try { + result = responseHandler.handleResponse(response); + } catch (final Exception t) { + final HttpEntity entity = response.getEntity(); + try { + EntityUtils.consume(entity); + } catch (final Exception t2) { + // Log this exception. The original exception is more + // important and will be thrown to the caller. + this.log.warn("Error consuming content after an exception.", t2); + } + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + if (t instanceof IOException) { + throw (IOException) t; + } + throw new UndeclaredThrowableException(t); + } + + // Handling the response was successful. Ensure that the content has + // been fully consumed. + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + return result; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java new file mode 100644 index 000000000..9869740df --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java @@ -0,0 +1,87 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * @since 4.3 + */ +@NotThreadSafe +class CloseableHttpResponseProxy implements InvocationHandler { + + private final HttpResponse original; + + CloseableHttpResponseProxy(final HttpResponse original) { + super(); + this.original = original; + } + + public void close() throws IOException { + final HttpEntity entity = this.original.getEntity(); + EntityUtils.consume(entity); + } + + public Object invoke( + final Object proxy, final Method method, final Object[] args) throws Throwable { + final String mname = method.getName(); + if (mname.equals("close")) { + close(); + return null; + } else { + try { + return method.invoke(original, args); + } catch (final InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause != null) { + throw cause; + } else { + throw ex; + } + } + } + } + + public static CloseableHttpResponse newProxy(final HttpResponse original) { + return (CloseableHttpResponse) Proxy.newProxyInstance( + CloseableHttpResponseProxy.class.getClassLoader(), + new Class<?>[] { CloseableHttpResponse.class }, + new CloseableHttpResponseProxy(original)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java new file mode 100644 index 000000000..f4169225e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java @@ -0,0 +1,93 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.protocol.RequestAcceptEncoding; +import ch.boye.httpclientandroidlib.client.protocol.ResponseContentEncoding; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpProcessor; + +/** + * {@link DefaultHttpClient} sub-class which includes a {@link RequestAcceptEncoding} + * for the request and response. + * + * <b>Deprecation note:</b> due to the way this class modifies a response body + * without changing the response headers to reflect the entity changes, it cannot + * be used as the "backend" for a caching {@link + * ch.boye.httpclientandroidlib.client.HttpClient} and still have uncompressed responses be cached. + * Users are encouraged to use the {@link DecompressingHttpClient} instead + * of this class, which can be wired in either before or after caching, depending on + * whether you want to cache responses in compressed or uncompressed form. + * + * @since 4.1 + * + * @deprecated (4.2) use {@link HttpClientBuilder} + */ +@Deprecated +@ThreadSafe // since DefaultHttpClient is +public class ContentEncodingHttpClient extends DefaultHttpClient { + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public ContentEncodingHttpClient(final ClientConnectionManager conman, final HttpParams params) { + super(conman, params); + } + + /** + * @param params + */ + public ContentEncodingHttpClient(final HttpParams params) { + this(null, params); + } + + /** + * + */ + public ContentEncodingHttpClient() { + this(null); + } + + /** + * {@inheritDoc} + */ + @Override + protected BasicHttpProcessor createHttpProcessor() { + final BasicHttpProcessor result = super.createHttpProcessor(); + + result.addRequestInterceptor(new RequestAcceptEncoding()); + result.addResponseInterceptor(new ResponseContentEncoding()); + + return result; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java new file mode 100644 index 000000000..4d55303ad --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java @@ -0,0 +1,214 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.net.URI; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestInterceptor; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseInterceptor; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.protocol.RequestAcceptEncoding; +import ch.boye.httpclientandroidlib.client.protocol.ResponseContentEncoding; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * <p>Decorator adding support for compressed responses. This class sets + * the <code>Accept-Encoding</code> header on requests to indicate + * support for the <code>gzip</code> and <code>deflate</code> + * compression schemes; it then checks the <code>Content-Encoding</code> + * header on the response to uncompress any compressed response bodies. + * The {@link java.io.InputStream} of the entity will contain the uncompressed + * content.</p> + * + * <p><b>N.B.</b> Any upstream clients of this class need to be aware that + * this effectively obscures visibility into the length of a server + * response body, since the <code>Content-Length</code> header will + * correspond to the compressed entity length received from the server, + * but the content length experienced by reading the response body may + * be different (hopefully higher!).</p> + * + * <p>That said, this decorator is compatible with the + * <code>CachingHttpClient</code> in that the two decorators can be added + * in either order and still have cacheable responses be cached.</p> + * + * @since 4.2 + * + * @deprecated (4.3) use {@link HttpClientBuilder} + */ +@Deprecated +public class DecompressingHttpClient implements HttpClient { + + private final HttpClient backend; + private final HttpRequestInterceptor acceptEncodingInterceptor; + private final HttpResponseInterceptor contentEncodingInterceptor; + + /** + * Constructs a decorator to ask for and handle compressed + * entities on the fly. + */ + public DecompressingHttpClient() { + this(new DefaultHttpClient()); + } + + /** + * Constructs a decorator to ask for and handle compressed + * entities on the fly. + * @param backend the {@link HttpClient} to use for actually + * issuing requests + */ + public DecompressingHttpClient(final HttpClient backend) { + this(backend, new RequestAcceptEncoding(), new ResponseContentEncoding()); + } + + DecompressingHttpClient(final HttpClient backend, + final HttpRequestInterceptor requestInterceptor, + final HttpResponseInterceptor responseInterceptor) { + this.backend = backend; + this.acceptEncodingInterceptor = requestInterceptor; + this.contentEncodingInterceptor = responseInterceptor; + } + + public HttpParams getParams() { + return backend.getParams(); + } + + public ClientConnectionManager getConnectionManager() { + return backend.getConnectionManager(); + } + + public HttpResponse execute(final HttpUriRequest request) throws IOException, + ClientProtocolException { + return execute(getHttpHost(request), request, (HttpContext)null); + } + + /** + * Gets the HttpClient to issue request. + * + * @return the HttpClient to issue request + */ + public HttpClient getHttpClient() { + return this.backend; + } + + HttpHost getHttpHost(final HttpUriRequest request) { + final URI uri = request.getURI(); + return URIUtils.extractHost(uri); + } + + public HttpResponse execute(final HttpUriRequest request, final HttpContext context) + throws IOException, ClientProtocolException { + return execute(getHttpHost(request), request, context); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request) + throws IOException, ClientProtocolException { + return execute(target, request, (HttpContext)null); + } + + public HttpResponse execute(final HttpHost target, final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + try { + final HttpContext localContext = context != null ? context : new BasicHttpContext(); + final HttpRequest wrapped; + if (request instanceof HttpEntityEnclosingRequest) { + wrapped = new EntityEnclosingRequestWrapper((HttpEntityEnclosingRequest) request); + } else { + wrapped = new RequestWrapper(request); + } + acceptEncodingInterceptor.process(wrapped, localContext); + final HttpResponse response = backend.execute(target, wrapped, localContext); + try { + contentEncodingInterceptor.process(response, localContext); + if (Boolean.TRUE.equals(localContext.getAttribute(ResponseContentEncoding.UNCOMPRESSED))) { + response.removeHeaders("Content-Length"); + response.removeHeaders("Content-Encoding"); + response.removeHeaders("Content-MD5"); + } + return response; + } catch (final HttpException ex) { + EntityUtils.consume(response.getEntity()); + throw ex; + } catch (final IOException ex) { + EntityUtils.consume(response.getEntity()); + throw ex; + } catch (final RuntimeException ex) { + EntityUtils.consume(response.getEntity()); + throw ex; + } + } catch (final HttpException e) { + throw new ClientProtocolException(e); + } + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(getHttpHost(request), request, responseHandler); + } + + public <T> T execute(final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + return execute(getHttpHost(request), request, responseHandler, context); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) throws IOException, + ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + public <T> T execute(final HttpHost target, final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, final HttpContext context) + throws IOException, ClientProtocolException { + final HttpResponse response = execute(target, request, context); + try { + return responseHandler.handleResponse(response); + } finally { + final HttpEntity entity = response.getEntity(); + if (entity != null) { + EntityUtils.consume(entity); + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java new file mode 100644 index 000000000..5a5e67caa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java @@ -0,0 +1,54 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.ConnectException; +import java.net.SocketTimeoutException; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; + +/** + * This {@link ConnectionBackoffStrategy} backs off either for a raw + * network socket or connection timeout or if the server explicitly + * sends a 503 (Service Unavailable) response. + * + * @since 4.2 + */ +public class DefaultBackoffStrategy implements ConnectionBackoffStrategy { + + public boolean shouldBackoff(final Throwable t) { + return (t instanceof SocketTimeoutException + || t instanceof ConnectException); + } + + public boolean shouldBackoff(final HttpResponse resp) { + return (resp.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java new file mode 100644 index 000000000..a5524bd43 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HeaderElementIterator; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.message.BasicHeaderElementIterator; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of a strategy deciding duration + * that a connection can remain idle. + * + * The default implementation looks solely at the 'Keep-Alive' + * header's timeout token. + * + * @since 4.0 + */ +@Immutable +public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { + + public static final DefaultConnectionKeepAliveStrategy INSTANCE = new DefaultConnectionKeepAliveStrategy(); + + public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) { + Args.notNull(response, "HTTP response"); + final HeaderElementIterator it = new BasicHeaderElementIterator( + response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + final HeaderElement he = it.nextElement(); + final String param = he.getName(); + final String value = he.getValue(); + if (value != null && param.equalsIgnoreCase("timeout")) { + try { + return Long.parseLong(value) * 1000; + } catch(final NumberFormatException ignore) { + } + } + } + return -1; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java new file mode 100644 index 000000000..55e5ab07e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java @@ -0,0 +1,225 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.protocol.RequestAddCookies; +import ch.boye.httpclientandroidlib.client.protocol.RequestAuthCache; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.client.protocol.RequestDefaultHeaders; +import ch.boye.httpclientandroidlib.client.protocol.RequestProxyAuthentication; +import ch.boye.httpclientandroidlib.client.protocol.RequestTargetAuthentication; +import ch.boye.httpclientandroidlib.client.protocol.ResponseProcessCookies; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.params.HttpConnectionParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.params.SyncBasicHttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.RequestContent; +import ch.boye.httpclientandroidlib.protocol.RequestExpectContinue; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; + +/** + * Default implementation of {@link ch.boye.httpclientandroidlib.client.HttpClient} pre-configured + * for most common use scenarios. + * <p> + * Please see the Javadoc for {@link #createHttpProcessor()} for the details of the interceptors + * that are set up by default. + * <p> + * Additional interceptors can be added as follows, but + * take care not to add the same interceptor more than once. + * <pre> + * DefaultHttpClient httpclient = new DefaultHttpClient(); + * httpclient.addRequestInterceptor(new RequestAcceptEncoding()); + * httpclient.addResponseInterceptor(new ResponseContentEncoding()); + * </pre> + * <p> + * This class sets up the following parameters if not explicitly set: + * <ul> + * <li>Version: HttpVersion.HTTP_1_1</li> + * <li>ContentCharset: HTTP.DEFAULT_CONTENT_CHARSET</li> + * <li>NoTcpDelay: true</li> + * <li>SocketBufferSize: 8192</li> + * <li>UserAgent: Apache-HttpClient/release (java 1.5)</li> + * </ul> + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link HttpClientBuilder}. + */ +@ThreadSafe +@Deprecated +public class DefaultHttpClient extends AbstractHttpClient { + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public DefaultHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(conman, params); + } + + + /** + * @since 4.1 + */ + public DefaultHttpClient( + final ClientConnectionManager conman) { + super(conman, null); + } + + + public DefaultHttpClient(final HttpParams params) { + super(null, params); + } + + + public DefaultHttpClient() { + super(null, null); + } + + + /** + * Creates the default set of HttpParams by invoking {@link DefaultHttpClient#setDefaultHttpParams(HttpParams)} + * + * @return a new instance of {@link SyncBasicHttpParams} with the defaults applied to it. + */ + @Override + protected HttpParams createHttpParams() { + final HttpParams params = new SyncBasicHttpParams(); + setDefaultHttpParams(params); + return params; + } + + /** + * Saves the default set of HttpParams in the provided parameter. + * These are: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}: + * 1.1</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_CONTENT_CHARSET}: + * ISO-8859-1</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}: + * true</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}: + * 8192</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}: + * Apache-HttpClient/<release> (java 1.5)</li> + * </ul> + */ + public static void setDefaultHttpParams(final HttpParams params) { + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, HTTP.DEF_CONTENT_CHARSET.name()); + HttpConnectionParams.setTcpNoDelay(params, true); + HttpConnectionParams.setSocketBufferSize(params, 8192); + HttpProtocolParams.setUserAgent(params, HttpClientBuilder.DEFAULT_USER_AGENT); + } + + /** + * Create the processor with the following interceptors: + * <ul> + * <li>{@link RequestDefaultHeaders}</li> + * <li>{@link RequestContent}</li> + * <li>{@link RequestTargetHost}</li> + * <li>{@link RequestClientConnControl}</li> + * <li>{@link RequestUserAgent}</li> + * <li>{@link RequestExpectContinue}</li> + * <li>{@link RequestAddCookies}</li> + * <li>{@link ResponseProcessCookies}</li> + * <li>{@link RequestAuthCache}</li> + * <li>{@link RequestTargetAuthentication}</li> + * <li>{@link RequestProxyAuthentication}</li> + * </ul> + * <p> + * @return the processor with the added interceptors. + */ + @Override + protected BasicHttpProcessor createHttpProcessor() { + final BasicHttpProcessor httpproc = new BasicHttpProcessor(); + httpproc.addInterceptor(new RequestDefaultHeaders()); + // Required protocol interceptors + httpproc.addInterceptor(new RequestContent()); + httpproc.addInterceptor(new RequestTargetHost()); + // Recommended protocol interceptors + httpproc.addInterceptor(new RequestClientConnControl()); + httpproc.addInterceptor(new RequestUserAgent()); + httpproc.addInterceptor(new RequestExpectContinue()); + // HTTP state management interceptors + httpproc.addInterceptor(new RequestAddCookies()); + httpproc.addInterceptor(new ResponseProcessCookies()); + // HTTP authentication interceptors + httpproc.addInterceptor(new RequestAuthCache()); + httpproc.addInterceptor(new RequestTargetAuthentication()); + httpproc.addInterceptor(new RequestProxyAuthentication()); + return httpproc; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java new file mode 100644 index 000000000..1721c8e70 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java @@ -0,0 +1,203 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ConnectException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.net.ssl.SSLException; + +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * The default {@link HttpRequestRetryHandler} used by request executors. + * + * @since 4.0 + */ +@Immutable +public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler { + + public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler(); + + /** the number of times a method will be retried */ + private final int retryCount; + + /** Whether or not methods that have successfully sent their request will be retried */ + private final boolean requestSentRetryEnabled; + + private final Set<Class<? extends IOException>> nonRetriableClasses; + + /** + * Create the request retry handler using the specified IOException classes + * + * @param retryCount how many times to retry; 0 means no retries + * @param requestSentRetryEnabled true if it's OK to retry requests that have been sent + * @param clazzes the IOException types that should not be retried + * @since 4.3 + */ + protected DefaultHttpRequestRetryHandler( + final int retryCount, + final boolean requestSentRetryEnabled, + final Collection<Class<? extends IOException>> clazzes) { + super(); + this.retryCount = retryCount; + this.requestSentRetryEnabled = requestSentRetryEnabled; + this.nonRetriableClasses = new HashSet<Class<? extends IOException>>(); + for (final Class<? extends IOException> clazz: clazzes) { + this.nonRetriableClasses.add(clazz); + } + } + + /** + * Create the request retry handler using the following list of + * non-retriable IOException classes: <br> + * <ul> + * <li>InterruptedIOException</li> + * <li>UnknownHostException</li> + * <li>ConnectException</li> + * <li>SSLException</li> + * </ul> + * @param retryCount how many times to retry; 0 means no retries + * @param requestSentRetryEnabled true if it's OK to retry requests that have been sent + */ + @SuppressWarnings("unchecked") + public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { + this(retryCount, requestSentRetryEnabled, Arrays.asList( + InterruptedIOException.class, + UnknownHostException.class, + ConnectException.class, + SSLException.class)); + } + + /** + * Create the request retry handler with a retry count of 3, requestSentRetryEnabled false + * and using the following list of non-retriable IOException classes: <br> + * <ul> + * <li>InterruptedIOException</li> + * <li>UnknownHostException</li> + * <li>ConnectException</li> + * <li>SSLException</li> + * </ul> + */ + public DefaultHttpRequestRetryHandler() { + this(3, false); + } + /** + * Used <code>retryCount</code> and <code>requestSentRetryEnabled</code> to determine + * if the given method should be retried. + */ + public boolean retryRequest( + final IOException exception, + final int executionCount, + final HttpContext context) { + Args.notNull(exception, "Exception parameter"); + Args.notNull(context, "HTTP context"); + if (executionCount > this.retryCount) { + // Do not retry if over max retry count + return false; + } + if (this.nonRetriableClasses.contains(exception.getClass())) { + return false; + } else { + for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) { + if (rejectException.isInstance(exception)) { + return false; + } + } + } + final HttpClientContext clientContext = HttpClientContext.adapt(context); + final HttpRequest request = clientContext.getRequest(); + + if(requestIsAborted(request)){ + return false; + } + + if (handleAsIdempotent(request)) { + // Retry if the request is considered idempotent + return true; + } + + if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) { + // Retry if the request has not been sent fully or + // if it's OK to retry methods that have been sent + return true; + } + // otherwise do not retry + return false; + } + + /** + * @return <code>true</code> if this handler will retry methods that have + * successfully sent their request, <code>false</code> otherwise + */ + public boolean isRequestSentRetryEnabled() { + return requestSentRetryEnabled; + } + + /** + * @return the maximum number of times a method will be retried + */ + public int getRetryCount() { + return retryCount; + } + + /** + * @since 4.2 + */ + protected boolean handleAsIdempotent(final HttpRequest request) { + return !(request instanceof HttpEntityEnclosingRequest); + } + + /** + * @since 4.2 + * + * @deprecated (4.3) + */ + @Deprecated + protected boolean requestIsAborted(final HttpRequest request) { + HttpRequest req = request; + if (request instanceof RequestWrapper) { // does not forward request to original + req = ((RequestWrapper) request).getOriginal(); + } + return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted()); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java new file mode 100644 index 000000000..a70057ae5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java @@ -0,0 +1,90 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.List; +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.auth.params.AuthPNames; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationHandler} implementation + * for proxy host authentication. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ProxyAuthenticationStrategy} + */ +@Deprecated +@Immutable +public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultProxyAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + final int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(response, "HTTP response"); + final Header[] headers = response.getHeaders(AUTH.PROXY_AUTH); + return parseChallenges(headers); + } + + @Override + protected List<String> getAuthPreferences( + final HttpResponse response, + final HttpContext context) { + @SuppressWarnings("unchecked") + final + List<String> authpref = (List<String>) response.getParams().getParameter( + AuthPNames.PROXY_AUTH_PREF); + if (authpref != null) { + return authpref; + } else { + return super.getAuthPreferences(response, context); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java new file mode 100644 index 000000000..6bd58c035 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java @@ -0,0 +1,180 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.CircularRedirectException; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.ExecutionContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Default implementation of {@link RedirectHandler}. + * + * @since 4.0 + * + * @deprecated (4.1) use {@link DefaultRedirectStrategy}. + */ +@Immutable +@Deprecated +public class DefaultRedirectHandler implements RedirectHandler { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public DefaultRedirectHandler() { + super(); + } + + public boolean isRedirectRequested( + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + + final int statusCode = response.getStatusLine().getStatusCode(); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_TEMPORARY_REDIRECT: + final HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + final String method = request.getRequestLine().getMethod(); + return method.equalsIgnoreCase(HttpGet.METHOD_NAME) + || method.equalsIgnoreCase(HttpHead.METHOD_NAME); + case HttpStatus.SC_SEE_OTHER: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpResponse response, + final HttpContext context) throws ProtocolException { + Args.notNull(response, "HTTP response"); + //get the location header to find out where to redirect to + final Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + final String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + URI uri; + try { + uri = new URI(location); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + + final HttpParams params = response.getParams(); + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + if (!uri.isAbsolute()) { + if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + final HttpHost target = (HttpHost) context.getAttribute( + ExecutionContext.HTTP_TARGET_HOST); + Asserts.notNull(target, "Target host"); + + final HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + + try { + final URI requestURI = new URI(request.getRequestLine().getUri()); + final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, true); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } + + if (params.isParameterFalse(ClientPNames.ALLOW_CIRCULAR_REDIRECTS)) { + + RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute( + REDIRECT_LOCATIONS); + + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(REDIRECT_LOCATIONS, redirectLocations); + } + + final URI redirectURI; + if (uri.getFragment() != null) { + try { + final HttpHost target = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + redirectURI = URIUtils.rewriteURI(uri, target, true); + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } else { + redirectURI = uri; + } + + if (redirectLocations.contains(redirectURI)) { + throw new CircularRedirectException("Circular redirect to '" + + redirectURI + "'"); + } else { + redirectLocations.add(redirectURI); + } + } + + return uri; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java new file mode 100644 index 000000000..e7d9315d2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java @@ -0,0 +1,233 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.CircularRedirectException; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.methods.RequestBuilder; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.utils.URIBuilder; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.TextUtils; + +/** + * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions + * on automatic redirection of entity enclosing methods such as POST and PUT imposed by the + * HTTP specification. <tt>302 Moved Temporarily</tt>, <tt>301 Moved Permanently</tt> and + * <tt>307 Temporary Redirect</tt> status codes will result in an automatic redirect of + * HEAD and GET methods only. POST and PUT methods will not be automatically redirected + * as requiring user confirmation. + * <p/> + * The restriction on automatic redirection of POST methods can be relaxed by using + * {@link LaxRedirectStrategy} instead of {@link DefaultRedirectStrategy}. + * + * @see LaxRedirectStrategy + * @since 4.1 + */ +@Immutable +public class DefaultRedirectStrategy implements RedirectStrategy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * @deprecated (4.3) use {@link ch.boye.httpclientandroidlib.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}. + */ + @Deprecated + public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy(); + + /** + * Redirectable methods. + */ + private static final String[] REDIRECT_METHODS = new String[] { + HttpGet.METHOD_NAME, + HttpHead.METHOD_NAME + }; + + public DefaultRedirectStrategy() { + super(); + } + + public boolean isRedirected( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + Args.notNull(request, "HTTP request"); + Args.notNull(response, "HTTP response"); + + final int statusCode = response.getStatusLine().getStatusCode(); + final String method = request.getRequestLine().getMethod(); + final Header locationHeader = response.getFirstHeader("location"); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + return isRedirectable(method) && locationHeader != null; + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_TEMPORARY_REDIRECT: + return isRedirectable(method); + case HttpStatus.SC_SEE_OTHER: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + Args.notNull(request, "HTTP request"); + Args.notNull(response, "HTTP response"); + Args.notNull(context, "HTTP context"); + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + //get the location header to find out where to redirect to + final Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + final String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + final RequestConfig config = clientContext.getRequestConfig(); + + URI uri = createLocationURI(location); + + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + try { + if (!uri.isAbsolute()) { + if (!config.isRelativeRedirectsAllowed()) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + final HttpHost target = clientContext.getTargetHost(); + Asserts.notNull(target, "Target host"); + final URI requestURI = new URI(request.getRequestLine().getUri()); + final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, false); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + + RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute( + HttpClientContext.REDIRECT_LOCATIONS); + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations); + } + if (!config.isCircularRedirectsAllowed()) { + if (redirectLocations.contains(uri)) { + throw new CircularRedirectException("Circular redirect to '" + uri + "'"); + } + } + redirectLocations.add(uri); + return uri; + } + + /** + * @since 4.1 + */ + protected URI createLocationURI(final String location) throws ProtocolException { + try { + final URIBuilder b = new URIBuilder(new URI(location).normalize()); + final String host = b.getHost(); + if (host != null) { + b.setHost(host.toLowerCase(Locale.ENGLISH)); + } + final String path = b.getPath(); + if (TextUtils.isEmpty(path)) { + b.setPath("/"); + } + return b.build(); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + } + + /** + * @since 4.2 + */ + protected boolean isRedirectable(final String method) { + for (final String m: REDIRECT_METHODS) { + if (m.equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + + public HttpUriRequest getRedirect( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + final URI uri = getLocationURI(request, response, context); + final String method = request.getRequestLine().getMethod(); + if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { + return new HttpHead(uri); + } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) { + return new HttpGet(uri); + } else { + final int status = response.getStatusLine().getStatusCode(); + if (status == HttpStatus.SC_TEMPORARY_REDIRECT) { + return RequestBuilder.copy(request).setUri(uri).build(); + } else { + return new HttpGet(uri); + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java new file mode 100644 index 000000000..b49fb7f73 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java @@ -0,0 +1,81 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * @deprecated (4.1) do not use + */ +@Immutable +@Deprecated +class DefaultRedirectStrategyAdaptor implements RedirectStrategy { + + private final RedirectHandler handler; + + public DefaultRedirectStrategyAdaptor(final RedirectHandler handler) { + super(); + this.handler = handler; + } + + public boolean isRedirected( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + return this.handler.isRedirectRequested(response, context); + } + + public HttpUriRequest getRedirect( + final HttpRequest request, + final HttpResponse response, + final HttpContext context) throws ProtocolException { + final URI uri = this.handler.getLocationURI(response, context); + final String method = request.getRequestLine().getMethod(); + if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { + return new HttpHead(uri); + } else { + return new HttpGet(uri); + } + } + + public RedirectHandler getHandler() { + return this.handler; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java new file mode 100644 index 000000000..03d9e1e5f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java @@ -0,0 +1,1150 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthProtocolState; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials; +import ch.boye.httpclientandroidlib.client.AuthenticationHandler; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException; +import ch.boye.httpclientandroidlib.client.RedirectException; +import ch.boye.httpclientandroidlib.client.RedirectHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.RequestDirector; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.methods.AbortableHttpRequest; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.params.HttpClientParams; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.BasicManagedEntity; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; +import ch.boye.httpclientandroidlib.impl.auth.BasicScheme; +import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException; +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; +import ch.boye.httpclientandroidlib.params.HttpConnectionParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.protocol.ExecutionContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * Default implementation of {@link RequestDirector}. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) + */ +@Deprecated +@NotThreadSafe // e.g. managedConn +public class DefaultRequestDirector implements RequestDirector { + + public HttpClientAndroidLog log; + + /** The connection manager. */ + protected final ClientConnectionManager connManager; + + /** The route planner. */ + protected final HttpRoutePlanner routePlanner; + + /** The connection re-use strategy. */ + protected final ConnectionReuseStrategy reuseStrategy; + + /** The keep-alive duration strategy. */ + protected final ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The request executor. */ + protected final HttpRequestExecutor requestExec; + + /** The HTTP protocol processor. */ + protected final HttpProcessor httpProcessor; + + /** The request retry handler. */ + protected final HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + @Deprecated + protected final RedirectHandler redirectHandler; + + /** The redirect strategy. */ + protected final RedirectStrategy redirectStrategy; + + /** The target authentication handler. */ + @Deprecated + protected final AuthenticationHandler targetAuthHandler; + + /** The target authentication handler. */ + protected final AuthenticationStrategy targetAuthStrategy; + + /** The proxy authentication handler. */ + @Deprecated + protected final AuthenticationHandler proxyAuthHandler; + + /** The proxy authentication handler. */ + protected final AuthenticationStrategy proxyAuthStrategy; + + /** The user token handler. */ + protected final UserTokenHandler userTokenHandler; + + /** The HTTP parameters. */ + protected final HttpParams params; + + /** The currently allocated connection. */ + protected ManagedClientConnection managedConn; + + protected final AuthState targetAuthState; + + protected final AuthState proxyAuthState; + + private final HttpAuthenticator authenticator; + + private int execCount; + + private int redirectCount; + + private final int maxRedirects; + + private HttpHost virtualHost; + + @Deprecated + public DefaultRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + this(new HttpClientAndroidLog(DefaultRequestDirector.class), + requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, + new DefaultRedirectStrategyAdaptor(redirectHandler), + new AuthenticationStrategyAdaptor(targetAuthHandler), + new AuthenticationStrategyAdaptor(proxyAuthHandler), + userTokenHandler, + params); + } + + + @Deprecated + public DefaultRequestDirector( + final HttpClientAndroidLog log, + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + this(new HttpClientAndroidLog(DefaultRequestDirector.class), + requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, + redirectStrategy, + new AuthenticationStrategyAdaptor(targetAuthHandler), + new AuthenticationStrategyAdaptor(proxyAuthHandler), + userTokenHandler, + params); + } + + /** + * @since 4.2 + */ + public DefaultRequestDirector( + final HttpClientAndroidLog log, + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectStrategy redirectStrategy, + final AuthenticationStrategy targetAuthStrategy, + final AuthenticationStrategy proxyAuthStrategy, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + + Args.notNull(log, "Log"); + Args.notNull(requestExec, "Request executor"); + Args.notNull(conman, "Client connection manager"); + Args.notNull(reustrat, "Connection reuse strategy"); + Args.notNull(kastrat, "Connection keep alive strategy"); + Args.notNull(rouplan, "Route planner"); + Args.notNull(httpProcessor, "HTTP protocol processor"); + Args.notNull(retryHandler, "HTTP request retry handler"); + Args.notNull(redirectStrategy, "Redirect strategy"); + Args.notNull(targetAuthStrategy, "Target authentication strategy"); + Args.notNull(proxyAuthStrategy, "Proxy authentication strategy"); + Args.notNull(userTokenHandler, "User token handler"); + Args.notNull(params, "HTTP parameters"); + this.log = log; + this.authenticator = new HttpAuthenticator(log); + this.requestExec = requestExec; + this.connManager = conman; + this.reuseStrategy = reustrat; + this.keepAliveStrategy = kastrat; + this.routePlanner = rouplan; + this.httpProcessor = httpProcessor; + this.retryHandler = retryHandler; + this.redirectStrategy = redirectStrategy; + this.targetAuthStrategy = targetAuthStrategy; + this.proxyAuthStrategy = proxyAuthStrategy; + this.userTokenHandler = userTokenHandler; + this.params = params; + + if (redirectStrategy instanceof DefaultRedirectStrategyAdaptor) { + this.redirectHandler = ((DefaultRedirectStrategyAdaptor) redirectStrategy).getHandler(); + } else { + this.redirectHandler = null; + } + if (targetAuthStrategy instanceof AuthenticationStrategyAdaptor) { + this.targetAuthHandler = ((AuthenticationStrategyAdaptor) targetAuthStrategy).getHandler(); + } else { + this.targetAuthHandler = null; + } + if (proxyAuthStrategy instanceof AuthenticationStrategyAdaptor) { + this.proxyAuthHandler = ((AuthenticationStrategyAdaptor) proxyAuthStrategy).getHandler(); + } else { + this.proxyAuthHandler = null; + } + + this.managedConn = null; + + this.execCount = 0; + this.redirectCount = 0; + this.targetAuthState = new AuthState(); + this.proxyAuthState = new AuthState(); + this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100); + } + + + private RequestWrapper wrapRequest( + final HttpRequest request) throws ProtocolException { + if (request instanceof HttpEntityEnclosingRequest) { + return new EntityEnclosingRequestWrapper( + (HttpEntityEnclosingRequest) request); + } else { + return new RequestWrapper( + request); + } + } + + + protected void rewriteRequestURI( + final RequestWrapper request, + final HttpRoute route) throws ProtocolException { + try { + + URI uri = request.getURI(); + if (route.getProxyHost() != null && !route.isTunnelled()) { + // Make sure the request URI is absolute + if (!uri.isAbsolute()) { + final HttpHost target = route.getTargetHost(); + uri = URIUtils.rewriteURI(uri, target, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + } else { + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + } + request.setURI(uri); + + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid URI: " + + request.getRequestLine().getUri(), ex); + } + } + + + // non-javadoc, see interface ClientRequestDirector + public HttpResponse execute(final HttpHost targetHost, final HttpRequest request, + final HttpContext context) + throws HttpException, IOException { + + context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState); + context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState); + + HttpHost target = targetHost; + + final HttpRequest orig = request; + final RequestWrapper origWrapper = wrapRequest(orig); + origWrapper.setParams(params); + final HttpRoute origRoute = determineRoute(target, origWrapper, context); + + virtualHost = (HttpHost) origWrapper.getParams().getParameter(ClientPNames.VIRTUAL_HOST); + + // HTTPCLIENT-1092 - add the port if necessary + if (virtualHost != null && virtualHost.getPort() == -1) { + final HttpHost host = (target != null) ? target : origRoute.getTargetHost(); + final int port = host.getPort(); + if (port != -1){ + virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName()); + } + } + + RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute); + + boolean reuse = false; + boolean done = false; + try { + HttpResponse response = null; + while (!done) { + // In this loop, the RoutedRequest may be replaced by a + // followup request and route. The request and route passed + // in the method arguments will be replaced. The original + // request is still available in 'orig'. + + final RequestWrapper wrapper = roureq.getRequest(); + final HttpRoute route = roureq.getRoute(); + response = null; + + // See if we have a user token bound to the execution context + Object userToken = context.getAttribute(ClientContext.USER_TOKEN); + + // Allocate connection if needed + if (managedConn == null) { + final ClientConnectionRequest connRequest = connManager.requestConnection( + route, userToken); + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setConnectionRequest(connRequest); + } + + final long timeout = HttpClientParams.getConnectionManagerTimeout(params); + try { + managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS); + } catch(final InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + + if (HttpConnectionParams.isStaleCheckingEnabled(params)) { + // validate connection + if (managedConn.isOpen()) { + this.log.debug("Stale connection check"); + if (managedConn.isStale()) { + this.log.debug("Stale connection detected"); + managedConn.close(); + } + } + } + } + + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn); + } + + try { + tryConnect(roureq, context); + } catch (final TunnelRefusedException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage()); + } + response = ex.getResponse(); + break; + } + + final String userinfo = wrapper.getURI().getUserInfo(); + if (userinfo != null) { + targetAuthState.update( + new BasicScheme(), new UsernamePasswordCredentials(userinfo)); + } + + // Get target. Even if there's virtual host, we may need the target to set the port. + if (virtualHost != null) { + target = virtualHost; + } else { + final URI requestURI = wrapper.getURI(); + if (requestURI.isAbsolute()) { + target = URIUtils.extractHost(requestURI); + } + } + if (target == null) { + target = route.getTargetHost(); + } + + // Reset headers on the request wrapper + wrapper.resetHeaders(); + // Re-write request URI if needed + rewriteRequestURI(wrapper, route); + + // Populate the execution context + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target); + context.setAttribute(ClientContext.ROUTE, route); + context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn); + + // Run request protocol interceptors + requestExec.preProcess(wrapper, httpProcessor, context); + + response = tryExecute(roureq, context); + if (response == null) { + // Need to start over + continue; + } + + // Run response protocol interceptors + response.setParams(params); + requestExec.postProcess(response, httpProcessor, context); + + + // The connection is in or can be brought to a re-usable state. + reuse = reuseStrategy.keepAlive(response, context); + if (reuse) { + // Set the idle duration of this connection + final long duration = keepAliveStrategy.getKeepAliveDuration(response, context); + if (this.log.isDebugEnabled()) { + final String s; + if (duration > 0) { + s = "for " + duration + " " + TimeUnit.MILLISECONDS; + } else { + s = "indefinitely"; + } + this.log.debug("Connection can be kept alive " + s); + } + managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); + } + + final RoutedRequest followup = handleResponse(roureq, response, context); + if (followup == null) { + done = true; + } else { + if (reuse) { + // Make sure the response body is fully consumed, if present + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + // entity consumed above is not an auto-release entity, + // need to mark the connection re-usable explicitly + managedConn.markReusable(); + } else { + managedConn.close(); + if (proxyAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0 + && proxyAuthState.getAuthScheme() != null + && proxyAuthState.getAuthScheme().isConnectionBased()) { + this.log.debug("Resetting proxy auth state"); + proxyAuthState.reset(); + } + if (targetAuthState.getState().compareTo(AuthProtocolState.CHALLENGED) > 0 + && targetAuthState.getAuthScheme() != null + && targetAuthState.getAuthScheme().isConnectionBased()) { + this.log.debug("Resetting target auth state"); + targetAuthState.reset(); + } + } + // check if we can use the same connection for the followup + if (!followup.getRoute().equals(roureq.getRoute())) { + releaseConnection(); + } + roureq = followup; + } + + if (managedConn != null) { + if (userToken == null) { + userToken = userTokenHandler.getUserToken(context); + context.setAttribute(ClientContext.USER_TOKEN, userToken); + } + if (userToken != null) { + managedConn.setState(userToken); + } + } + + } // while not done + + + // check for entity, release connection if possible + if ((response == null) || (response.getEntity() == null) || + !response.getEntity().isStreaming()) { + // connection not needed and (assumed to be) in re-usable state + if (reuse) { + managedConn.markReusable(); + } + releaseConnection(); + } else { + // install an auto-release entity + HttpEntity entity = response.getEntity(); + entity = new BasicManagedEntity(entity, managedConn, reuse); + response.setEntity(entity); + } + + return response; + + } catch (final ConnectionShutdownException ex) { + final InterruptedIOException ioex = new InterruptedIOException( + "Connection has been shut down"); + ioex.initCause(ex); + throw ioex; + } catch (final HttpException ex) { + abortConnection(); + throw ex; + } catch (final IOException ex) { + abortConnection(); + throw ex; + } catch (final RuntimeException ex) { + abortConnection(); + throw ex; + } + } // execute + + /** + * Establish connection either directly or through a tunnel and retry in case of + * a recoverable I/O failure + */ + private void tryConnect( + final RoutedRequest req, final HttpContext context) throws HttpException, IOException { + final HttpRoute route = req.getRoute(); + final HttpRequest wrapper = req.getRequest(); + + int connectCount = 0; + for (;;) { + context.setAttribute(ExecutionContext.HTTP_REQUEST, wrapper); + // Increment connect count + connectCount++; + try { + if (!managedConn.isOpen()) { + managedConn.open(route, context, params); + } else { + managedConn.setSocketTimeout(HttpConnectionParams.getSoTimeout(params)); + } + establishRoute(route, context); + break; + } catch (final IOException ex) { + try { + managedConn.close(); + } catch (final IOException ignore) { + } + if (retryHandler.retryRequest(ex, connectCount, context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when connecting to " + + route + + ": " + + ex.getMessage()); + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + this.log.info("Retrying connect to " + route); + } + } else { + throw ex; + } + } + } + } + + /** + * Execute request and retry in case of a recoverable I/O failure + */ + private HttpResponse tryExecute( + final RoutedRequest req, final HttpContext context) throws HttpException, IOException { + final RequestWrapper wrapper = req.getRequest(); + final HttpRoute route = req.getRoute(); + HttpResponse response = null; + + Exception retryReason = null; + for (;;) { + // Increment total exec count (with redirects) + execCount++; + // Increment exec count for this particular request + wrapper.incrementExecCount(); + if (!wrapper.isRepeatable()) { + this.log.debug("Cannot retry non-repeatable request"); + if (retryReason != null) { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity. The cause lists the " + + "reason the original request failed.", retryReason); + } else { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity."); + } + } + + try { + if (!managedConn.isOpen()) { + // If we have a direct route to the target host + // just re-open connection and re-try the request + if (!route.isTunnelled()) { + this.log.debug("Reopening the direct connection."); + managedConn.open(route, context, params); + } else { + // otherwise give up + this.log.debug("Proxied connection. Need to start over."); + break; + } + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Attempt " + execCount + " to execute request"); + } + response = requestExec.execute(wrapper, managedConn, context); + break; + + } catch (final IOException ex) { + this.log.debug("Closing the connection."); + try { + managedConn.close(); + } catch (final IOException ignore) { + } + if (retryHandler.retryRequest(ex, wrapper.getExecCount(), context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when processing request to " + + route + + ": " + + ex.getMessage()); + } + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + if (this.log.isInfoEnabled()) { + this.log.info("Retrying request to " + route); + } + retryReason = ex; + } else { + if (ex instanceof NoHttpResponseException) { + final NoHttpResponseException updatedex = new NoHttpResponseException( + route.getTargetHost().toHostString() + " failed to respond"); + updatedex.setStackTrace(ex.getStackTrace()); + throw updatedex; + } else { + throw ex; + } + } + } + } + return response; + } + + /** + * Returns the connection back to the connection manager + * and prepares for retrieving a new connection during + * the next request. + */ + protected void releaseConnection() { + // Release the connection through the ManagedConnection instead of the + // ConnectionManager directly. This lets the connection control how + // it is released. + try { + managedConn.releaseConnection(); + } catch(final IOException ignored) { + this.log.debug("IOException releasing connection", ignored); + } + managedConn = null; + } + + /** + * Determines the route for a request. + * Called by {@link #execute} + * to determine the route for either the original or a followup request. + * + * @param targetHost the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the execution, + * never <code>null</code> + * + * @return the route the request should take + * + * @throws HttpException in case of a problem + */ + protected HttpRoute determineRoute(final HttpHost targetHost, + final HttpRequest request, + final HttpContext context) + throws HttpException { + return this.routePlanner.determineRoute( + targetHost != null ? targetHost : (HttpHost) request.getParams() + .getParameter(ClientPNames.DEFAULT_HOST), + request, context); + } + + + /** + * Establishes the target route. + * + * @param route the route to establish + * @param context the context for the request execution + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected void establishRoute(final HttpRoute route, final HttpContext context) + throws HttpException, IOException { + + final HttpRouteDirector rowdy = new BasicRouteDirector(); + int step; + do { + final HttpRoute fact = managedConn.getRoute(); + step = rowdy.nextStep(route, fact); + + switch (step) { + + case HttpRouteDirector.CONNECT_TARGET: + case HttpRouteDirector.CONNECT_PROXY: + managedConn.open(route, context, this.params); + break; + + case HttpRouteDirector.TUNNEL_TARGET: { + final boolean secure = createTunnelToTarget(route, context); + this.log.debug("Tunnel to target created."); + managedConn.tunnelTarget(secure, this.params); + } break; + + case HttpRouteDirector.TUNNEL_PROXY: { + // The most simple example for this case is a proxy chain + // of two proxies, where P1 must be tunnelled to P2. + // route: Source -> P1 -> P2 -> Target (3 hops) + // fact: Source -> P1 -> Target (2 hops) + final int hop = fact.getHopCount()-1; // the hop to establish + final boolean secure = createTunnelToProxy(route, hop, context); + this.log.debug("Tunnel to proxy created."); + managedConn.tunnelProxy(route.getHopTarget(hop), + secure, this.params); + } break; + + + case HttpRouteDirector.LAYER_PROTOCOL: + managedConn.layerProtocol(context, this.params); + break; + + case HttpRouteDirector.UNREACHABLE: + throw new HttpException("Unable to establish route: " + + "planned = " + route + "; current = " + fact); + case HttpRouteDirector.COMPLETE: + // do nothing + break; + default: + throw new IllegalStateException("Unknown step indicator " + + step + " from RouteDirector."); + } + + } while (step > HttpRouteDirector.COMPLETE); + + } // establishConnection + + + /** + * Creates a tunnel to the target server. + * The connection must be established to the (last) proxy. + * A CONNECT request for tunnelling through the proxy will + * be created and sent, the response received and checked. + * This method does <i>not</i> update the connection with + * information about the tunnel, that is left to the caller. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return <code>true</code> if the tunnelled route is secure, + * <code>false</code> otherwise. + * The implementation here always returns <code>false</code>, + * but derived classes may override. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToTarget(final HttpRoute route, + final HttpContext context) + throws HttpException, IOException { + + final HttpHost proxy = route.getProxyHost(); + final HttpHost target = route.getTargetHost(); + HttpResponse response = null; + + for (;;) { + if (!this.managedConn.isOpen()) { + this.managedConn.open(route, context, this.params); + } + + final HttpRequest connect = createConnectRequest(route, context); + connect.setParams(this.params); + + // Populate the execution context + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target); + context.setAttribute(ClientContext.ROUTE, route); + context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy); + context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn); + context.setAttribute(ExecutionContext.HTTP_REQUEST, connect); + + this.requestExec.preProcess(connect, this.httpProcessor, context); + + response = this.requestExec.execute(connect, this.managedConn, context); + + response.setParams(this.params); + this.requestExec.postProcess(response, this.httpProcessor, context); + + final int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + + if (HttpClientParams.isAuthenticating(this.params)) { + if (this.authenticator.isAuthenticationRequested(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + if (this.authenticator.authenticate(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + this.log.debug("Connection kept alive"); + // Consume response content + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + } else { + this.managedConn.close(); + } + } else { + break; + } + } else { + break; + } + } + } + + final int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + final HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + this.managedConn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + + this.managedConn.markReusable(); + + // How to decide on security of the tunnelled connection? + // The socket factory knows only about the segment to the proxy. + // Even if that is secure, the hop to the target may be insecure. + // Leave it to derived classes, consider insecure by default here. + return false; + + } // createTunnelToTarget + + + + /** + * Creates a tunnel to an intermediate proxy. + * This method is <i>not</i> implemented in this class. + * It just throws an exception here. + * + * @param route the route to establish + * @param hop the hop in the route to establish now. + * <code>route.getHopTarget(hop)</code> + * will return the proxy to tunnel to. + * @param context the context for request execution + * + * @return <code>true</code> if the partially tunnelled connection + * is secure, <code>false</code> otherwise. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToProxy(final HttpRoute route, final int hop, + final HttpContext context) + throws HttpException, IOException { + + // Have a look at createTunnelToTarget and replicate the parts + // you need in a custom derived class. If your proxies don't require + // authentication, it is not too hard. But for the stock version of + // HttpClient, we cannot make such simplifying assumptions and would + // have to include proxy authentication code. The HttpComponents team + // is currently not in a position to support rarely used code of this + // complexity. Feel free to submit patches that refactor the code in + // createTunnelToTarget to facilitate re-use for proxy tunnelling. + + throw new HttpException("Proxy chains are not supported."); + } + + + + /** + * Creates the CONNECT request for tunnelling. + * Called by {@link #createTunnelToTarget createTunnelToTarget}. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return the CONNECT request for tunnelling + */ + protected HttpRequest createConnectRequest(final HttpRoute route, + final HttpContext context) { + // see RFC 2817, section 5.2 and + // INTERNET-DRAFT: Tunneling TCP based protocols through + // Web proxy servers + + final HttpHost target = route.getTargetHost(); + + final String host = target.getHostName(); + int port = target.getPort(); + if (port < 0) { + final Scheme scheme = connManager.getSchemeRegistry(). + getScheme(target.getSchemeName()); + port = scheme.getDefaultPort(); + } + + final StringBuilder buffer = new StringBuilder(host.length() + 6); + buffer.append(host); + buffer.append(':'); + buffer.append(Integer.toString(port)); + + final String authority = buffer.toString(); + final ProtocolVersion ver = HttpProtocolParams.getVersion(params); + final HttpRequest req = new BasicHttpRequest + ("CONNECT", authority, ver); + + return req; + } + + + /** + * Analyzes a response to check need for a followup. + * + * @param roureq the request and route. + * @param response the response to analayze + * @param context the context used for the current request execution + * + * @return the followup request and route if there is a followup, or + * <code>null</code> if the response should be returned as is + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected RoutedRequest handleResponse(final RoutedRequest roureq, + final HttpResponse response, + final HttpContext context) + throws HttpException, IOException { + + final HttpRoute route = roureq.getRoute(); + final RequestWrapper request = roureq.getRequest(); + + final HttpParams params = request.getParams(); + + if (HttpClientParams.isAuthenticating(params)) { + HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + target = route.getTargetHost(); + } + if (target.getPort() < 0) { + final Scheme scheme = connManager.getSchemeRegistry().getScheme(target); + target = new HttpHost(target.getHostName(), scheme.getDefaultPort(), target.getSchemeName()); + } + + final boolean targetAuthRequested = this.authenticator.isAuthenticationRequested( + target, response, this.targetAuthStrategy, targetAuthState, context); + + HttpHost proxy = route.getProxyHost(); + // if proxy is not set use target host instead + if (proxy == null) { + proxy = route.getTargetHost(); + } + final boolean proxyAuthRequested = this.authenticator.isAuthenticationRequested( + proxy, response, this.proxyAuthStrategy, proxyAuthState, context); + + if (targetAuthRequested) { + if (this.authenticator.authenticate(target, response, + this.targetAuthStrategy, this.targetAuthState, context)) { + // Re-try the same request via the same route + return roureq; + } + } + if (proxyAuthRequested) { + if (this.authenticator.authenticate(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + // Re-try the same request via the same route + return roureq; + } + } + } + + if (HttpClientParams.isRedirecting(params) && + this.redirectStrategy.isRedirected(request, response, context)) { + + if (redirectCount >= maxRedirects) { + throw new RedirectException("Maximum redirects (" + + maxRedirects + ") exceeded"); + } + redirectCount++; + + // Virtual host cannot be used any longer + virtualHost = null; + + final HttpUriRequest redirect = redirectStrategy.getRedirect(request, response, context); + final HttpRequest orig = request.getOriginal(); + redirect.setHeaders(orig.getAllHeaders()); + + final URI uri = redirect.getURI(); + final HttpHost newTarget = URIUtils.extractHost(uri); + if (newTarget == null) { + throw new ProtocolException("Redirect URI does not specify a valid host name: " + uri); + } + + // Reset auth states if redirecting to another host + if (!route.getTargetHost().equals(newTarget)) { + this.log.debug("Resetting target auth state"); + targetAuthState.reset(); + final AuthScheme authScheme = proxyAuthState.getAuthScheme(); + if (authScheme != null && authScheme.isConnectionBased()) { + this.log.debug("Resetting proxy auth state"); + proxyAuthState.reset(); + } + } + + final RequestWrapper wrapper = wrapRequest(redirect); + wrapper.setParams(params); + + final HttpRoute newRoute = determineRoute(newTarget, wrapper, context); + final RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); + + if (this.log.isDebugEnabled()) { + this.log.debug("Redirecting to '" + uri + "' via " + newRoute); + } + + return newRequest; + } + + return null; + } // handleResponse + + + /** + * Shuts down the connection. + * This method is called from a <code>catch</code> block in + * {@link #execute execute} during exception handling. + */ + private void abortConnection() { + final ManagedClientConnection mcc = managedConn; + if (mcc != null) { + // we got here as the result of an exception + // no response will be returned, release the connection + managedConn = null; + try { + mcc.abortConnection(); + } catch (final IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } + // ensure the connection manager properly releases this connection + try { + mcc.releaseConnection(); + } catch(final IOException ignored) { + this.log.debug("Error releasing connection", ignored); + } + } + } // abortConnection + + +} // class DefaultClientRequestDirector diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java new file mode 100644 index 000000000..8e7279aab --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java @@ -0,0 +1,80 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of the {@link ServiceUnavailableRetryStrategy} interface. + * that retries <code>503</code> (Service Unavailable) responses for a fixed number of times + * at a fixed interval. + * + * @since 4.2 + */ +@Immutable +public class DefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy { + + /** + * Maximum number of allowed retries if the server responds with a HTTP code + * in our retry code list. Default value is 1. + */ + private final int maxRetries; + + /** + * Retry interval between subsequent requests, in milliseconds. Default + * value is 1 second. + */ + private final long retryInterval; + + public DefaultServiceUnavailableRetryStrategy(final int maxRetries, final int retryInterval) { + super(); + Args.positive(maxRetries, "Max retries"); + Args.positive(retryInterval, "Retry interval"); + this.maxRetries = maxRetries; + this.retryInterval = retryInterval; + } + + public DefaultServiceUnavailableRetryStrategy() { + this(1, 1000); + } + + public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) { + return executionCount <= maxRetries && + response.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE; + } + + public long getRetryInterval() { + return retryInterval; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java new file mode 100644 index 000000000..a2a57e1d5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java @@ -0,0 +1,91 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.List; +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.MalformedChallengeException; +import ch.boye.httpclientandroidlib.auth.params.AuthPNames; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationHandler} implementation + * for target host authentication. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link TargetAuthenticationStrategy} + */ +@Deprecated +@Immutable +public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultTargetAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + Args.notNull(response, "HTTP response"); + final int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_UNAUTHORIZED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + Args.notNull(response, "HTTP response"); + final Header[] headers = response.getHeaders(AUTH.WWW_AUTH); + return parseChallenges(headers); + } + + @Override + protected List<String> getAuthPreferences( + final HttpResponse response, + final HttpContext context) { + @SuppressWarnings("unchecked") + final + List<String> authpref = (List<String>) response.getParams().getParameter( + AuthPNames.TARGET_AUTH_PREF); + if (authpref != null) { + return authpref; + } else { + return super.getAuthPreferences(response, context); + } + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java new file mode 100644 index 000000000..c6d2a8daa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java @@ -0,0 +1,101 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.security.Principal; + +import javax.net.ssl.SSLSession; + +import ch.boye.httpclientandroidlib.HttpConnection; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Default implementation of {@link UserTokenHandler}. This class will use + * an instance of {@link Principal} as a state object for HTTP connections, + * if it can be obtained from the given execution context. This helps ensure + * persistent connections created with a particular user identity within + * a particular security context can be reused by the same user only. + * <p> + * DefaultUserTokenHandler will use the user principle of connection + * based authentication schemes such as NTLM or that of the SSL session + * with the client authentication turned on. If both are unavailable, + * <code>null</code> token will be returned. + * + * @since 4.0 + */ +@Immutable +public class DefaultUserTokenHandler implements UserTokenHandler { + + public static final DefaultUserTokenHandler INSTANCE = new DefaultUserTokenHandler(); + + public Object getUserToken(final HttpContext context) { + + final HttpClientContext clientContext = HttpClientContext.adapt(context); + + Principal userPrincipal = null; + + final AuthState targetAuthState = clientContext.getTargetAuthState(); + if (targetAuthState != null) { + userPrincipal = getAuthPrincipal(targetAuthState); + if (userPrincipal == null) { + final AuthState proxyAuthState = clientContext.getProxyAuthState(); + userPrincipal = getAuthPrincipal(proxyAuthState); + } + } + + if (userPrincipal == null) { + final HttpConnection conn = clientContext.getConnection(); + if (conn.isOpen() && conn instanceof ManagedHttpClientConnection) { + final SSLSession sslsession = ((ManagedHttpClientConnection) conn).getSSLSession(); + if (sslsession != null) { + userPrincipal = sslsession.getLocalPrincipal(); + } + } + } + + return userPrincipal; + } + + private static Principal getAuthPrincipal(final AuthState authState) { + final AuthScheme scheme = authState.getAuthScheme(); + if (scheme != null && scheme.isComplete() && scheme.isConnectionBased()) { + final Credentials creds = authState.getCredentials(); + if (creds != null) { + return creds.getUserPrincipal(); + } + } + return null; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java new file mode 100644 index 000000000..45fbbe0a6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java @@ -0,0 +1,113 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.entity.HttpEntityWrapper; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * A wrapper class for {@link HttpEntityEnclosingRequest}s that can + * be used to change properties of the current request without + * modifying the original object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @since 4.0 + * + * @deprecated (4.3) do not use. + */ +@Deprecated +@NotThreadSafe // e.g. [gs]etEntity() +public class EntityEnclosingRequestWrapper extends RequestWrapper + implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + private boolean consumed; + + public EntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request) + throws ProtocolException { + super(request); + setEntity(request.getEntity()); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity != null ? new EntityWrapper(entity) : null; + this.consumed = false; + } + + public boolean expectContinue() { + final Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + + @Override + public boolean isRepeatable() { + return this.entity == null || this.entity.isRepeatable() || !this.consumed; + } + + class EntityWrapper extends HttpEntityWrapper { + + EntityWrapper(final HttpEntity entity) { + super(entity); + } + + @Override + public void consumeContent() throws IOException { + consumed = true; + super.consumeContent(); + } + + @Override + public InputStream getContent() throws IOException { + consumed = true; + return super.getContent(); + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + consumed = true; + super.writeTo(outstream); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java new file mode 100644 index 000000000..888e7e3a5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java @@ -0,0 +1,156 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Collection of different counters used to gather metrics for {@link FutureRequestExecutionService}. + */ +public final class FutureRequestExecutionMetrics { + + private final AtomicLong activeConnections = new AtomicLong(); + private final AtomicLong scheduledConnections = new AtomicLong(); + private final DurationCounter successfulConnections = new DurationCounter(); + private final DurationCounter failedConnections = new DurationCounter(); + private final DurationCounter requests = new DurationCounter(); + private final DurationCounter tasks = new DurationCounter(); + + FutureRequestExecutionMetrics() { + } + + AtomicLong getActiveConnections() { + return activeConnections; + } + + AtomicLong getScheduledConnections() { + return scheduledConnections; + } + + DurationCounter getSuccessfulConnections() { + return successfulConnections; + } + + DurationCounter getFailedConnections() { + return failedConnections; + } + + DurationCounter getRequests() { + return requests; + } + + DurationCounter getTasks() { + return tasks; + } + + public long getActiveConnectionCount() { + return activeConnections.get(); + } + + public long getScheduledConnectionCount() { + return scheduledConnections.get(); + } + + public long getSuccessfulConnectionCount() { + return successfulConnections.count(); + } + + public long getSuccessfulConnectionAverageDuration() { + return successfulConnections.averageDuration(); + } + + public long getFailedConnectionCount() { + return failedConnections.count(); + } + + public long getFailedConnectionAverageDuration() { + return failedConnections.averageDuration(); + } + + public long getRequestCount() { + return requests.count(); + } + + public long getRequestAverageDuration() { + return requests.averageDuration(); + } + + public long getTaskCount() { + return tasks.count(); + } + + public long getTaskAverageDuration() { + return tasks.averageDuration(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[activeConnections=").append(activeConnections) + .append(", scheduledConnections=").append(scheduledConnections) + .append(", successfulConnections=").append(successfulConnections) + .append(", failedConnections=").append(failedConnections) + .append(", requests=").append(requests) + .append(", tasks=").append(tasks) + .append("]"); + return builder.toString(); + } + + /** + * A counter that can measure duration and number of events. + */ + static class DurationCounter { + + private final AtomicLong count = new AtomicLong(0); + private final AtomicLong cumulativeDuration = new AtomicLong(0); + + public void increment(final long startTime) { + count.incrementAndGet(); + cumulativeDuration.addAndGet(System.currentTimeMillis() - startTime); + } + + public long count() { + return count.get(); + } + + public long averageDuration() { + final long counter = count.get(); + return counter > 0 ? cumulativeDuration.get() / counter : 0; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[count=").append(count()) + .append(", averageDuration=").append(averageDuration()) + .append("]"); + return builder.toString(); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java new file mode 100644 index 000000000..26fa9dba3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java @@ -0,0 +1,142 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.concurrent.FutureCallback; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * HttpAsyncClientWithFuture wraps calls to execute with a {@link HttpRequestFutureTask} + * and schedules them using the provided executor service. Scheduled calls may be cancelled. + */ +@ThreadSafe +public class FutureRequestExecutionService implements Closeable { + + private final HttpClient httpclient; + private final ExecutorService executorService; + private final FutureRequestExecutionMetrics metrics = new FutureRequestExecutionMetrics(); + private final AtomicBoolean closed = new AtomicBoolean(false); + + /** + * Create a new FutureRequestExecutionService. + * + * @param httpclient + * you should tune your httpclient instance to match your needs. You should + * align the max number of connections in the pool and the number of threads + * in the executor; it doesn't make sense to have more threads than connections + * and if you have less connections than threads, the threads will just end up + * blocking on getting a connection from the pool. + * @param executorService + * any executorService will do here. E.g. + * {@link java.util.concurrent.Executors#newFixedThreadPool(int)} + */ + public FutureRequestExecutionService( + final HttpClient httpclient, + final ExecutorService executorService) { + this.httpclient = httpclient; + this.executorService = executorService; + } + + /** + * Schedule a request for execution. + * + * @param <T> + * + * @param request + * request to execute + * @param responseHandler + * handler that will process the response. + * @return HttpAsyncClientFutureTask for the scheduled request. + * @throws InterruptedException + */ + public <T> HttpRequestFutureTask<T> execute( + final HttpUriRequest request, + final HttpContext context, + final ResponseHandler<T> responseHandler) { + return execute(request, context, responseHandler, null); + } + + /** + * Schedule a request for execution. + * + * @param <T> + * + * @param request + * request to execute + * @param context + * optional context; use null if not needed. + * @param responseHandler + * handler that will process the response. + * @param callback + * callback handler that will be called when the request is scheduled, + * started, completed, failed, or cancelled. + * @return HttpAsyncClientFutureTask for the scheduled request. + * @throws InterruptedException + */ + public <T> HttpRequestFutureTask<T> execute( + final HttpUriRequest request, + final HttpContext context, + final ResponseHandler<T> responseHandler, + final FutureCallback<T> callback) { + if(closed.get()) { + throw new IllegalStateException("Close has been called on this httpclient instance."); + } + metrics.getScheduledConnections().incrementAndGet(); + final HttpRequestTaskCallable<T> callable = new HttpRequestTaskCallable<T>( + httpclient, request, context, responseHandler, callback, metrics); + final HttpRequestFutureTask<T> httpRequestFutureTask = new HttpRequestFutureTask<T>( + request, callable); + executorService.execute(httpRequestFutureTask); + + return httpRequestFutureTask; + } + + /** + * @return metrics gathered for this instance. + * @see FutureRequestExecutionMetrics + */ + public FutureRequestExecutionMetrics metrics() { + return metrics; + } + + public void close() throws IOException { + closed.set(true); + executorService.shutdownNow(); + if (httpclient instanceof Closeable) { + ((Closeable) httpclient).close(); + } + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java new file mode 100644 index 000000000..745ae4735 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java @@ -0,0 +1,61 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * @deprecated (4.3) reserved for internal use. + * + */ +@Deprecated +public class HttpAuthenticator extends ch.boye.httpclientandroidlib.impl.auth.HttpAuthenticator { + + public HttpAuthenticator(final HttpClientAndroidLog log) { + super(log); + } + + public HttpAuthenticator() { + super(); + } + + public boolean authenticate ( + final HttpHost host, + final HttpResponse response, + final AuthenticationStrategy authStrategy, + final AuthState authState, + final HttpContext context) { + return handleAuthChallenge(host, response, authStrategy, authState, context); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java new file mode 100644 index 000000000..00313e668 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java @@ -0,0 +1,954 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.net.ProxySelector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequestInterceptor; +import ch.boye.httpclientandroidlib.HttpResponseInterceptor; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.client.config.CookieSpecs; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.protocol.RequestAcceptEncoding; +import ch.boye.httpclientandroidlib.client.protocol.RequestAddCookies; +import ch.boye.httpclientandroidlib.client.protocol.RequestAuthCache; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.client.protocol.RequestDefaultHeaders; +import ch.boye.httpclientandroidlib.client.protocol.RequestExpectContinue; +import ch.boye.httpclientandroidlib.client.protocol.ResponseContentEncoding; +import ch.boye.httpclientandroidlib.client.protocol.ResponseProcessCookies; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.config.RegistryBuilder; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.ssl.SSLConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.ssl.SSLContexts; +import ch.boye.httpclientandroidlib.conn.ssl.X509HostnameVerifier; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.NoConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.auth.BasicSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.DigestSchemeFactory; +/* KerberosSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.auth.NTLMSchemeFactory; +/* SPNegoSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.conn.DefaultProxyRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.DefaultRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.DefaultSchemePortResolver; +import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.SystemDefaultRoutePlanner; +import ch.boye.httpclientandroidlib.impl.cookie.BestMatchSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.BrowserCompatSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.IgnoreSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.NetscapeDraftSpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2109SpecFactory; +import ch.boye.httpclientandroidlib.impl.cookie.RFC2965SpecFactory; +import ch.boye.httpclientandroidlib.impl.execchain.BackoffStrategyExec; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; +import ch.boye.httpclientandroidlib.impl.execchain.MainClientExec; +import ch.boye.httpclientandroidlib.impl.execchain.ProtocolExec; +import ch.boye.httpclientandroidlib.impl.execchain.RedirectExec; +import ch.boye.httpclientandroidlib.impl.execchain.RetryExec; +import ch.boye.httpclientandroidlib.impl.execchain.ServiceUnavailableRetryExec; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpProcessorBuilder; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.RequestContent; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; +import ch.boye.httpclientandroidlib.util.TextUtils; +import ch.boye.httpclientandroidlib.util.VersionInfo; + +/** + * Builder for {@link CloseableHttpClient} instances. + * <p/> + * When a particular component is not explicitly this class will + * use its default implementation. System properties will be taken + * into account when configuring the default implementations when + * {@link #useSystemProperties()} method is called prior to calling + * {@link #build()}. + * <ul> + * <li>ssl.TrustManagerFactory.algorithm</li> + * <li>javax.net.ssl.trustStoreType</li> + * <li>javax.net.ssl.trustStore</li> + * <li>javax.net.ssl.trustStoreProvider</li> + * <li>javax.net.ssl.trustStorePassword</li> + * <li>ssl.KeyManagerFactory.algorithm</li> + * <li>javax.net.ssl.keyStoreType</li> + * <li>javax.net.ssl.keyStore</li> + * <li>javax.net.ssl.keyStoreProvider</li> + * <li>javax.net.ssl.keyStorePassword</li> + * <li>https.protocols</li> + * <li>https.cipherSuites</li> + * <li>http.proxyHost</li> + * <li>http.proxyPort</li> + * <li>http.nonProxyHosts</li> + * <li>http.keepAlive</li> + * <li>http.maxConnections</li> + * <li>http.agent</li> + * </ul> + * <p/> + * Please note that some settings used by this class can be mutually + * exclusive and may not apply when building {@link CloseableHttpClient} + * instances. + * + * @since 4.3 + */ +@NotThreadSafe +public class HttpClientBuilder { + + private HttpRequestExecutor requestExec; + private X509HostnameVerifier hostnameVerifier; + private LayeredConnectionSocketFactory sslSocketFactory; + private SSLContext sslcontext; + private HttpClientConnectionManager connManager; + private SchemePortResolver schemePortResolver; + private ConnectionReuseStrategy reuseStrategy; + private ConnectionKeepAliveStrategy keepAliveStrategy; + private AuthenticationStrategy targetAuthStrategy; + private AuthenticationStrategy proxyAuthStrategy; + private UserTokenHandler userTokenHandler; + private HttpProcessor httpprocessor; + + private LinkedList<HttpRequestInterceptor> requestFirst; + private LinkedList<HttpRequestInterceptor> requestLast; + private LinkedList<HttpResponseInterceptor> responseFirst; + private LinkedList<HttpResponseInterceptor> responseLast; + + private HttpRequestRetryHandler retryHandler; + private HttpRoutePlanner routePlanner; + private RedirectStrategy redirectStrategy; + private ConnectionBackoffStrategy connectionBackoffStrategy; + private BackoffManager backoffManager; + private ServiceUnavailableRetryStrategy serviceUnavailStrategy; + private Lookup<AuthSchemeProvider> authSchemeRegistry; + private Lookup<CookieSpecProvider> cookieSpecRegistry; + private CookieStore cookieStore; + private CredentialsProvider credentialsProvider; + private String userAgent; + private HttpHost proxy; + private Collection<? extends Header> defaultHeaders; + private SocketConfig defaultSocketConfig; + private ConnectionConfig defaultConnectionConfig; + private RequestConfig defaultRequestConfig; + + private boolean systemProperties; + private boolean redirectHandlingDisabled; + private boolean automaticRetriesDisabled; + private boolean contentCompressionDisabled; + private boolean cookieManagementDisabled; + private boolean authCachingDisabled; + private boolean connectionStateDisabled; + + private int maxConnTotal = 0; + private int maxConnPerRoute = 0; + + private List<Closeable> closeables; + + static final String DEFAULT_USER_AGENT; + static { + final VersionInfo vi = VersionInfo.loadVersionInfo + ("ch.boye.httpclientandroidlib.client", HttpClientBuilder.class.getClassLoader()); + final String release = (vi != null) ? + vi.getRelease() : VersionInfo.UNAVAILABLE; + DEFAULT_USER_AGENT = "Apache-HttpClient/" + release + " (java 1.5)"; + } + + public static HttpClientBuilder create() { + return new HttpClientBuilder(); + } + + protected HttpClientBuilder() { + super(); + } + + /** + * Assigns {@link HttpRequestExecutor} instance. + */ + public final HttpClientBuilder setRequestExecutor(final HttpRequestExecutor requestExec) { + this.requestExec = requestExec; + return this; + } + + /** + * Assigns {@link X509HostnameVerifier} instance. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} and the {@link #setSSLSocketFactory( + * ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory)} methods. + */ + public final HttpClientBuilder setHostnameVerifier(final X509HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + + /** + * Assigns {@link SSLContext} instance. + * <p/> + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} and the {@link #setSSLSocketFactory( + * ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory)} methods. + */ + public final HttpClientBuilder setSslcontext(final SSLContext sslcontext) { + this.sslcontext = sslcontext; + return this; + } + + /** + * Assigns {@link LayeredConnectionSocketFactory} instance. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setSSLSocketFactory( + final LayeredConnectionSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + return this; + } + + /** + * Assigns maximum total connection value. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setMaxConnTotal(final int maxConnTotal) { + this.maxConnTotal = maxConnTotal; + return this; + } + + /** + * Assigns maximum connection per route value. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setMaxConnPerRoute(final int maxConnPerRoute) { + this.maxConnPerRoute = maxConnPerRoute; + return this; + } + + /** + * Assigns default {@link SocketConfig}. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setDefaultSocketConfig(final SocketConfig config) { + this.defaultSocketConfig = config; + return this; + } + + /** + * Assigns default {@link ConnectionConfig}. + * <p/> + * Please note this value can be overridden by the {@link #setConnectionManager( + * ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager)} method. + */ + public final HttpClientBuilder setDefaultConnectionConfig(final ConnectionConfig config) { + this.defaultConnectionConfig = config; + return this; + } + + /** + * Assigns {@link HttpClientConnectionManager} instance. + */ + public final HttpClientBuilder setConnectionManager( + final HttpClientConnectionManager connManager) { + this.connManager = connManager; + return this; + } + + /** + * Assigns {@link ConnectionReuseStrategy} instance. + */ + public final HttpClientBuilder setConnectionReuseStrategy( + final ConnectionReuseStrategy reuseStrategy) { + this.reuseStrategy = reuseStrategy; + return this; + } + + /** + * Assigns {@link ConnectionKeepAliveStrategy} instance. + */ + public final HttpClientBuilder setKeepAliveStrategy( + final ConnectionKeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for proxy + * authentication. + */ + public final HttpClientBuilder setTargetAuthenticationStrategy( + final AuthenticationStrategy targetAuthStrategy) { + this.targetAuthStrategy = targetAuthStrategy; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for target + * host authentication. + */ + public final HttpClientBuilder setProxyAuthenticationStrategy( + final AuthenticationStrategy proxyAuthStrategy) { + this.proxyAuthStrategy = proxyAuthStrategy; + return this; + } + + /** + * Assigns {@link UserTokenHandler} instance. + * <p/> + * Please note this value can be overridden by the {@link #disableConnectionState()} + * method. + */ + public final HttpClientBuilder setUserTokenHandler(final UserTokenHandler userTokenHandler) { + this.userTokenHandler = userTokenHandler; + return this; + } + + /** + * Disables connection state tracking. + */ + public final HttpClientBuilder disableConnectionState() { + connectionStateDisabled = true; + return this; + } + + /** + * Assigns {@link SchemePortResolver} instance. + */ + public final HttpClientBuilder setSchemePortResolver( + final SchemePortResolver schemePortResolver) { + this.schemePortResolver = schemePortResolver; + return this; + } + + /** + * Assigns <tt>User-Agent</tt> value. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder setUserAgent(final String userAgent) { + this.userAgent = userAgent; + return this; + } + + /** + * Assigns default request header values. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders) { + this.defaultHeaders = defaultHeaders; + return this; + } + + /** + * Adds this protocol interceptor to the head of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorFirst(final HttpResponseInterceptor itcp) { + if (itcp == null) { + return this; + } + if (responseFirst == null) { + responseFirst = new LinkedList<HttpResponseInterceptor>(); + } + responseFirst.addFirst(itcp); + return this; + } + + /** + * Adds this protocol interceptor to the tail of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorLast(final HttpResponseInterceptor itcp) { + if (itcp == null) { + return this; + } + if (responseLast == null) { + responseLast = new LinkedList<HttpResponseInterceptor>(); + } + responseLast.addLast(itcp); + return this; + } + + /** + * Adds this protocol interceptor to the head of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorFirst(final HttpRequestInterceptor itcp) { + if (itcp == null) { + return this; + } + if (requestFirst == null) { + requestFirst = new LinkedList<HttpRequestInterceptor>(); + } + requestFirst.addFirst(itcp); + return this; + } + + /** + * Adds this protocol interceptor to the tail of the protocol processing list. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder addInterceptorLast(final HttpRequestInterceptor itcp) { + if (itcp == null) { + return this; + } + if (requestLast == null) { + requestLast = new LinkedList<HttpRequestInterceptor>(); + } + requestLast.addLast(itcp); + return this; + } + + /** + * Disables state (cookie) management. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder disableCookieManagement() { + this.cookieManagementDisabled = true; + return this; + } + + /** + * Disables automatic content decompression. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder disableContentCompression() { + contentCompressionDisabled = true; + return this; + } + + /** + * Disables authentication scheme caching. + * <p/> + * Please note this value can be overridden by the {@link #setHttpProcessor( + * ch.boye.httpclientandroidlib.protocol.HttpProcessor)} method. + */ + public final HttpClientBuilder disableAuthCaching() { + this.authCachingDisabled = true; + return this; + } + + /** + * Assigns {@link HttpProcessor} instance. + */ + public final HttpClientBuilder setHttpProcessor(final HttpProcessor httpprocessor) { + this.httpprocessor = httpprocessor; + return this; + } + + /** + * Assigns {@link HttpRequestRetryHandler} instance. + * <p/> + * Please note this value can be overridden by the {@link #disableAutomaticRetries()} + * method. + */ + public final HttpClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) { + this.retryHandler = retryHandler; + return this; + } + + /** + * Disables automatic request recovery and re-execution. + */ + public final HttpClientBuilder disableAutomaticRetries() { + automaticRetriesDisabled = true; + return this; + } + + /** + * Assigns default proxy value. + * <p/> + * Please note this value can be overridden by the {@link #setRoutePlanner( + * ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner)} method. + */ + public final HttpClientBuilder setProxy(final HttpHost proxy) { + this.proxy = proxy; + return this; + } + + /** + * Assigns {@link HttpRoutePlanner} instance. + */ + public final HttpClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + return this; + } + + /** + * Assigns {@link RedirectStrategy} instance. + * <p/> + * Please note this value can be overridden by the {@link #disableRedirectHandling()} + * method. +` */ + public final HttpClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) { + this.redirectStrategy = redirectStrategy; + return this; + } + + /** + * Disables automatic redirect handling. + */ + public final HttpClientBuilder disableRedirectHandling() { + redirectHandlingDisabled = true; + return this; + } + + /** + * Assigns {@link ConnectionBackoffStrategy} instance. + */ + public final HttpClientBuilder setConnectionBackoffStrategy( + final ConnectionBackoffStrategy connectionBackoffStrategy) { + this.connectionBackoffStrategy = connectionBackoffStrategy; + return this; + } + + /** + * Assigns {@link BackoffManager} instance. + */ + public final HttpClientBuilder setBackoffManager(final BackoffManager backoffManager) { + this.backoffManager = backoffManager; + return this; + } + + /** + * Assigns {@link ServiceUnavailableRetryStrategy} instance. + */ + public final HttpClientBuilder setServiceUnavailableRetryStrategy( + final ServiceUnavailableRetryStrategy serviceUnavailStrategy) { + this.serviceUnavailStrategy = serviceUnavailStrategy; + return this; + } + + /** + * Assigns default {@link CookieStore} instance which will be used for + * request execution if not explicitly set in the client execution context. + */ + public final HttpClientBuilder setDefaultCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + return this; + } + + /** + * Assigns default {@link CredentialsProvider} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultCredentialsProvider( + final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Assigns default {@link ch.boye.httpclientandroidlib.auth.AuthScheme} registry which will + * be used for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultAuthSchemeRegistry( + final Lookup<AuthSchemeProvider> authSchemeRegistry) { + this.authSchemeRegistry = authSchemeRegistry; + return this; + } + + /** + * Assigns default {@link ch.boye.httpclientandroidlib.cookie.CookieSpec} registry which will + * be used for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultCookieSpecRegistry( + final Lookup<CookieSpecProvider> cookieSpecRegistry) { + this.cookieSpecRegistry = cookieSpecRegistry; + return this; + } + + /** + * Assigns default {@link RequestConfig} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final HttpClientBuilder setDefaultRequestConfig(final RequestConfig config) { + this.defaultRequestConfig = config; + return this; + } + + /** + * Use system properties when creating and configuring default + * implementations. + */ + public final HttpClientBuilder useSystemProperties() { + systemProperties = true; + return this; + } + + /** + * For internal use. + */ + protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) { + return mainExec; + } + + /** + * For internal use. + */ + protected ClientExecChain decorateProtocolExec(final ClientExecChain protocolExec) { + return protocolExec; + } + + /** + * For internal use. + */ + protected void addCloseable(final Closeable closeable) { + if (closeable == null) { + return; + } + if (closeables == null) { + closeables = new ArrayList<Closeable>(); + } + closeables.add(closeable); + } + + private static String[] split(final String s) { + if (TextUtils.isBlank(s)) { + return null; + } + return s.split(" *, *"); + } + + public CloseableHttpClient build() { + // Create main request executor + HttpRequestExecutor requestExec = this.requestExec; + if (requestExec == null) { + requestExec = new HttpRequestExecutor(); + } + HttpClientConnectionManager connManager = this.connManager; + if (connManager == null) { + LayeredConnectionSocketFactory sslSocketFactory = this.sslSocketFactory; + if (sslSocketFactory == null) { + final String[] supportedProtocols = systemProperties ? split( + System.getProperty("https.protocols")) : null; + final String[] supportedCipherSuites = systemProperties ? split( + System.getProperty("https.cipherSuites")) : null; + X509HostnameVerifier hostnameVerifier = this.hostnameVerifier; + if (hostnameVerifier == null) { + hostnameVerifier = SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + } + if (sslcontext != null) { + sslSocketFactory = new SSLConnectionSocketFactory( + sslcontext, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } else { + if (systemProperties) { + sslSocketFactory = new SSLConnectionSocketFactory( + (SSLSocketFactory) SSLSocketFactory.getDefault(), + supportedProtocols, supportedCipherSuites, hostnameVerifier); + } else { + sslSocketFactory = new SSLConnectionSocketFactory( + SSLContexts.createDefault(), + hostnameVerifier); + } + } + } + @SuppressWarnings("resource") + final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager( + RegistryBuilder.<ConnectionSocketFactory>create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build()); + if (defaultSocketConfig != null) { + poolingmgr.setDefaultSocketConfig(defaultSocketConfig); + } + if (defaultConnectionConfig != null) { + poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig); + } + if (systemProperties) { + String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + s = System.getProperty("http.maxConnections", "5"); + final int max = Integer.parseInt(s); + poolingmgr.setDefaultMaxPerRoute(max); + poolingmgr.setMaxTotal(2 * max); + } + } + if (maxConnTotal > 0) { + poolingmgr.setMaxTotal(maxConnTotal); + } + if (maxConnPerRoute > 0) { + poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute); + } + connManager = poolingmgr; + } + ConnectionReuseStrategy reuseStrategy = this.reuseStrategy; + if (reuseStrategy == null) { + if (systemProperties) { + final String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE; + } else { + reuseStrategy = NoConnectionReuseStrategy.INSTANCE; + } + } else { + reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE; + } + } + ConnectionKeepAliveStrategy keepAliveStrategy = this.keepAliveStrategy; + if (keepAliveStrategy == null) { + keepAliveStrategy = DefaultConnectionKeepAliveStrategy.INSTANCE; + } + AuthenticationStrategy targetAuthStrategy = this.targetAuthStrategy; + if (targetAuthStrategy == null) { + targetAuthStrategy = TargetAuthenticationStrategy.INSTANCE; + } + AuthenticationStrategy proxyAuthStrategy = this.proxyAuthStrategy; + if (proxyAuthStrategy == null) { + proxyAuthStrategy = ProxyAuthenticationStrategy.INSTANCE; + } + UserTokenHandler userTokenHandler = this.userTokenHandler; + if (userTokenHandler == null) { + if (!connectionStateDisabled) { + userTokenHandler = DefaultUserTokenHandler.INSTANCE; + } else { + userTokenHandler = NoopUserTokenHandler.INSTANCE; + } + } + ClientExecChain execChain = new MainClientExec( + requestExec, + connManager, + reuseStrategy, + keepAliveStrategy, + targetAuthStrategy, + proxyAuthStrategy, + userTokenHandler); + + execChain = decorateMainExec(execChain); + + HttpProcessor httpprocessor = this.httpprocessor; + if (httpprocessor == null) { + + String userAgent = this.userAgent; + if (userAgent == null) { + if (systemProperties) { + userAgent = System.getProperty("http.agent"); + } + if (userAgent == null) { + userAgent = DEFAULT_USER_AGENT; + } + } + + final HttpProcessorBuilder b = HttpProcessorBuilder.create(); + if (requestFirst != null) { + for (final HttpRequestInterceptor i: requestFirst) { + b.addFirst(i); + } + } + if (responseFirst != null) { + for (final HttpResponseInterceptor i: responseFirst) { + b.addFirst(i); + } + } + b.addAll( + new RequestDefaultHeaders(defaultHeaders), + new RequestContent(), + new RequestTargetHost(), + new RequestClientConnControl(), + new RequestUserAgent(userAgent), + new RequestExpectContinue()); + if (!cookieManagementDisabled) { + b.add(new RequestAddCookies()); + } + if (!contentCompressionDisabled) { + b.add(new RequestAcceptEncoding()); + } + if (!authCachingDisabled) { + b.add(new RequestAuthCache()); + } + if (!cookieManagementDisabled) { + b.add(new ResponseProcessCookies()); + } + if (!contentCompressionDisabled) { + b.add(new ResponseContentEncoding()); + } + if (requestLast != null) { + for (final HttpRequestInterceptor i: requestLast) { + b.addLast(i); + } + } + if (responseLast != null) { + for (final HttpResponseInterceptor i: responseLast) { + b.addLast(i); + } + } + httpprocessor = b.build(); + } + execChain = new ProtocolExec(execChain, httpprocessor); + + execChain = decorateProtocolExec(execChain); + + // Add request retry executor, if not disabled + if (!automaticRetriesDisabled) { + HttpRequestRetryHandler retryHandler = this.retryHandler; + if (retryHandler == null) { + retryHandler = DefaultHttpRequestRetryHandler.INSTANCE; + } + execChain = new RetryExec(execChain, retryHandler); + } + + HttpRoutePlanner routePlanner = this.routePlanner; + if (routePlanner == null) { + SchemePortResolver schemePortResolver = this.schemePortResolver; + if (schemePortResolver == null) { + schemePortResolver = DefaultSchemePortResolver.INSTANCE; + } + if (proxy != null) { + routePlanner = new DefaultProxyRoutePlanner(proxy, schemePortResolver); + } else if (systemProperties) { + routePlanner = new SystemDefaultRoutePlanner( + schemePortResolver, ProxySelector.getDefault()); + } else { + routePlanner = new DefaultRoutePlanner(schemePortResolver); + } + } + // Add redirect executor, if not disabled + if (!redirectHandlingDisabled) { + RedirectStrategy redirectStrategy = this.redirectStrategy; + if (redirectStrategy == null) { + redirectStrategy = DefaultRedirectStrategy.INSTANCE; + } + execChain = new RedirectExec(execChain, routePlanner, redirectStrategy); + } + + // Optionally, add service unavailable retry executor + final ServiceUnavailableRetryStrategy serviceUnavailStrategy = this.serviceUnavailStrategy; + if (serviceUnavailStrategy != null) { + execChain = new ServiceUnavailableRetryExec(execChain, serviceUnavailStrategy); + } + // Optionally, add connection back-off executor + final BackoffManager backoffManager = this.backoffManager; + final ConnectionBackoffStrategy connectionBackoffStrategy = this.connectionBackoffStrategy; + if (backoffManager != null && connectionBackoffStrategy != null) { + execChain = new BackoffStrategyExec(execChain, connectionBackoffStrategy, backoffManager); + } + + Lookup<AuthSchemeProvider> authSchemeRegistry = this.authSchemeRegistry; + if (authSchemeRegistry == null) { + authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create() + .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) + .register(AuthSchemes.NTLM, new NTLMSchemeFactory()) + /* SPNegoSchemeFactory removed by HttpClient for Android script. */ + /* KerberosSchemeFactory removed by HttpClient for Android script. */ + .build(); + } + Lookup<CookieSpecProvider> cookieSpecRegistry = this.cookieSpecRegistry; + if (cookieSpecRegistry == null) { + cookieSpecRegistry = RegistryBuilder.<CookieSpecProvider>create() + .register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory()) + .register(CookieSpecs.STANDARD, new RFC2965SpecFactory()) + .register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory()) + .register(CookieSpecs.NETSCAPE, new NetscapeDraftSpecFactory()) + .register(CookieSpecs.IGNORE_COOKIES, new IgnoreSpecFactory()) + .register("rfc2109", new RFC2109SpecFactory()) + .register("rfc2965", new RFC2965SpecFactory()) + .build(); + } + + CookieStore defaultCookieStore = this.cookieStore; + if (defaultCookieStore == null) { + defaultCookieStore = new BasicCookieStore(); + } + + CredentialsProvider defaultCredentialsProvider = this.credentialsProvider; + if (defaultCredentialsProvider == null) { + if (systemProperties) { + defaultCredentialsProvider = new SystemDefaultCredentialsProvider(); + } else { + defaultCredentialsProvider = new BasicCredentialsProvider(); + } + } + + return new InternalHttpClient( + execChain, + connManager, + routePlanner, + cookieSpecRegistry, + authSchemeRegistry, + defaultCookieStore, + defaultCredentialsProvider, + defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT, + closeables != null ? new ArrayList<Closeable>(closeables) : null); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClients.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClients.java new file mode 100644 index 000000000..294ac636b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpClients.java @@ -0,0 +1,85 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager; + +/** + * Factory methods for {@link CloseableHttpClient} instances. + * @since 4.3 + */ +@Immutable +public class HttpClients { + + private HttpClients() { + super(); + } + + /** + * Creates builder object for construction of custom + * {@link CloseableHttpClient} instances. + */ + public static HttpClientBuilder custom() { + return HttpClientBuilder.create(); + } + + /** + * Creates {@link CloseableHttpClient} instance with default + * configuration. + */ + public static CloseableHttpClient createDefault() { + return HttpClientBuilder.create().build(); + } + + /** + * Creates {@link CloseableHttpClient} instance with default + * configuration based on ssytem properties. + */ + public static CloseableHttpClient createSystem() { + return HttpClientBuilder.create().useSystemProperties().build(); + } + + /** + * Creates {@link CloseableHttpClient} instance that implements + * the most basic HTTP protocol support. + */ + public static CloseableHttpClient createMinimal() { + return new MinimalHttpClient(new PoolingHttpClientConnectionManager()); + } + + /** + * Creates {@link CloseableHttpClient} instance that implements + * the most basic HTTP protocol support. + */ + public static CloseableHttpClient createMinimal(final HttpClientConnectionManager connManager) { + return new MinimalHttpClient(connManager); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java new file mode 100644 index 000000000..b5f932403 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java @@ -0,0 +1,118 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.concurrent.FutureTask; + +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; + +/** + * FutureTask implementation that wraps a HttpAsyncClientCallable and exposes various task + * specific metrics. + * + * @param <V> + */ +public class HttpRequestFutureTask<V> extends FutureTask<V> { + + private final HttpUriRequest request; + private final HttpRequestTaskCallable<V> callable; + + public HttpRequestFutureTask( + final HttpUriRequest request, + final HttpRequestTaskCallable<V> httpCallable) { + super(httpCallable); + this.request = request; + this.callable = httpCallable; + } + + /* + * (non-Javadoc) + * @see java.util.concurrent.FutureTask#cancel(boolean) + */ + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + callable.cancel(); + if (mayInterruptIfRunning) { + request.abort(); + } + return super.cancel(mayInterruptIfRunning); + } + + /** + * @return the time in millis the task was scheduled. + */ + public long scheduledTime() { + return callable.getScheduled(); + } + + /** + * @return the time in millis the task was started. + */ + public long startedTime() { + return callable.getStarted(); + } + + /** + * @return the time in millis the task was finished/cancelled. + */ + public long endedTime() { + if (isDone()) { + return callable.getEnded(); + } else { + throw new IllegalStateException("Task is not done yet"); + } + } + + /** + * @return the time in millis it took to make the request (excluding the time it was + * scheduled to be executed). + */ + public long requestDuration() { + if (isDone()) { + return endedTime() - startedTime(); + } else { + throw new IllegalStateException("Task is not done yet"); + } + } + + /** + * @return the time in millis it took to execute the task from the moment it was scheduled. + */ + public long taskDuration() { + if (isDone()) { + return endedTime() - scheduledTime(); + } else { + throw new IllegalStateException("Task is not done yet"); + } + } + + @Override + public String toString() { + return request.getRequestLine().getUri(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java new file mode 100644 index 000000000..42e95f0aa --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java @@ -0,0 +1,119 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.client.HttpClient; +import ch.boye.httpclientandroidlib.client.ResponseHandler; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.concurrent.FutureCallback; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +class HttpRequestTaskCallable<V> implements Callable<V> { + + private final HttpUriRequest request; + private final HttpClient httpclient; + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + private final long scheduled = System.currentTimeMillis(); + private long started = -1; + private long ended = -1; + + private final HttpContext context; + private final ResponseHandler<V> responseHandler; + private final FutureCallback<V> callback; + + private final FutureRequestExecutionMetrics metrics; + + HttpRequestTaskCallable( + final HttpClient httpClient, + final HttpUriRequest request, + final HttpContext context, + final ResponseHandler<V> responseHandler, + final FutureCallback<V> callback, + final FutureRequestExecutionMetrics metrics) { + this.httpclient = httpClient; + this.responseHandler = responseHandler; + this.request = request; + this.context = context; + this.callback = callback; + this.metrics = metrics; + } + + public long getScheduled() { + return scheduled; + } + + public long getStarted() { + return started; + } + + public long getEnded() { + return ended; + } + + public V call() throws Exception { + if (!cancelled.get()) { + try { + metrics.getActiveConnections().incrementAndGet(); + started = System.currentTimeMillis(); + try { + metrics.getScheduledConnections().decrementAndGet(); + final V result = httpclient.execute(request, responseHandler, context); + ended = System.currentTimeMillis(); + metrics.getSuccessfulConnections().increment(started); + if (callback != null) { + callback.completed(result); + } + return result; + } catch (final Exception e) { + metrics.getFailedConnections().increment(started); + ended = System.currentTimeMillis(); + if (callback != null) { + callback.failed(e); + } + throw e; + } + } finally { + metrics.getRequests().increment(started); + metrics.getTasks().increment(started); + metrics.getActiveConnections().decrementAndGet(); + } + } else { + throw new IllegalStateException("call has been cancelled for request " + request.getURI()); + } + } + + public void cancel() { + cancelled.set(true); + if (callback != null) { + callback.cancelled(); + } + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java new file mode 100644 index 000000000..c445fe181 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java @@ -0,0 +1,242 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthSchemeProvider; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.CookieStore; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.Configurable; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.params.HttpClientParamConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpParamsNames; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Internal class. + * + * @since 4.3 + */ +@ThreadSafe +@SuppressWarnings("deprecation") +class InternalHttpClient extends CloseableHttpClient { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ClientExecChain execChain; + private final HttpClientConnectionManager connManager; + private final HttpRoutePlanner routePlanner; + private final Lookup<CookieSpecProvider> cookieSpecRegistry; + private final Lookup<AuthSchemeProvider> authSchemeRegistry; + private final CookieStore cookieStore; + private final CredentialsProvider credentialsProvider; + private final RequestConfig defaultConfig; + private final List<Closeable> closeables; + + public InternalHttpClient( + final ClientExecChain execChain, + final HttpClientConnectionManager connManager, + final HttpRoutePlanner routePlanner, + final Lookup<CookieSpecProvider> cookieSpecRegistry, + final Lookup<AuthSchemeProvider> authSchemeRegistry, + final CookieStore cookieStore, + final CredentialsProvider credentialsProvider, + final RequestConfig defaultConfig, + final List<Closeable> closeables) { + super(); + Args.notNull(execChain, "HTTP client exec chain"); + Args.notNull(connManager, "HTTP connection manager"); + Args.notNull(routePlanner, "HTTP route planner"); + this.execChain = execChain; + this.connManager = connManager; + this.routePlanner = routePlanner; + this.cookieSpecRegistry = cookieSpecRegistry; + this.authSchemeRegistry = authSchemeRegistry; + this.cookieStore = cookieStore; + this.credentialsProvider = credentialsProvider; + this.defaultConfig = defaultConfig; + this.closeables = closeables; + } + + private HttpRoute determineRoute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws HttpException { + HttpHost host = target; + if (host == null) { + host = (HttpHost) request.getParams().getParameter(ClientPNames.DEFAULT_HOST); + } + return this.routePlanner.determineRoute(host, request, context); + } + + private void setupContext(final HttpClientContext context) { + if (context.getAttribute(HttpClientContext.TARGET_AUTH_STATE) == null) { + context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, new AuthState()); + } + if (context.getAttribute(HttpClientContext.PROXY_AUTH_STATE) == null) { + context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, new AuthState()); + } + if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) { + context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) { + context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, this.cookieSpecRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) { + context.setAttribute(HttpClientContext.COOKIE_STORE, this.cookieStore); + } + if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) { + context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credentialsProvider); + } + if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { + context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.defaultConfig); + } + } + + @Override + protected CloseableHttpResponse doExecute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + Args.notNull(request, "HTTP request"); + HttpExecutionAware execAware = null; + if (request instanceof HttpExecutionAware) { + execAware = (HttpExecutionAware) request; + } + try { + final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request); + final HttpClientContext localcontext = HttpClientContext.adapt( + context != null ? context : new BasicHttpContext()); + RequestConfig config = null; + if (request instanceof Configurable) { + config = ((Configurable) request).getConfig(); + } + if (config == null) { + final HttpParams params = request.getParams(); + if (params instanceof HttpParamsNames) { + if (!((HttpParamsNames) params).getNames().isEmpty()) { + config = HttpClientParamConfig.getRequestConfig(params); + } + } else { + config = HttpClientParamConfig.getRequestConfig(params); + } + } + if (config != null) { + localcontext.setRequestConfig(config); + } + setupContext(localcontext); + final HttpRoute route = determineRoute(target, wrapper, localcontext); + return this.execChain.execute(route, wrapper, localcontext, execAware); + } catch (final HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } + + public void close() { + this.connManager.shutdown(); + if (this.closeables != null) { + for (final Closeable closeable: this.closeables) { + try { + closeable.close(); + } catch (final IOException ex) { + this.log.error(ex.getMessage(), ex); + } + } + } + } + + public HttpParams getParams() { + throw new UnsupportedOperationException(); + } + + public ClientConnectionManager getConnectionManager() { + + return new ClientConnectionManager() { + + public void shutdown() { + connManager.shutdown(); + } + + public ClientConnectionRequest requestConnection( + final HttpRoute route, final Object state) { + throw new UnsupportedOperationException(); + } + + public void releaseConnection( + final ManagedClientConnection conn, + final long validDuration, final TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + public SchemeRegistry getSchemeRegistry() { + throw new UnsupportedOperationException(); + } + + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + connManager.closeIdleConnections(idletime, tunit); + } + + public void closeExpiredConnections() { + connManager.closeExpiredConnections(); + } + + }; + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java new file mode 100644 index 000000000..47688e642 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java @@ -0,0 +1,65 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.methods.HttpGet; +import ch.boye.httpclientandroidlib.client.methods.HttpHead; +import ch.boye.httpclientandroidlib.client.methods.HttpPost; + +/** + * Lax {@link ch.boye.httpclientandroidlib.client.RedirectStrategy} implementation + * that automatically redirects all HEAD, GET and POST requests. + * This strategy relaxes restrictions on automatic redirection of + * POST methods imposed by the HTTP specification. + * + * @since 4.2 + */ +@Immutable +public class LaxRedirectStrategy extends DefaultRedirectStrategy { + + /** + * Redirectable methods. + */ + private static final String[] REDIRECT_METHODS = new String[] { + HttpGet.METHOD_NAME, + HttpPost.METHOD_NAME, + HttpHead.METHOD_NAME + }; + + @Override + protected boolean isRedirectable(final String method) { + for (final String m: REDIRECT_METHODS) { + if (m.equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java new file mode 100644 index 000000000..8b0c21127 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java @@ -0,0 +1,156 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.Configurable; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.execchain.MinimalClientExec; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Internal class. + * + * @since 4.3 + */ +@ThreadSafe +@SuppressWarnings("deprecation") +class MinimalHttpClient extends CloseableHttpClient { + + private final HttpClientConnectionManager connManager; + private final MinimalClientExec requestExecutor; + private final HttpParams params; + + public MinimalHttpClient( + final HttpClientConnectionManager connManager) { + super(); + this.connManager = Args.notNull(connManager, "HTTP connection manager"); + this.requestExecutor = new MinimalClientExec( + new HttpRequestExecutor(), + connManager, + DefaultConnectionReuseStrategy.INSTANCE, + DefaultConnectionKeepAliveStrategy.INSTANCE); + this.params = new BasicHttpParams(); + } + + @Override + protected CloseableHttpResponse doExecute( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws IOException, ClientProtocolException { + Args.notNull(target, "Target host"); + Args.notNull(request, "HTTP request"); + HttpExecutionAware execAware = null; + if (request instanceof HttpExecutionAware) { + execAware = (HttpExecutionAware) request; + } + try { + final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request); + final HttpClientContext localcontext = HttpClientContext.adapt( + context != null ? context : new BasicHttpContext()); + final HttpRoute route = new HttpRoute(target); + RequestConfig config = null; + if (request instanceof Configurable) { + config = ((Configurable) request).getConfig(); + } + if (config != null) { + localcontext.setRequestConfig(config); + } + return this.requestExecutor.execute(route, wrapper, localcontext, execAware); + } catch (final HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } + + public HttpParams getParams() { + return this.params; + } + + public void close() { + this.connManager.shutdown(); + } + + public ClientConnectionManager getConnectionManager() { + + return new ClientConnectionManager() { + + public void shutdown() { + connManager.shutdown(); + } + + public ClientConnectionRequest requestConnection( + final HttpRoute route, final Object state) { + throw new UnsupportedOperationException(); + } + + public void releaseConnection( + final ManagedClientConnection conn, + final long validDuration, final TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + public SchemeRegistry getSchemeRegistry() { + throw new UnsupportedOperationException(); + } + + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + connManager.closeIdleConnections(idletime, tunit); + } + + public void closeExpiredConnections() { + connManager.closeExpiredConnections(); + } + + }; + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java new file mode 100644 index 000000000..1e8eb3bde --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Noop implementation of {@link UserTokenHandler} that always returns <code>null</code>. + * + * @since 4.3 + */ +@Immutable +public class NoopUserTokenHandler implements UserTokenHandler { + + public static final NoopUserTokenHandler INSTANCE = new NoopUserTokenHandler(); + + public Object getUserToken(final HttpContext context) { + return null; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java new file mode 100644 index 000000000..8cad78d75 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; + +/** + * This is a {@link ConnectionBackoffStrategy} that never backs off, + * for compatibility with existing behavior. + * + * @since 4.2 + */ +public class NullBackoffStrategy implements ConnectionBackoffStrategy { + + public boolean shouldBackoff(final Throwable t) { + return false; + } + + public boolean shouldBackoff(final HttpResponse resp) { + return false; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java new file mode 100644 index 000000000..636143630 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationStrategy} implementation + * for proxy host authentication. + * + * @since 4.2 + */ +@Immutable +public class ProxyAuthenticationStrategy extends AuthenticationStrategyImpl { + + public static final ProxyAuthenticationStrategy INSTANCE = new ProxyAuthenticationStrategy(); + + public ProxyAuthenticationStrategy() { + super(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, AUTH.PROXY_AUTH); + } + + @Override + Collection<String> getPreferredAuthSchemes(final RequestConfig config) { + return config.getProxyPreferredAuthSchemes(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyClient.java new file mode 100644 index 000000000..e1211bc4b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/ProxyClient.java @@ -0,0 +1,254 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.io.IOException; +import java.net.Socket; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthSchemeRegistry; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.params.HttpClientParamConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.conn.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.RouteInfo.LayerType; +import ch.boye.httpclientandroidlib.conn.routing.RouteInfo.TunnelType; +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.auth.BasicSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.DigestSchemeFactory; +import ch.boye.httpclientandroidlib.impl.auth.HttpAuthenticator; +/* KerberosSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.auth.NTLMSchemeFactory; +/* SPNegoSchemeFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.impl.conn.ManagedHttpClientConnectionFactory; +import ch.boye.httpclientandroidlib.impl.execchain.TunnelRefusedException; +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParamConfig; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.BasicHttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.protocol.HttpCoreContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * ProxyClient can be used to establish a tunnel via an HTTP proxy. + */ +@SuppressWarnings("deprecation") +public class ProxyClient { + + private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; + private final ConnectionConfig connectionConfig; + private final RequestConfig requestConfig; + private final HttpProcessor httpProcessor; + private final HttpRequestExecutor requestExec; + private final ProxyAuthenticationStrategy proxyAuthStrategy; + private final HttpAuthenticator authenticator; + private final AuthState proxyAuthState; + private final AuthSchemeRegistry authSchemeRegistry; + private final ConnectionReuseStrategy reuseStrategy; + + /** + * @since 4.3 + */ + public ProxyClient( + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, + final ConnectionConfig connectionConfig, + final RequestConfig requestConfig) { + super(); + this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; + this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT; + this.httpProcessor = new ImmutableHttpProcessor( + new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent()); + this.requestExec = new HttpRequestExecutor(); + this.proxyAuthStrategy = new ProxyAuthenticationStrategy(); + this.authenticator = new HttpAuthenticator(); + this.proxyAuthState = new AuthState(); + this.authSchemeRegistry = new AuthSchemeRegistry(); + this.authSchemeRegistry.register(AuthSchemes.BASIC, new BasicSchemeFactory()); + this.authSchemeRegistry.register(AuthSchemes.DIGEST, new DigestSchemeFactory()); + this.authSchemeRegistry.register(AuthSchemes.NTLM, new NTLMSchemeFactory()); + /* SPNegoSchemeFactory removed by HttpClient for Android script. */ + /* KerberosSchemeFactory removed by HttpClient for Android script. */ + this.reuseStrategy = new DefaultConnectionReuseStrategy(); + } + + /** + * @deprecated (4.3) use {@link ProxyClient#ProxyClient(HttpConnectionFactory, ConnectionConfig, RequestConfig)} + */ + @Deprecated + public ProxyClient(final HttpParams params) { + this(null, + HttpParamConfig.getConnectionConfig(params), + HttpClientParamConfig.getRequestConfig(params)); + } + + /** + * @since 4.3 + */ + public ProxyClient(final RequestConfig requestConfig) { + this(null, null, requestConfig); + } + + public ProxyClient() { + this(null, null, null); + } + + /** + * @deprecated (4.3) do not use. + */ + @Deprecated + public HttpParams getParams() { + return new BasicHttpParams(); + } + + /** + * @deprecated (4.3) do not use. + */ + @Deprecated + public AuthSchemeRegistry getAuthSchemeRegistry() { + return this.authSchemeRegistry; + } + + public Socket tunnel( + final HttpHost proxy, + final HttpHost target, + final Credentials credentials) throws IOException, HttpException { + Args.notNull(proxy, "Proxy host"); + Args.notNull(target, "Target host"); + Args.notNull(credentials, "Credentials"); + HttpHost host = target; + if (host.getPort() <= 0) { + host = new HttpHost(host.getHostName(), 80, host.getSchemeName()); + } + final HttpRoute route = new HttpRoute( + host, + this.requestConfig.getLocalAddress(), + proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN); + + final ManagedHttpClientConnection conn = this.connFactory.create( + route, this.connectionConfig); + final HttpContext context = new BasicHttpContext(); + HttpResponse response; + + final HttpRequest connect = new BasicHttpRequest( + "CONNECT", host.toHostString(), HttpVersion.HTTP_1_1); + + final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(proxy), credentials); + + // Populate the execution context + context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target); + context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn); + context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect); + context.setAttribute(HttpClientContext.HTTP_ROUTE, route); + context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, this.proxyAuthState); + context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider); + context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry); + context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig); + + this.requestExec.preProcess(connect, this.httpProcessor, context); + + for (;;) { + if (!conn.isOpen()) { + final Socket socket = new Socket(proxy.getHostName(), proxy.getPort()); + conn.bind(socket); + } + + this.authenticator.generateAuthResponse(connect, this.proxyAuthState, context); + + response = this.requestExec.execute(connect, conn, context); + + final int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + if (this.authenticator.isAuthenticationRequested(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + if (this.authenticator.handleAuthChallenge(proxy, response, + this.proxyAuthStrategy, this.proxyAuthState, context)) { + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + // Consume response content + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + } else { + conn.close(); + } + // discard previous auth header + connect.removeHeaders(AUTH.PROXY_AUTH_RESP); + } else { + break; + } + } else { + break; + } + } + + final int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + final HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + conn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + return conn.getSocket(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java new file mode 100644 index 000000000..872dc167f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java @@ -0,0 +1,227 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +/** + * This class represents a collection of {@link java.net.URI}s used + * as redirect locations. + * + * @since 4.0 + */ +@NotThreadSafe // HashSet/ArrayList are not synch. +public class RedirectLocations extends AbstractList<Object> { + + private final Set<URI> unique; + private final List<URI> all; + + public RedirectLocations() { + super(); + this.unique = new HashSet<URI>(); + this.all = new ArrayList<URI>(); + } + + /** + * Test if the URI is present in the collection. + */ + public boolean contains(final URI uri) { + return this.unique.contains(uri); + } + + /** + * Adds a new URI to the collection. + */ + public void add(final URI uri) { + this.unique.add(uri); + this.all.add(uri); + } + + /** + * Removes a URI from the collection. + */ + public boolean remove(final URI uri) { + final boolean removed = this.unique.remove(uri); + if (removed) { + final Iterator<URI> it = this.all.iterator(); + while (it.hasNext()) { + final URI current = it.next(); + if (current.equals(uri)) { + it.remove(); + } + } + } + return removed; + } + + /** + * Returns all redirect {@link URI}s in the order they were added to the collection. + * + * @return list of all URIs + * + * @since 4.1 + */ + public List<URI> getAll() { + return new ArrayList<URI>(this.all); + } + + /** + * Returns the URI at the specified position in this list. + * + * @param index + * index of the location to return + * @return the URI at the specified position in this list + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index >= size()</tt>) + * @since 4.3 + */ + @Override + public URI get(final int index) { + return this.all.get(index); + } + + /** + * Returns the number of elements in this list. If this list contains more + * than <tt>Integer.MAX_VALUE</tt> elements, returns + * <tt>Integer.MAX_VALUE</tt>. + * + * @return the number of elements in this list + * @since 4.3 + */ + @Override + public int size() { + return this.all.size(); + } + + /** + * Replaces the URI at the specified position in this list with the + * specified element (must be a URI). + * + * @param index + * index of the element to replace + * @param element + * URI to be stored at the specified position + * @return the URI previously at the specified position + * @throws UnsupportedOperationException + * if the <tt>set</tt> operation is not supported by this list + * @throws ClassCastException + * if the element is not a {@link URI} + * @throws NullPointerException + * if the specified element is null and this list does not + * permit null elements + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index >= size()</tt>) + * @since 4.3 + */ + @Override + public Object set(final int index, final Object element) { + final URI removed = this.all.set(index, (URI) element); + this.unique.remove(removed); + this.unique.add((URI) element); + if (this.all.size() != this.unique.size()) { + this.unique.addAll(this.all); + } + return removed; + } + + /** + * Inserts the specified element at the specified position in this list + * (must be a URI). Shifts the URI currently at that position (if any) and + * any subsequent URIs to the right (adds one to their indices). + * + * @param index + * index at which the specified element is to be inserted + * @param element + * URI to be inserted + * @throws UnsupportedOperationException + * if the <tt>add</tt> operation is not supported by this list + * @throws ClassCastException + * if the element is not a {@link URI} + * @throws NullPointerException + * if the specified element is null and this list does not + * permit null elements + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index > size()</tt>) + * @since 4.3 + */ + @Override + public void add(final int index, final Object element) { + this.all.add(index, (URI) element); + this.unique.add((URI) element); + } + + /** + * Removes the URI at the specified position in this list. Shifts any + * subsequent URIs to the left (subtracts one from their indices). Returns + * the URI that was removed from the list. + * + * @param index + * the index of the URI to be removed + * @return the URI previously at the specified position + * @throws IndexOutOfBoundsException + * if the index is out of range ( + * <tt>index < 0 || index >= size()</tt>) + * @since 4.3 + */ + @Override + public URI remove(final int index) { + final URI removed = this.all.remove(index); + this.unique.remove(removed); + if (this.all.size() != this.unique.size()) { + this.unique.addAll(this.all); + } + return removed; + } + + /** + * Returns <tt>true</tt> if this collection contains the specified element. + * More formally, returns <tt>true</tt> if and only if this collection + * contains at least one element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt>. + * + * @param o element whose presence in this collection is to be tested + * @return <tt>true</tt> if this collection contains the specified + * element + */ + @Override + public boolean contains(final Object o) { + return this.unique.contains(o); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java new file mode 100644 index 000000000..3577b61a6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java @@ -0,0 +1,164 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.message.AbstractHttpMessage; +import ch.boye.httpclientandroidlib.message.BasicRequestLine; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * A wrapper class for {@link HttpRequest}s that can be used to change + * properties of the current request without modifying the original + * object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @since 4.0 + * + * @deprecated (4.3) do not use. + */ +@NotThreadSafe +@Deprecated +public class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest { + + private final HttpRequest original; + + private URI uri; + private String method; + private ProtocolVersion version; + private int execCount; + + public RequestWrapper(final HttpRequest request) throws ProtocolException { + super(); + Args.notNull(request, "HTTP request"); + this.original = request; + setParams(request.getParams()); + setHeaders(request.getAllHeaders()); + // Make a copy of the original URI + if (request instanceof HttpUriRequest) { + this.uri = ((HttpUriRequest) request).getURI(); + this.method = ((HttpUriRequest) request).getMethod(); + this.version = null; + } else { + final RequestLine requestLine = request.getRequestLine(); + try { + this.uri = new URI(requestLine.getUri()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + + requestLine.getUri(), ex); + } + this.method = requestLine.getMethod(); + this.version = request.getProtocolVersion(); + } + this.execCount = 0; + } + + public void resetHeaders() { + // Make a copy of original headers + this.headergroup.clear(); + setHeaders(this.original.getAllHeaders()); + } + + public String getMethod() { + return this.method; + } + + public void setMethod(final String method) { + Args.notNull(method, "Method name"); + this.method = method; + } + + public ProtocolVersion getProtocolVersion() { + if (this.version == null) { + this.version = HttpProtocolParams.getVersion(getParams()); + } + return this.version; + } + + public void setProtocolVersion(final ProtocolVersion version) { + this.version = version; + } + + + public URI getURI() { + return this.uri; + } + + public void setURI(final URI uri) { + this.uri = uri; + } + + public RequestLine getRequestLine() { + final String method = getMethod(); + final ProtocolVersion ver = getProtocolVersion(); + String uritext = null; + if (uri != null) { + uritext = uri.toASCIIString(); + } + if (uritext == null || uritext.length() == 0) { + uritext = "/"; + } + return new BasicRequestLine(method, uritext, ver); + } + + public void abort() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean isAborted() { + return false; + } + + public HttpRequest getOriginal() { + return this.original; + } + + public boolean isRepeatable() { + return true; + } + + public int getExecCount() { + return this.execCount; + } + + public void incrementExecCount() { + this.execCount++; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java new file mode 100644 index 000000000..2e5ff76e9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * A request with the route along which it should be sent. + * + * @since 4.0 + * + * @deprecated (4.3) do not use. + */ +@Deprecated +@NotThreadSafe // RequestWrapper is @NotThreadSafe +public class RoutedRequest { + + protected final RequestWrapper request; // @NotThreadSafe + protected final HttpRoute route; // @Immutable + + /** + * Creates a new routed request. + * + * @param req the request + * @param route the route + */ + public RoutedRequest(final RequestWrapper req, final HttpRoute route) { + super(); + this.request = req; + this.route = route; + } + + public final RequestWrapper getRequest() { + return request; + } + + public final HttpRoute getRoute() { + return route; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java new file mode 100644 index 000000000..ec0ce7e00 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java @@ -0,0 +1,80 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * {@link ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler} which assumes + * that all requested HTTP methods which should be idempotent according + * to RFC-2616 are in fact idempotent and can be retried. + * <p/> + * According to RFC-2616 section 9.1.2 the idempotent HTTP methods are: + * GET, HEAD, PUT, DELETE, OPTIONS, and TRACE + * + * @since 4.2 + */ +@Immutable +public class StandardHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler { + + private final Map<String, Boolean> idempotentMethods; + + /** + * Default constructor + */ + public StandardHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) { + super(retryCount, requestSentRetryEnabled); + this.idempotentMethods = new ConcurrentHashMap<String, Boolean>(); + this.idempotentMethods.put("GET", Boolean.TRUE); + this.idempotentMethods.put("HEAD", Boolean.TRUE); + this.idempotentMethods.put("PUT", Boolean.TRUE); + this.idempotentMethods.put("DELETE", Boolean.TRUE); + this.idempotentMethods.put("OPTIONS", Boolean.TRUE); + this.idempotentMethods.put("TRACE", Boolean.TRUE); + } + + /** + * Default constructor + */ + public StandardHttpRequestRetryHandler() { + this(3, false); + } + + @Override + protected boolean handleAsIdempotent(final HttpRequest request) { + final String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH); + final Boolean b = this.idempotentMethods.get(method); + return b != null && b.booleanValue(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemClock.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemClock.java new file mode 100644 index 000000000..b037af841 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemClock.java @@ -0,0 +1,40 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +/** + * The actual system clock. + * + * @since 4.2 + */ +class SystemClock implements Clock { + + public long getCurrentTime() { + return System.currentTimeMillis(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java new file mode 100644 index 000000000..1327b71dc --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java @@ -0,0 +1,145 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.Credentials; +import ch.boye.httpclientandroidlib.auth.NTCredentials; +import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.config.AuthSchemes; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Implementation of {@link CredentialsProvider} backed by standard + * JRE {@link Authenticator}. + * + * @since 4.3 + */ +@ThreadSafe +public class SystemDefaultCredentialsProvider implements CredentialsProvider { + + private static final Map<String, String> SCHEME_MAP; + + static { + SCHEME_MAP = new ConcurrentHashMap<String, String>(); + SCHEME_MAP.put(AuthSchemes.BASIC.toUpperCase(Locale.ENGLISH), "Basic"); + SCHEME_MAP.put(AuthSchemes.DIGEST.toUpperCase(Locale.ENGLISH), "Digest"); + SCHEME_MAP.put(AuthSchemes.NTLM.toUpperCase(Locale.ENGLISH), "NTLM"); + SCHEME_MAP.put(AuthSchemes.SPNEGO.toUpperCase(Locale.ENGLISH), "SPNEGO"); + SCHEME_MAP.put(AuthSchemes.KERBEROS.toUpperCase(Locale.ENGLISH), "Kerberos"); + } + + private static String translateScheme(final String key) { + if (key == null) { + return null; + } + final String s = SCHEME_MAP.get(key); + return s != null ? s : key; + } + + private final BasicCredentialsProvider internal; + + /** + * Default constructor. + */ + public SystemDefaultCredentialsProvider() { + super(); + this.internal = new BasicCredentialsProvider(); + } + + public void setCredentials(final AuthScope authscope, final Credentials credentials) { + internal.setCredentials(authscope, credentials); + } + + private static PasswordAuthentication getSystemCreds( + final AuthScope authscope, + final Authenticator.RequestorType requestorType) { + final String hostname = authscope.getHost(); + final int port = authscope.getPort(); + final String protocol = port == 443 ? "https" : "http"; + return Authenticator.requestPasswordAuthentication( + hostname, + null, + port, + protocol, + null, + translateScheme(authscope.getScheme()), + null, + requestorType); + } + + public Credentials getCredentials(final AuthScope authscope) { + Args.notNull(authscope, "Auth scope"); + final Credentials localcreds = internal.getCredentials(authscope); + if (localcreds != null) { + return localcreds; + } + if (authscope.getHost() != null) { + PasswordAuthentication systemcreds = getSystemCreds( + authscope, Authenticator.RequestorType.SERVER); + if (systemcreds == null) { + systemcreds = getSystemCreds( + authscope, Authenticator.RequestorType.PROXY); + } + if (systemcreds != null) { + final String domain = System.getProperty("http.auth.ntlm.domain"); + if (domain != null) { + return new NTCredentials( + systemcreds.getUserName(), + new String(systemcreds.getPassword()), + null, domain); + } else { + if (AuthSchemes.NTLM.equalsIgnoreCase(authscope.getScheme())) { + // Domian may be specified in a fully qualified user name + return new NTCredentials( + systemcreds.getUserName(), + new String(systemcreds.getPassword()), + null, null); + } else { + return new UsernamePasswordCredentials( + systemcreds.getUserName(), + new String(systemcreds.getPassword())); + } + } + } + } + return null; + } + + public void clear() { + internal.clear(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java new file mode 100644 index 000000000..0806537ed --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java @@ -0,0 +1,149 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.net.ProxySelector; + +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.impl.DefaultConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.NoConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.impl.conn.PoolingClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.ProxySelectorRoutePlanner; +import ch.boye.httpclientandroidlib.impl.conn.SchemeRegistryFactory; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * An extension of {@link DefaultHttpClient} pre-configured using system properties. + * <p> + * The following system properties are taken into account by this class: + * <ul> + * <li>ssl.TrustManagerFactory.algorithm</li> + * <li>javax.net.ssl.trustStoreType</li> + * <li>javax.net.ssl.trustStore</li> + * <li>javax.net.ssl.trustStoreProvider</li> + * <li>javax.net.ssl.trustStorePassword</li> + * <li>java.home</li> + * <li>ssl.KeyManagerFactory.algorithm</li> + * <li>javax.net.ssl.keyStoreType</li> + * <li>javax.net.ssl.keyStore</li> + * <li>javax.net.ssl.keyStoreProvider</li> + * <li>javax.net.ssl.keyStorePassword</li> + * <li>http.proxyHost</li> + * <li>http.proxyPort</li> + * <li>http.nonProxyHosts</li> + * <li>http.keepAlive</li> + * <li>http.maxConnections</li> + * </ul> + * <p> + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#PROTOCOL_VERSION}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#USER_AGENT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li> + * <li>{@link ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li> + * <li>{@link ch.boye.httpclientandroidlib.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#COOKIE_POLICY}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#HANDLE_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#MAX_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#VIRTUAL_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HOST}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#DEFAULT_HEADERS}</li> + * <li>{@link ch.boye.httpclientandroidlib.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li> + * </ul> + * </p> + * + * @since 4.2 + * + * @deprecated (4.3) use {@link HttpClientBuilder} + */ +@ThreadSafe +@Deprecated +public class SystemDefaultHttpClient extends DefaultHttpClient { + + public SystemDefaultHttpClient(final HttpParams params) { + super(null, params); + } + + public SystemDefaultHttpClient() { + super(null, null); + } + + @Override + protected ClientConnectionManager createClientConnectionManager() { + final PoolingClientConnectionManager connmgr = new PoolingClientConnectionManager( + SchemeRegistryFactory.createSystemDefault()); + String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + s = System.getProperty("http.maxConnections", "5"); + final int max = Integer.parseInt(s); + connmgr.setDefaultMaxPerRoute(max); + connmgr.setMaxTotal(2 * max); + } + return connmgr; + } + + @Override + protected HttpRoutePlanner createHttpRoutePlanner() { + return new ProxySelectorRoutePlanner(getConnectionManager().getSchemeRegistry(), + ProxySelector.getDefault()); + } + + @Override + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + final String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + return new DefaultConnectionReuseStrategy(); + } else { + return new NoConnectionReuseStrategy(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java new file mode 100644 index 000000000..486167dc1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; + +/** + * Default {@link ch.boye.httpclientandroidlib.client.AuthenticationStrategy} implementation + * for proxy host authentication. + * + * @since 4.2 + */ +@Immutable +public class TargetAuthenticationStrategy extends AuthenticationStrategyImpl { + + public static final TargetAuthenticationStrategy INSTANCE = new TargetAuthenticationStrategy(); + + public TargetAuthenticationStrategy() { + super(HttpStatus.SC_UNAUTHORIZED, AUTH.WWW_AUTH); + } + + @Override + Collection<String> getPreferredAuthSchemes(final RequestConfig config) { + return config.getTargetPreferredAuthSchemes(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java new file mode 100644 index 000000000..97edac1f0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java @@ -0,0 +1,58 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Signals that the tunnel request was rejected by the proxy host. + * + * @since 4.0 + * + * @deprecated (4.3) reserved for internal use. + */ +@Deprecated +@Immutable +public class TunnelRefusedException extends HttpException { + + private static final long serialVersionUID = -8646722842745617323L; + + private final HttpResponse response; + + public TunnelRefusedException(final String message, final HttpResponse response) { + super(message); + this.response = response; + } + + public HttpResponse getResponse() { + return this.response; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java new file mode 100644 index 000000000..60ad6ca1f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java @@ -0,0 +1,178 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * Class used to represent an asynchronous revalidation event, such as with + * "stale-while-revalidate" + */ +class AsynchronousValidationRequest implements Runnable { + private final AsynchronousValidator parent; + private final CachingExec cachingExec; + private final HttpRoute route; + private final HttpRequestWrapper request; + private final HttpClientContext context; + private final HttpExecutionAware execAware; + private final HttpCacheEntry cacheEntry; + private final String identifier; + private final int consecutiveFailedAttempts; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Used internally by {@link AsynchronousValidator} to schedule a + * revalidation. + * @param request + * @param context + * @param cacheEntry + * @param identifier + * @param consecutiveFailedAttempts + */ + AsynchronousValidationRequest( + final AsynchronousValidator parent, + final CachingExec cachingExec, + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry cacheEntry, + final String identifier, + final int consecutiveFailedAttempts) { + this.parent = parent; + this.cachingExec = cachingExec; + this.route = route; + this.request = request; + this.context = context; + this.execAware = execAware; + this.cacheEntry = cacheEntry; + this.identifier = identifier; + this.consecutiveFailedAttempts = consecutiveFailedAttempts; + } + + public void run() { + try { + if (revalidateCacheEntry()) { + parent.jobSuccessful(identifier); + } else { + parent.jobFailed(identifier); + } + } finally { + parent.markComplete(identifier); + } + } + + /** + * Revalidate the cache entry and return if the operation was successful. + * Success means a connection to the server was established and replay did + * not indicate a server error. + * @return <code>true</code> if the cache entry was successfully validated; + * otherwise <code>false</code> + */ + protected boolean revalidateCacheEntry() { + try { + final CloseableHttpResponse httpResponse = cachingExec.revalidateCacheEntry(route, request, context, execAware, cacheEntry); + try { + final int statusCode = httpResponse.getStatusLine().getStatusCode(); + return isNotServerError(statusCode) && isNotStale(httpResponse); + } finally { + httpResponse.close(); + } + } catch (final IOException ioe) { + log.debug("Asynchronous revalidation failed due to I/O error", ioe); + return false; + } catch (final HttpException pe) { + log.error("HTTP protocol exception during asynchronous revalidation", pe); + return false; + } catch (final RuntimeException re) { + log.error("RuntimeException thrown during asynchronous revalidation: " + re); + return false; + } + } + + /** + * Return whether the status code indicates a server error or not. + * @param statusCode the status code to be checked + * @return if the status code indicates a server error or not + */ + private boolean isNotServerError(final int statusCode) { + return statusCode < 500; + } + + /** + * Try to detect if the returned response is generated from a stale cache entry. + * @param httpResponse the response to be checked + * @return whether the response is stale or not + */ + private boolean isNotStale(final HttpResponse httpResponse) { + final Header[] warnings = httpResponse.getHeaders(HeaderConstants.WARNING); + if (warnings != null) + { + for (final Header warning : warnings) + { + /** + * warn-codes + * 110 = Response is stale + * 111 = Revalidation failed + */ + final String warningValue = warning.getValue(); + if (warningValue.startsWith("110") || warningValue.startsWith("111")) + { + return false; + } + } + } + return true; + } + + String getIdentifier() { + return identifier; + } + + /** + * The number of consecutively failed revalidation attempts. + * @return the number of consecutively failed revalidation attempts. + */ + public int getConsecutiveFailedAttempts() { + return consecutiveFailedAttempts; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java new file mode 100644 index 000000000..05765a236 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java @@ -0,0 +1,150 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.RejectedExecutionException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * Class used for asynchronous revalidations to be used when the "stale- + * while-revalidate" directive is present + */ +class AsynchronousValidator implements Closeable { + private final SchedulingStrategy schedulingStrategy; + private final Set<String> queued; + private final CacheKeyGenerator cacheKeyGenerator; + private final FailureCache failureCache; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Create AsynchronousValidator which will make revalidation requests + * using an {@link ImmediateSchedulingStrategy}. Its thread + * pool will be configured according to the given {@link CacheConfig}. + * @param config specifies thread pool settings. See + * {@link CacheConfig#getAsynchronousWorkersMax()}, + * {@link CacheConfig#getAsynchronousWorkersCore()}, + * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()}, + * and {@link CacheConfig#getRevalidationQueueSize()}. + */ + public AsynchronousValidator(final CacheConfig config) { + this(new ImmediateSchedulingStrategy(config)); + } + + /** + * Create AsynchronousValidator which will make revalidation requests + * using the supplied {@link SchedulingStrategy}. Closing the validator + * will also close the given schedulingStrategy. + * @param schedulingStrategy used to maintain a pool of worker threads and + * schedules when requests are executed + */ + AsynchronousValidator(final SchedulingStrategy schedulingStrategy) { + this.schedulingStrategy = schedulingStrategy; + this.queued = new HashSet<String>(); + this.cacheKeyGenerator = new CacheKeyGenerator(); + this.failureCache = new DefaultFailureCache(); + } + + public void close() throws IOException { + schedulingStrategy.close(); + } + + /** + * Schedules an asynchronous revalidation + */ + public synchronized void revalidateCacheEntry( + final CachingExec cachingExec, + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry entry) { + // getVariantURI will fall back on getURI if no variants exist + final String uri = cacheKeyGenerator.getVariantURI(context.getTargetHost(), request, entry); + + if (!queued.contains(uri)) { + final int consecutiveFailedAttempts = failureCache.getErrorCount(uri); + final AsynchronousValidationRequest revalidationRequest = + new AsynchronousValidationRequest( + this, cachingExec, route, request, context, execAware, entry, uri, consecutiveFailedAttempts); + + try { + schedulingStrategy.schedule(revalidationRequest); + queued.add(uri); + } catch (final RejectedExecutionException ree) { + log.debug("Revalidation for [" + uri + "] not scheduled: " + ree); + } + } + } + + /** + * Removes an identifier from the internal list of revalidation jobs in + * progress. This is meant to be called by + * {@link AsynchronousValidationRequest#run()} once the revalidation is + * complete, using the identifier passed in during constructions. + * @param identifier + */ + synchronized void markComplete(final String identifier) { + queued.remove(identifier); + } + + /** + * The revalidation job was successful thus the number of consecutive + * failed attempts will be reset to zero. Should be called by + * {@link AsynchronousValidationRequest#run()}. + * @param identifier the revalidation job's unique identifier + */ + void jobSuccessful(final String identifier) { + failureCache.resetErrorCount(identifier); + } + + /** + * The revalidation job did fail and thus the number of consecutive failed + * attempts will be increased. Should be called by + * {@link AsynchronousValidationRequest#run()}. + * @param identifier the revalidation job's unique identifier + */ + void jobFailed(final String identifier) { + failureCache.increaseErrorCount(identifier); + } + + Set<String> getScheduledIdentifiers() { + return Collections.unmodifiableSet(queued); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java new file mode 100644 index 000000000..4347e173d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java @@ -0,0 +1,376 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateException; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.entity.ByteArrayEntity; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +class BasicHttpCache implements HttpCache { + private static final Set<String> safeRequestMethods = new HashSet<String>( + Arrays.asList(HeaderConstants.HEAD_METHOD, + HeaderConstants.GET_METHOD, HeaderConstants.OPTIONS_METHOD, + HeaderConstants.TRACE_METHOD)); + + private final CacheKeyGenerator uriExtractor; + private final ResourceFactory resourceFactory; + private final long maxObjectSizeBytes; + private final CacheEntryUpdater cacheEntryUpdater; + private final CachedHttpResponseGenerator responseGenerator; + private final HttpCacheInvalidator cacheInvalidator; + private final HttpCacheStorage storage; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + public BasicHttpCache( + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config, + final CacheKeyGenerator uriExtractor, + final HttpCacheInvalidator cacheInvalidator) { + this.resourceFactory = resourceFactory; + this.uriExtractor = uriExtractor; + this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory); + this.maxObjectSizeBytes = config.getMaxObjectSize(); + this.responseGenerator = new CachedHttpResponseGenerator(); + this.storage = storage; + this.cacheInvalidator = cacheInvalidator; + } + + public BasicHttpCache( + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config, + final CacheKeyGenerator uriExtractor) { + this( resourceFactory, storage, config, uriExtractor, + new CacheInvalidator(uriExtractor, storage)); + } + + public BasicHttpCache( + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config) { + this( resourceFactory, storage, config, new CacheKeyGenerator()); + } + + public BasicHttpCache(final CacheConfig config) { + this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config); + } + + public BasicHttpCache() { + this(CacheConfig.DEFAULT); + } + + public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) + throws IOException { + if (!safeRequestMethods.contains(request.getRequestLine().getMethod())) { + final String uri = uriExtractor.getURI(host, request); + storage.removeEntry(uri); + } + } + + public void flushInvalidatedCacheEntriesFor(final HttpHost host, final HttpRequest request, final HttpResponse response) { + if (!safeRequestMethods.contains(request.getRequestLine().getMethod())) { + cacheInvalidator.flushInvalidatedCacheEntries(host, request, response); + } + } + + void storeInCache( + final HttpHost target, final HttpRequest request, final HttpCacheEntry entry) throws IOException { + if (entry.hasVariants()) { + storeVariantEntry(target, request, entry); + } else { + storeNonVariantEntry(target, request, entry); + } + } + + void storeNonVariantEntry( + final HttpHost target, final HttpRequest req, final HttpCacheEntry entry) throws IOException { + final String uri = uriExtractor.getURI(target, req); + storage.putEntry(uri, entry); + } + + void storeVariantEntry( + final HttpHost target, + final HttpRequest req, + final HttpCacheEntry entry) throws IOException { + final String parentURI = uriExtractor.getURI(target, req); + final String variantURI = uriExtractor.getVariantURI(target, req, entry); + storage.putEntry(variantURI, entry); + + final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { + + public HttpCacheEntry update(final HttpCacheEntry existing) throws IOException { + return doGetUpdatedParentEntry( + req.getRequestLine().getUri(), existing, entry, + uriExtractor.getVariantKey(req, entry), + variantURI); + } + + }; + + try { + storage.updateEntry(parentURI, callback); + } catch (final HttpCacheUpdateException e) { + log.warn("Could not update key [" + parentURI + "]", e); + } + } + + public void reuseVariantEntryFor(final HttpHost target, final HttpRequest req, + final Variant variant) throws IOException { + final String parentCacheKey = uriExtractor.getURI(target, req); + final HttpCacheEntry entry = variant.getEntry(); + final String variantKey = uriExtractor.getVariantKey(req, entry); + final String variantCacheKey = variant.getCacheKey(); + + final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() { + public HttpCacheEntry update(final HttpCacheEntry existing) + throws IOException { + return doGetUpdatedParentEntry(req.getRequestLine().getUri(), + existing, entry, variantKey, variantCacheKey); + } + }; + + try { + storage.updateEntry(parentCacheKey, callback); + } catch (final HttpCacheUpdateException e) { + log.warn("Could not update key [" + parentCacheKey + "]", e); + } + } + + boolean isIncompleteResponse(final HttpResponse resp, final Resource resource) { + final int status = resp.getStatusLine().getStatusCode(); + if (status != HttpStatus.SC_OK + && status != HttpStatus.SC_PARTIAL_CONTENT) { + return false; + } + final Header hdr = resp.getFirstHeader(HTTP.CONTENT_LEN); + if (hdr == null) { + return false; + } + final int contentLength; + try { + contentLength = Integer.parseInt(hdr.getValue()); + } catch (final NumberFormatException nfe) { + return false; + } + return (resource.length() < contentLength); + } + + CloseableHttpResponse generateIncompleteResponseError( + final HttpResponse response, final Resource resource) { + final int contentLength = Integer.parseInt(response.getFirstHeader(HTTP.CONTENT_LEN).getValue()); + final HttpResponse error = + new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); + error.setHeader("Content-Type","text/plain;charset=UTF-8"); + final String msg = String.format("Received incomplete response " + + "with Content-Length %d but actual body length %d", + contentLength, resource.length()); + final byte[] msgBytes = msg.getBytes(); + error.setHeader("Content-Length", Integer.toString(msgBytes.length)); + error.setEntity(new ByteArrayEntity(msgBytes)); + return Proxies.enhanceResponse(error); + } + + HttpCacheEntry doGetUpdatedParentEntry( + final String requestId, + final HttpCacheEntry existing, + final HttpCacheEntry entry, + final String variantKey, + final String variantCacheKey) throws IOException { + HttpCacheEntry src = existing; + if (src == null) { + src = entry; + } + + Resource resource = null; + if (src.getResource() != null) { + resource = resourceFactory.copy(requestId, src.getResource()); + } + final Map<String,String> variantMap = new HashMap<String,String>(src.getVariantMap()); + variantMap.put(variantKey, variantCacheKey); + return new HttpCacheEntry( + src.getRequestDate(), + src.getResponseDate(), + src.getStatusLine(), + src.getAllHeaders(), + resource, + variantMap); + } + + public HttpCacheEntry updateCacheEntry(final HttpHost target, final HttpRequest request, + final HttpCacheEntry stale, final HttpResponse originResponse, + final Date requestSent, final Date responseReceived) throws IOException { + final HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry( + request.getRequestLine().getUri(), + stale, + requestSent, + responseReceived, + originResponse); + storeInCache(target, request, updatedEntry); + return updatedEntry; + } + + public HttpCacheEntry updateVariantCacheEntry(final HttpHost target, final HttpRequest request, + final HttpCacheEntry stale, final HttpResponse originResponse, + final Date requestSent, final Date responseReceived, final String cacheKey) throws IOException { + final HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry( + request.getRequestLine().getUri(), + stale, + requestSent, + responseReceived, + originResponse); + storage.putEntry(cacheKey, updatedEntry); + return updatedEntry; + } + + public HttpResponse cacheAndReturnResponse(final HttpHost host, final HttpRequest request, + final HttpResponse originResponse, final Date requestSent, final Date responseReceived) + throws IOException { + return cacheAndReturnResponse(host, request, + Proxies.enhanceResponse(originResponse), requestSent, + responseReceived); + } + + public CloseableHttpResponse cacheAndReturnResponse( + final HttpHost host, + final HttpRequest request, + final CloseableHttpResponse originResponse, + final Date requestSent, + final Date responseReceived) throws IOException { + + boolean closeOriginResponse = true; + final SizeLimitedResponseReader responseReader = getResponseReader(request, originResponse); + try { + responseReader.readResponse(); + + if (responseReader.isLimitReached()) { + closeOriginResponse = false; + return responseReader.getReconstructedResponse(); + } + + final Resource resource = responseReader.getResource(); + if (isIncompleteResponse(originResponse, resource)) { + return generateIncompleteResponseError(originResponse, resource); + } + + final HttpCacheEntry entry = new HttpCacheEntry( + requestSent, + responseReceived, + originResponse.getStatusLine(), + originResponse.getAllHeaders(), + resource); + storeInCache(host, request, entry); + return responseGenerator.generateResponse(entry); + } finally { + if (closeOriginResponse) { + originResponse.close(); + } + } + } + + SizeLimitedResponseReader getResponseReader(final HttpRequest request, + final CloseableHttpResponse backEndResponse) { + return new SizeLimitedResponseReader( + resourceFactory, maxObjectSizeBytes, request, backEndResponse); + } + + public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) throws IOException { + final HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request)); + if (root == null) { + return null; + } + if (!root.hasVariants()) { + return root; + } + final String variantCacheKey = root.getVariantMap().get(uriExtractor.getVariantKey(request, root)); + if (variantCacheKey == null) { + return null; + } + return storage.getEntry(variantCacheKey); + } + + public void flushInvalidatedCacheEntriesFor(final HttpHost host, + final HttpRequest request) throws IOException { + cacheInvalidator.flushInvalidatedCacheEntries(host, request); + } + + public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) + throws IOException { + final Map<String,Variant> variants = new HashMap<String,Variant>(); + final HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request)); + if (root == null || !root.hasVariants()) { + return variants; + } + for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) { + final String variantKey = variant.getKey(); + final String variantCacheKey = variant.getValue(); + addVariantWithEtag(variantKey, variantCacheKey, variants); + } + return variants; + } + + private void addVariantWithEtag(final String variantKey, + final String variantCacheKey, final Map<String, Variant> variants) + throws IOException { + final HttpCacheEntry entry = storage.getEntry(variantCacheKey); + if (entry == null) { + return; + } + final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG); + if (etagHeader == null) { + return; + } + variants.put(etagHeader.getValue(), new Variant(variantKey, variantCacheKey, entry)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java new file mode 100644 index 000000000..225730858 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java @@ -0,0 +1,96 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback; + +/** + * Basic {@link HttpCacheStorage} implementation backed by an instance of + * {@link java.util.LinkedHashMap}. In other words, cache entries and + * the cached response bodies are held in-memory. This cache does NOT + * deallocate resources associated with the cache entries; it is intended + * for use with {@link HeapResource} and similar. This is the default cache + * storage backend used by {@link CachingHttpClients}. + * + * @since 4.1 + */ +@ThreadSafe +public class BasicHttpCacheStorage implements HttpCacheStorage { + + private final CacheMap entries; + + public BasicHttpCacheStorage(final CacheConfig config) { + super(); + this.entries = new CacheMap(config.getMaxCacheEntries()); + } + + /** + * Places a HttpCacheEntry in the cache + * + * @param url + * Url to use as the cache key + * @param entry + * HttpCacheEntry to place in the cache + */ + public synchronized void putEntry(final String url, final HttpCacheEntry entry) throws IOException { + entries.put(url, entry); + } + + /** + * Gets an entry from the cache, if it exists + * + * @param url + * Url that is the cache key + * @return HttpCacheEntry if one exists, or null for cache miss + */ + public synchronized HttpCacheEntry getEntry(final String url) throws IOException { + return entries.get(url); + } + + /** + * Removes a HttpCacheEntry from the cache + * + * @param url + * Url that is the cache key + */ + public synchronized void removeEntry(final String url) throws IOException { + entries.remove(url); + } + + public synchronized void updateEntry( + final String url, + final HttpCacheUpdateCallback callback) throws IOException { + final HttpCacheEntry existingEntry = entries.get(url); + entries.put(url, callback.update(existingEntry)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java new file mode 100644 index 000000000..defdc3999 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Formatter; +import java.util.Locale; + +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +/** + * Should produce reasonably unique tokens. + */ +@ThreadSafe +class BasicIdGenerator { + + private final String hostname; + private final SecureRandom rnd; + + @GuardedBy("this") + private long count; + + public BasicIdGenerator() { + super(); + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (final UnknownHostException ex) { + hostname = "localhost"; + } + this.hostname = hostname; + try { + this.rnd = SecureRandom.getInstance("SHA1PRNG"); + } catch (final NoSuchAlgorithmException ex) { + throw new Error(ex); + } + this.rnd.setSeed(System.currentTimeMillis()); + } + + public synchronized void generate(final StringBuilder buffer) { + this.count++; + final int rndnum = this.rnd.nextInt(); + buffer.append(System.currentTimeMillis()); + buffer.append('.'); + final Formatter formatter = new Formatter(buffer, Locale.US); + formatter.format("%1$016x-%2$08x", this.count, rndnum); + formatter.close(); + buffer.append('.'); + buffer.append(this.hostname); + } + + public String generate() { + final StringBuilder buffer = new StringBuilder(); + generate(buffer); + return buffer.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java new file mode 100644 index 000000000..964ea2719 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java @@ -0,0 +1,764 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <p>Java Beans-style configuration for a {@link CachingHttpClient}. Any class + * in the caching module that has configuration options should take a + * {@link CacheConfig} argument in one of its constructors. A + * {@code CacheConfig} instance has sane and conservative defaults, so the + * easiest way to specify options is to get an instance and then set just + * the options you want to modify from their defaults.</p> + * + * <p><b>N.B.</b> This class is only for caching-specific configuration; to + * configure the behavior of the rest of the client, configure the + * {@link ch.boye.httpclientandroidlib.client.HttpClient} used as the "backend" + * for the {@code CachingHttpClient}.</p> + * + * <p>Cache configuration can be grouped into the following categories:</p> + * + * <p><b>Cache size.</b> If the backend storage supports these limits, you + * can specify the {@link CacheConfig#getMaxCacheEntries maximum number of + * cache entries} as well as the {@link CacheConfig#getMaxObjectSizeBytes + * maximum cacheable response body size}.</p> + * + * <p><b>Public/private caching.</b> By default, the caching module considers + * itself to be a shared (public) cache, and will not, for example, cache + * responses to requests with {@code Authorization} headers or responses + * marked with {@code Cache-Control: private}. If, however, the cache + * is only going to be used by one logical "user" (behaving similarly to a + * browser cache), then you will want to {@link + * CacheConfig#setSharedCache(boolean) turn off the shared cache setting}.</p> + * + * <p><b>303 caching</b>. RFC2616 explicitly disallows caching 303 responses; + * however, the HTTPbis working group says they can be cached + * if explicitly indicated in the response headers and permitted by the request method. + * (They also indicate that disallowing 303 caching is actually an unintended + * spec error in RFC2616). + * This behavior is off by default, to err on the side of a conservative + * adherence to the existing standard, but you may want to + * {@link Builder#setAllow303Caching(boolean) enable it}. + * + * <p><b>Weak ETags on PUT/DELETE If-Match requests</b>. RFC2616 explicitly + * prohibits the use of weak validators in non-GET requests, however, the + * HTTPbis working group says while the limitation for weak validators on ranged + * requests makes sense, weak ETag validation is useful on full non-GET + * requests; e.g., PUT with If-Match. This behavior is off by default, to err on + * the side of a conservative adherence to the existing standard, but you may + * want to {@link Builder#setWeakETagOnPutDeleteAllowed(boolean) enable it}. + * + * <p><b>Heuristic caching</b>. Per RFC2616, a cache may cache certain cache + * entries even if no explicit cache control headers are set by the origin. + * This behavior is off by default, but you may want to turn this on if you + * are working with an origin that doesn't set proper headers but where you + * still want to cache the responses. You will want to {@link + * CacheConfig#setHeuristicCachingEnabled(boolean) enable heuristic caching}, + * then specify either a {@link CacheConfig#getHeuristicDefaultLifetime() + * default freshness lifetime} and/or a {@link + * CacheConfig#setHeuristicCoefficient(float) fraction of the time since + * the resource was last modified}. See Sections + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2"> + * 13.2.2</a> and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4"> + * 13.2.4</a> of the HTTP/1.1 RFC for more details on heuristic caching.</p> + * + * <p><b>Background validation</b>. The cache module supports the + * {@code stale-while-revalidate} directive of + * <a href="http://tools.ietf.org/html/rfc5861">RFC5861</a>, which allows + * certain cache entry revalidations to happen in the background. You may + * want to tweak the settings for the {@link + * CacheConfig#getAsynchronousWorkersCore() minimum} and {@link + * CacheConfig#getAsynchronousWorkersMax() maximum} number of background + * worker threads, as well as the {@link + * CacheConfig#getAsynchronousWorkerIdleLifetimeSecs() maximum time they + * can be idle before being reclaimed}. You can also control the {@link + * CacheConfig#getRevalidationQueueSize() size of the queue} used for + * revalidations when there aren't enough workers to keep up with demand.</b> + */ +public class CacheConfig implements Cloneable { + + /** Default setting for the maximum object size that will be + * cached, in bytes. + */ + public final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192; + + /** Default setting for the maximum number of cache entries + * that will be retained. + */ + public final static int DEFAULT_MAX_CACHE_ENTRIES = 1000; + + /** Default setting for the number of retries on a failed + * cache update + */ + public final static int DEFAULT_MAX_UPDATE_RETRIES = 1; + + /** Default setting for 303 caching + */ + public final static boolean DEFAULT_303_CACHING_ENABLED = false; + + /** Default setting to allow weak tags on PUT/DELETE methods + */ + public final static boolean DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED = false; + + /** Default setting for heuristic caching + */ + public final static boolean DEFAULT_HEURISTIC_CACHING_ENABLED = false; + + /** Default coefficient used to heuristically determine freshness + * lifetime from the Last-Modified time of a cache entry. + */ + public final static float DEFAULT_HEURISTIC_COEFFICIENT = 0.1f; + + /** Default lifetime in seconds to be assumed when we cannot calculate + * freshness heuristically. + */ + public final static long DEFAULT_HEURISTIC_LIFETIME = 0; + + /** Default number of worker threads to allow for background revalidations + * resulting from the stale-while-revalidate directive. + */ + public static final int DEFAULT_ASYNCHRONOUS_WORKERS_MAX = 1; + + /** Default minimum number of worker threads to allow for background + * revalidations resulting from the stale-while-revalidate directive. + */ + public static final int DEFAULT_ASYNCHRONOUS_WORKERS_CORE = 1; + + /** Default maximum idle lifetime for a background revalidation thread + * before it gets reclaimed. + */ + public static final int DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS = 60; + + /** Default maximum queue length for background revalidation requests. + */ + public static final int DEFAULT_REVALIDATION_QUEUE_SIZE = 100; + + public static final CacheConfig DEFAULT = new Builder().build(); + + // TODO: make final + private long maxObjectSize; + private int maxCacheEntries; + private int maxUpdateRetries; + private boolean allow303Caching; + private boolean weakETagOnPutDeleteAllowed; + private boolean heuristicCachingEnabled; + private float heuristicCoefficient; + private long heuristicDefaultLifetime; + private boolean isSharedCache; + private int asynchronousWorkersMax; + private int asynchronousWorkersCore; + private int asynchronousWorkerIdleLifetimeSecs; + private int revalidationQueueSize; + private boolean neverCacheHTTP10ResponsesWithQuery; + + /** + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public CacheConfig() { + super(); + this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES; + this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES; + this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES; + this.allow303Caching = DEFAULT_303_CACHING_ENABLED; + this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED; + this.heuristicCachingEnabled = DEFAULT_HEURISTIC_CACHING_ENABLED; + this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT; + this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME; + this.isSharedCache = true; + this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX; + this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; + this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; + this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE; + } + + CacheConfig( + final long maxObjectSize, + final int maxCacheEntries, + final int maxUpdateRetries, + final boolean allow303Caching, + final boolean weakETagOnPutDeleteAllowed, + final boolean heuristicCachingEnabled, + final float heuristicCoefficient, + final long heuristicDefaultLifetime, + final boolean isSharedCache, + final int asynchronousWorkersMax, + final int asynchronousWorkersCore, + final int asynchronousWorkerIdleLifetimeSecs, + final int revalidationQueueSize, + final boolean neverCacheHTTP10ResponsesWithQuery) { + super(); + this.maxObjectSize = maxObjectSize; + this.maxCacheEntries = maxCacheEntries; + this.maxUpdateRetries = maxUpdateRetries; + this.allow303Caching = allow303Caching; + this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed; + this.heuristicCachingEnabled = heuristicCachingEnabled; + this.heuristicCoefficient = heuristicCoefficient; + this.heuristicDefaultLifetime = heuristicDefaultLifetime; + this.isSharedCache = isSharedCache; + this.asynchronousWorkersMax = asynchronousWorkersMax; + this.asynchronousWorkersCore = asynchronousWorkersCore; + this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs; + this.revalidationQueueSize = revalidationQueueSize; + } + + /** + * Returns the current maximum response body size that will be cached. + * @return size in bytes + * + * @deprecated (4.2) use {@link #getMaxObjectSize()} + */ + @Deprecated + public int getMaxObjectSizeBytes() { + return maxObjectSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxObjectSize; + } + + /** + * Specifies the maximum response body size that will be eligible for caching. + * @param maxObjectSizeBytes size in bytes + * + * @deprecated (4.2) use {@link Builder}. + */ + @Deprecated + public void setMaxObjectSizeBytes(final int maxObjectSizeBytes) { + if (maxObjectSizeBytes > Integer.MAX_VALUE) { + this.maxObjectSize = Integer.MAX_VALUE; + } else { + this.maxObjectSize = maxObjectSizeBytes; + } + } + + /** + * Returns the current maximum response body size that will be cached. + * @return size in bytes + * + * @since 4.2 + */ + public long getMaxObjectSize() { + return maxObjectSize; + } + + /** + * Specifies the maximum response body size that will be eligible for caching. + * @param maxObjectSize size in bytes + * + * @since 4.2 + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setMaxObjectSize(final long maxObjectSize) { + this.maxObjectSize = maxObjectSize; + } + + /** + * Returns whether the cache will never cache HTTP 1.0 responses with a query string or not. + * @return {@code true} to not cache query string responses, {@code false} to cache if explicit cache headers are + * found + */ + public boolean isNeverCacheHTTP10ResponsesWithQuery() { + return neverCacheHTTP10ResponsesWithQuery; + } + + /** + * Returns the maximum number of cache entries the cache will retain. + */ + public int getMaxCacheEntries() { + return maxCacheEntries; + } + + /** + * Sets the maximum number of cache entries the cache will retain. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setMaxCacheEntries(final int maxCacheEntries) { + this.maxCacheEntries = maxCacheEntries; + } + + /** + * Returns the number of times to retry a cache update on failure + */ + public int getMaxUpdateRetries(){ + return maxUpdateRetries; + } + + /** + * Sets the number of times to retry a cache update on failure + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setMaxUpdateRetries(final int maxUpdateRetries){ + this.maxUpdateRetries = maxUpdateRetries; + } + + /** + * Returns whether 303 caching is enabled. + * @return {@code true} if it is enabled. + */ + public boolean is303CachingEnabled() { + return allow303Caching; + } + + /** + * Returns whether weak etags is allowed with PUT/DELETE methods. + * @return {@code true} if it is allowed. + */ + public boolean isWeakETagOnPutDeleteAllowed() { + return weakETagOnPutDeleteAllowed; + } + + /** + * Returns whether heuristic caching is enabled. + * @return {@code true} if it is enabled. + */ + public boolean isHeuristicCachingEnabled() { + return heuristicCachingEnabled; + } + + /** + * Enables or disables heuristic caching. + * @param heuristicCachingEnabled should be {@code true} to + * permit heuristic caching, {@code false} to disable it. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) { + this.heuristicCachingEnabled = heuristicCachingEnabled; + } + + /** + * Returns lifetime coefficient used in heuristic freshness caching. + */ + public float getHeuristicCoefficient() { + return heuristicCoefficient; + } + + /** + * Sets coefficient to be used in heuristic freshness caching. This is + * interpreted as the fraction of the time between the {@code Last-Modified} + * and {@code Date} headers of a cached response during which the cached + * response will be considered heuristically fresh. + * @param heuristicCoefficient should be between {@code 0.0} and + * {@code 1.0}. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setHeuristicCoefficient(final float heuristicCoefficient) { + this.heuristicCoefficient = heuristicCoefficient; + } + + /** + * Get the default lifetime to be used if heuristic freshness calculation is + * not possible. + */ + public long getHeuristicDefaultLifetime() { + return heuristicDefaultLifetime; + } + + /** + * Sets default lifetime in seconds to be used if heuristic freshness + * calculation is not possible. Explicit cache control directives on + * either the request or origin response will override this, as will + * the heuristic {@code Last-Modified} freshness calculation if it is + * available. + * @param heuristicDefaultLifetimeSecs is the number of seconds to + * consider a cache-eligible response fresh in the absence of other + * information. Set this to {@code 0} to disable this style of + * heuristic caching. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setHeuristicDefaultLifetime(final long heuristicDefaultLifetimeSecs) { + this.heuristicDefaultLifetime = heuristicDefaultLifetimeSecs; + } + + /** + * Returns whether the cache will behave as a shared cache or not. + * @return {@code true} for a shared cache, {@code false} for a non- + * shared (private) cache + */ + public boolean isSharedCache() { + return isSharedCache; + } + + /** + * Sets whether the cache should behave as a shared cache or not. + * @param isSharedCache true to behave as a shared cache, false to + * behave as a non-shared (private) cache. To have the cache + * behave like a browser cache, you want to set this to {@code false}. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setSharedCache(final boolean isSharedCache) { + this.isSharedCache = isSharedCache; + } + + /** + * Returns the maximum number of threads to allow for background + * revalidations due to the {@code stale-while-revalidate} directive. A + * value of 0 means background revalidations are disabled. + */ + public int getAsynchronousWorkersMax() { + return asynchronousWorkersMax; + } + + /** + * Sets the maximum number of threads to allow for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param max number of threads; a value of 0 disables background + * revalidations. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setAsynchronousWorkersMax(final int max) { + this.asynchronousWorkersMax = max; + } + + /** + * Returns the minimum number of threads to keep alive for background + * revalidations due to the {@code stale-while-revalidate} directive. + */ + public int getAsynchronousWorkersCore() { + return asynchronousWorkersCore; + } + + /** + * Sets the minimum number of threads to keep alive for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param min should be greater than zero and less than or equal + * to <code>getAsynchronousWorkersMax()</code> + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setAsynchronousWorkersCore(final int min) { + this.asynchronousWorkersCore = min; + } + + /** + * Returns the current maximum idle lifetime in seconds for a + * background revalidation worker thread. If a worker thread is idle + * for this long, and there are more than the core number of worker + * threads alive, the worker will be reclaimed. + */ + public int getAsynchronousWorkerIdleLifetimeSecs() { + return asynchronousWorkerIdleLifetimeSecs; + } + + /** + * Sets the current maximum idle lifetime in seconds for a + * background revalidation worker thread. If a worker thread is idle + * for this long, and there are more than the core number of worker + * threads alive, the worker will be reclaimed. + * @param secs idle lifetime in seconds + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setAsynchronousWorkerIdleLifetimeSecs(final int secs) { + this.asynchronousWorkerIdleLifetimeSecs = secs; + } + + /** + * Returns the current maximum queue size for background revalidations. + */ + public int getRevalidationQueueSize() { + return revalidationQueueSize; + } + + /** + * Sets the current maximum queue size for background revalidations. + * + * @deprecated (4.3) use {@link Builder}. + */ + @Deprecated + public void setRevalidationQueueSize(final int size) { + this.revalidationQueueSize = size; + } + + @Override + protected CacheConfig clone() throws CloneNotSupportedException { + return (CacheConfig) super.clone(); + } + + public static Builder custom() { + return new Builder(); + } + + public static Builder copy(final CacheConfig config) { + Args.notNull(config, "Cache config"); + return new Builder() + .setMaxObjectSize(config.getMaxObjectSize()) + .setMaxCacheEntries(config.getMaxCacheEntries()) + .setMaxUpdateRetries(config.getMaxUpdateRetries()) + .setHeuristicCachingEnabled(config.isHeuristicCachingEnabled()) + .setHeuristicCoefficient(config.getHeuristicCoefficient()) + .setHeuristicDefaultLifetime(config.getHeuristicDefaultLifetime()) + .setSharedCache(config.isSharedCache()) + .setAsynchronousWorkersMax(config.getAsynchronousWorkersMax()) + .setAsynchronousWorkersCore(config.getAsynchronousWorkersCore()) + .setAsynchronousWorkerIdleLifetimeSecs(config.getAsynchronousWorkerIdleLifetimeSecs()) + .setRevalidationQueueSize(config.getRevalidationQueueSize()) + .setNeverCacheHTTP10ResponsesWithQueryString(config.isNeverCacheHTTP10ResponsesWithQuery()); + } + + + public static class Builder { + + private long maxObjectSize; + private int maxCacheEntries; + private int maxUpdateRetries; + private boolean allow303Caching; + private boolean weakETagOnPutDeleteAllowed; + private boolean heuristicCachingEnabled; + private float heuristicCoefficient; + private long heuristicDefaultLifetime; + private boolean isSharedCache; + private int asynchronousWorkersMax; + private int asynchronousWorkersCore; + private int asynchronousWorkerIdleLifetimeSecs; + private int revalidationQueueSize; + private boolean neverCacheHTTP10ResponsesWithQuery; + + Builder() { + this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES; + this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES; + this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES; + this.allow303Caching = DEFAULT_303_CACHING_ENABLED; + this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED; + this.heuristicCachingEnabled = false; + this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT; + this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME; + this.isSharedCache = true; + this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX; + this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; + this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; + this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE; + } + + /** + * Specifies the maximum response body size that will be eligible for caching. + * @param maxObjectSize size in bytes + */ + public Builder setMaxObjectSize(final long maxObjectSize) { + this.maxObjectSize = maxObjectSize; + return this; + } + + /** + * Sets the maximum number of cache entries the cache will retain. + */ + public Builder setMaxCacheEntries(final int maxCacheEntries) { + this.maxCacheEntries = maxCacheEntries; + return this; + } + + /** + * Sets the number of times to retry a cache update on failure + */ + public Builder setMaxUpdateRetries(final int maxUpdateRetries) { + this.maxUpdateRetries = maxUpdateRetries; + return this; + } + + /** + * Enables or disables 303 caching. + * @param allow303Caching should be {@code true} to + * permit 303 caching, {@code false} to disable it. + */ + public Builder setAllow303Caching(final boolean allow303Caching) { + this.allow303Caching = allow303Caching; + return this; + } + + /** + * Allows or disallows weak etags to be used with PUT/DELETE If-Match requests. + * @param weakETagOnPutDeleteAllowed should be {@code true} to + * permit weak etags, {@code false} to reject them. + */ + public Builder setWeakETagOnPutDeleteAllowed(final boolean weakETagOnPutDeleteAllowed) { + this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed; + return this; + } + + /** + * Enables or disables heuristic caching. + * @param heuristicCachingEnabled should be {@code true} to + * permit heuristic caching, {@code false} to enable it. + */ + public Builder setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) { + this.heuristicCachingEnabled = heuristicCachingEnabled; + return this; + } + + /** + * Sets coefficient to be used in heuristic freshness caching. This is + * interpreted as the fraction of the time between the {@code Last-Modified} + * and {@code Date} headers of a cached response during which the cached + * response will be considered heuristically fresh. + * @param heuristicCoefficient should be between {@code 0.0} and + * {@code 1.0}. + */ + public Builder setHeuristicCoefficient(final float heuristicCoefficient) { + this.heuristicCoefficient = heuristicCoefficient; + return this; + } + + /** + * Sets default lifetime in seconds to be used if heuristic freshness + * calculation is not possible. Explicit cache control directives on + * either the request or origin response will override this, as will + * the heuristic {@code Last-Modified} freshness calculation if it is + * available. + * @param heuristicDefaultLifetime is the number of seconds to + * consider a cache-eligible response fresh in the absence of other + * information. Set this to {@code 0} to disable this style of + * heuristic caching. + */ + public Builder setHeuristicDefaultLifetime(final long heuristicDefaultLifetime) { + this.heuristicDefaultLifetime = heuristicDefaultLifetime; + return this; + } + + /** + * Sets whether the cache should behave as a shared cache or not. + * @param isSharedCache true to behave as a shared cache, false to + * behave as a non-shared (private) cache. To have the cache + * behave like a browser cache, you want to set this to {@code false}. + */ + public Builder setSharedCache(final boolean isSharedCache) { + this.isSharedCache = isSharedCache; + return this; + } + + /** + * Sets the maximum number of threads to allow for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param asynchronousWorkersMax number of threads; a value of 0 disables background + * revalidations. + */ + public Builder setAsynchronousWorkersMax(final int asynchronousWorkersMax) { + this.asynchronousWorkersMax = asynchronousWorkersMax; + return this; + } + + /** + * Sets the minimum number of threads to keep alive for background + * revalidations due to the {@code stale-while-revalidate} directive. + * @param asynchronousWorkersCore should be greater than zero and less than or equal + * to <code>getAsynchronousWorkersMax()</code> + */ + public Builder setAsynchronousWorkersCore(final int asynchronousWorkersCore) { + this.asynchronousWorkersCore = asynchronousWorkersCore; + return this; + } + + /** + * Sets the current maximum idle lifetime in seconds for a + * background revalidation worker thread. If a worker thread is idle + * for this long, and there are more than the core number of worker + * threads alive, the worker will be reclaimed. + * @param asynchronousWorkerIdleLifetimeSecs idle lifetime in seconds + */ + public Builder setAsynchronousWorkerIdleLifetimeSecs(final int asynchronousWorkerIdleLifetimeSecs) { + this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs; + return this; + } + + /** + * Sets the current maximum queue size for background revalidations. + */ + public Builder setRevalidationQueueSize(final int revalidationQueueSize) { + this.revalidationQueueSize = revalidationQueueSize; + return this; + } + + /** + * Sets whether the cache should never cache HTTP 1.0 responses with a query string or not. + * @param neverCacheHTTP10ResponsesWithQuery true to never cache responses with a query + * string, false to cache if explicit cache headers are found. Set this to {@code true} + * to better emulate IE, which also never caches responses, regardless of what caching + * headers may be present. + */ + public Builder setNeverCacheHTTP10ResponsesWithQueryString( + final boolean neverCacheHTTP10ResponsesWithQuery) { + this.neverCacheHTTP10ResponsesWithQuery = neverCacheHTTP10ResponsesWithQuery; + return this; + } + + public CacheConfig build() { + return new CacheConfig( + maxObjectSize, + maxCacheEntries, + maxUpdateRetries, + allow303Caching, + weakETagOnPutDeleteAllowed, + heuristicCachingEnabled, + heuristicCoefficient, + heuristicDefaultLifetime, + isSharedCache, + asynchronousWorkersMax, + asynchronousWorkersCore, + asynchronousWorkerIdleLifetimeSecs, + revalidationQueueSize, + neverCacheHTTP10ResponsesWithQuery); + } + + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("[maxObjectSize=").append(this.maxObjectSize) + .append(", maxCacheEntries=").append(this.maxCacheEntries) + .append(", maxUpdateRetries=").append(this.maxUpdateRetries) + .append(", 303CachingEnabled=").append(this.allow303Caching) + .append(", weakETagOnPutDeleteAllowed=").append(this.weakETagOnPutDeleteAllowed) + .append(", heuristicCachingEnabled=").append(this.heuristicCachingEnabled) + .append(", heuristicCoefficient=").append(this.heuristicCoefficient) + .append(", heuristicDefaultLifetime=").append(this.heuristicDefaultLifetime) + .append(", isSharedCache=").append(this.isSharedCache) + .append(", asynchronousWorkersMax=").append(this.asynchronousWorkersMax) + .append(", asynchronousWorkersCore=").append(this.asynchronousWorkersCore) + .append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs) + .append(", revalidationQueueSize=").append(this.revalidationQueueSize) + .append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery) + .append("]"); + return builder.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java new file mode 100644 index 000000000..1906166d1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java @@ -0,0 +1,99 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +@Immutable +class CacheEntity implements HttpEntity, Serializable { + + private static final long serialVersionUID = -3467082284120936233L; + + private final HttpCacheEntry cacheEntry; + + public CacheEntity(final HttpCacheEntry cacheEntry) { + super(); + this.cacheEntry = cacheEntry; + } + + public Header getContentType() { + return this.cacheEntry.getFirstHeader(HTTP.CONTENT_TYPE); + } + + public Header getContentEncoding() { + return this.cacheEntry.getFirstHeader(HTTP.CONTENT_ENCODING); + } + + public boolean isChunked() { + return false; + } + + public boolean isRepeatable() { + return true; + } + + public long getContentLength() { + return this.cacheEntry.getResource().length(); + } + + public InputStream getContent() throws IOException { + return this.cacheEntry.getResource().getInputStream(); + } + + public void writeTo(final OutputStream outstream) throws IOException { + Args.notNull(outstream, "Output stream"); + final InputStream instream = this.cacheEntry.getResource().getInputStream(); + try { + IOUtils.copy(instream, outstream); + } finally { + instream.close(); + } + } + + public boolean isStreaming() { + return false; + } + + public void consumeContent() throws IOException { + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java new file mode 100644 index 000000000..381a7508d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java @@ -0,0 +1,173 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.ListIterator; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Update a {@link HttpCacheEntry} with new or updated information based on the latest + * 304 status response from the Server. Use the {@link HttpResponse} to perform + * the update. + * + * @since 4.1 + */ +@Immutable +class CacheEntryUpdater { + + private final ResourceFactory resourceFactory; + + CacheEntryUpdater() { + this(new HeapResourceFactory()); + } + + CacheEntryUpdater(final ResourceFactory resourceFactory) { + super(); + this.resourceFactory = resourceFactory; + } + + /** + * Update the entry with the new information from the response. Should only be used for + * 304 responses. + * + * @param requestId + * @param entry The cache Entry to be updated + * @param requestDate When the request was performed + * @param responseDate When the response was gotten + * @param response The HttpResponse from the backend server call + * @return HttpCacheEntry an updated version of the cache entry + * @throws java.io.IOException if something bad happens while trying to read the body from the original entry + */ + public HttpCacheEntry updateCacheEntry( + final String requestId, + final HttpCacheEntry entry, + final Date requestDate, + final Date responseDate, + final HttpResponse response) throws IOException { + Args.check(response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED, + "Response must have 304 status code"); + final Header[] mergedHeaders = mergeHeaders(entry, response); + Resource resource = null; + if (entry.getResource() != null) { + resource = resourceFactory.copy(requestId, entry.getResource()); + } + return new HttpCacheEntry( + requestDate, + responseDate, + entry.getStatusLine(), + mergedHeaders, + resource); + } + + protected Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) { + + if (entryAndResponseHaveDateHeader(entry, response) + && entryDateHeaderNewerThenResponse(entry, response)) { + // Don't merge headers, keep the entry's headers as they are newer. + return entry.getAllHeaders(); + } + + final List<Header> cacheEntryHeaderList = new ArrayList<Header>(Arrays.asList(entry + .getAllHeaders())); + removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response); + removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry); + cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders())); + + return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]); + } + + private void removeCacheHeadersThatMatchResponse(final List<Header> cacheEntryHeaderList, + final HttpResponse response) { + for (final Header responseHeader : response.getAllHeaders()) { + final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator(); + + while (cacheEntryHeaderListIter.hasNext()) { + final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName(); + + if (cacheEntryHeaderName.equals(responseHeader.getName())) { + cacheEntryHeaderListIter.remove(); + } + } + } + } + + private void removeCacheEntry1xxWarnings(final List<Header> cacheEntryHeaderList, final HttpCacheEntry entry) { + final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator(); + + while (cacheEntryHeaderListIter.hasNext()) { + final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName(); + + if (HeaderConstants.WARNING.equals(cacheEntryHeaderName)) { + for (final Header cacheEntryWarning : entry.getHeaders(HeaderConstants.WARNING)) { + if (cacheEntryWarning.getValue().startsWith("1")) { + cacheEntryHeaderListIter.remove(); + } + } + } + } + } + + private boolean entryDateHeaderNewerThenResponse(final HttpCacheEntry entry, final HttpResponse response) { + final Date entryDate = DateUtils.parseDate(entry.getFirstHeader(HTTP.DATE_HEADER) + .getValue()); + final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER) + .getValue()); + if (entryDate == null || responseDate == null) { + return false; + } + if (!entryDate.after(responseDate)) { + return false; + } + return true; + } + + private boolean entryAndResponseHaveDateHeader(final HttpCacheEntry entry, final HttpResponse response) { + if (entry.getFirstHeader(HTTP.DATE_HEADER) != null + && response.getFirstHeader(HTTP.DATE_HEADER) != null) { + return true; + } + + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java new file mode 100644 index 000000000..19e62869e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java @@ -0,0 +1,288 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * Given a particular HttpRequest, flush any cache entries that this request + * would invalidate. + * + * @since 4.1 + */ +@Immutable +class CacheInvalidator implements HttpCacheInvalidator { + + private final HttpCacheStorage storage; + private final CacheKeyGenerator cacheKeyGenerator; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Create a new {@link CacheInvalidator} for a given {@link HttpCache} and + * {@link CacheKeyGenerator}. + * + * @param uriExtractor Provides identifiers for the keys to store cache entries + * @param storage the cache to store items away in + */ + public CacheInvalidator( + final CacheKeyGenerator uriExtractor, + final HttpCacheStorage storage) { + this.cacheKeyGenerator = uriExtractor; + this.storage = storage; + } + + /** + * Remove cache entries from the cache that are no longer fresh or + * have been invalidated in some way. + * + * @param host The backend host we are talking to + * @param req The HttpRequest to that host + */ + public void flushInvalidatedCacheEntries(final HttpHost host, final HttpRequest req) { + if (requestShouldNotBeCached(req)) { + log.debug("Request should not be cached"); + + final String theUri = cacheKeyGenerator.getURI(host, req); + + final HttpCacheEntry parent = getEntry(theUri); + + log.debug("parent entry: " + parent); + + if (parent != null) { + for (final String variantURI : parent.getVariantMap().values()) { + flushEntry(variantURI); + } + flushEntry(theUri); + } + final URL reqURL = getAbsoluteURL(theUri); + if (reqURL == null) { + log.error("Couldn't transform request into valid URL"); + return; + } + final Header clHdr = req.getFirstHeader("Content-Location"); + if (clHdr != null) { + final String contentLocation = clHdr.getValue(); + if (!flushAbsoluteUriFromSameHost(reqURL, contentLocation)) { + flushRelativeUriFromSameHost(reqURL, contentLocation); + } + } + final Header lHdr = req.getFirstHeader("Location"); + if (lHdr != null) { + flushAbsoluteUriFromSameHost(reqURL, lHdr.getValue()); + } + } + } + + private void flushEntry(final String uri) { + try { + storage.removeEntry(uri); + } catch (final IOException ioe) { + log.warn("unable to flush cache entry", ioe); + } + } + + private HttpCacheEntry getEntry(final String theUri) { + try { + return storage.getEntry(theUri); + } catch (final IOException ioe) { + log.warn("could not retrieve entry from storage", ioe); + } + return null; + } + + protected void flushUriIfSameHost(final URL requestURL, final URL targetURL) { + final URL canonicalTarget = getAbsoluteURL(cacheKeyGenerator.canonicalizeUri(targetURL.toString())); + if (canonicalTarget == null) { + return; + } + if (canonicalTarget.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) { + flushEntry(canonicalTarget.toString()); + } + } + + protected void flushRelativeUriFromSameHost(final URL reqURL, final String relUri) { + final URL relURL = getRelativeURL(reqURL, relUri); + if (relURL == null) { + return; + } + flushUriIfSameHost(reqURL, relURL); + } + + + protected boolean flushAbsoluteUriFromSameHost(final URL reqURL, final String uri) { + final URL absURL = getAbsoluteURL(uri); + if (absURL == null) { + return false; + } + flushUriIfSameHost(reqURL,absURL); + return true; + } + + private URL getAbsoluteURL(final String uri) { + URL absURL = null; + try { + absURL = new URL(uri); + } catch (final MalformedURLException mue) { + // nop + } + return absURL; + } + + private URL getRelativeURL(final URL reqURL, final String relUri) { + URL relURL = null; + try { + relURL = new URL(reqURL,relUri); + } catch (final MalformedURLException e) { + // nop + } + return relURL; + } + + protected boolean requestShouldNotBeCached(final HttpRequest req) { + final String method = req.getRequestLine().getMethod(); + return notGetOrHeadRequest(method); + } + + private boolean notGetOrHeadRequest(final String method) { + return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD + .equals(method)); + } + + /** Flushes entries that were invalidated by the given response + * received for the given host/request pair. + */ + public void flushInvalidatedCacheEntries(final HttpHost host, + final HttpRequest request, final HttpResponse response) { + final int status = response.getStatusLine().getStatusCode(); + if (status < 200 || status > 299) { + return; + } + final URL reqURL = getAbsoluteURL(cacheKeyGenerator.getURI(host, request)); + if (reqURL == null) { + return; + } + final URL contentLocation = getContentLocationURL(reqURL, response); + if (contentLocation != null) { + flushLocationCacheEntry(reqURL, response, contentLocation); + } + final URL location = getLocationURL(reqURL, response); + if (location != null) { + flushLocationCacheEntry(reqURL, response, location); + } + } + + private void flushLocationCacheEntry(final URL reqURL, + final HttpResponse response, final URL location) { + final String cacheKey = cacheKeyGenerator.canonicalizeUri(location.toString()); + final HttpCacheEntry entry = getEntry(cacheKey); + if (entry == null) { + return; + } + + // do not invalidate if response is strictly older than entry + // or if the etags match + + if (responseDateOlderThanEntryDate(response, entry)) { + return; + } + if (!responseAndEntryEtagsDiffer(response, entry)) { + return; + } + + flushUriIfSameHost(reqURL, location); + } + + private URL getContentLocationURL(final URL reqURL, final HttpResponse response) { + final Header clHeader = response.getFirstHeader("Content-Location"); + if (clHeader == null) { + return null; + } + final String contentLocation = clHeader.getValue(); + final URL canonURL = getAbsoluteURL(contentLocation); + if (canonURL != null) { + return canonURL; + } + return getRelativeURL(reqURL, contentLocation); + } + + private URL getLocationURL(final URL reqURL, final HttpResponse response) { + final Header clHeader = response.getFirstHeader("Location"); + if (clHeader == null) { + return null; + } + final String location = clHeader.getValue(); + final URL canonURL = getAbsoluteURL(location); + if (canonURL != null) { + return canonURL; + } + return getRelativeURL(reqURL, location); + } + + private boolean responseAndEntryEtagsDiffer(final HttpResponse response, + final HttpCacheEntry entry) { + final Header entryEtag = entry.getFirstHeader(HeaderConstants.ETAG); + final Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG); + if (entryEtag == null || responseEtag == null) { + return false; + } + return (!entryEtag.getValue().equals(responseEtag.getValue())); + } + + private boolean responseDateOlderThanEntryDate(final HttpResponse response, + final HttpCacheEntry entry) { + final Header entryDateHeader = entry.getFirstHeader(HTTP.DATE_HEADER); + final Header responseDateHeader = response.getFirstHeader(HTTP.DATE_HEADER); + if (entryDateHeader == null || responseDateHeader == null) { + /* be conservative; should probably flush */ + return false; + } + final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue()); + final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue()); + if (entryDate == null || responseDate == null) { + return false; + } + return responseDate.before(entryDate); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java new file mode 100644 index 000000000..b0628041f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java @@ -0,0 +1,178 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; + +/** + * @since 4.1 + */ +@Immutable +class CacheKeyGenerator { + + private static final URI BASE_URI = URI.create("http://example.com/"); + + /** + * For a given {@link HttpHost} and {@link HttpRequest} get a URI from the + * pair that I can use as an identifier KEY into my HttpCache + * + * @param host The host for this request + * @param req the {@link HttpRequest} + * @return String the extracted URI + */ + public String getURI(final HttpHost host, final HttpRequest req) { + if (isRelativeRequest(req)) { + return canonicalizeUri(String.format("%s%s", host.toString(), req.getRequestLine().getUri())); + } + return canonicalizeUri(req.getRequestLine().getUri()); + } + + public String canonicalizeUri(final String uri) { + try { + final URI normalized = URIUtils.resolve(BASE_URI, uri); + final URL u = new URL(normalized.toASCIIString()); + final String protocol = u.getProtocol(); + final String hostname = u.getHost(); + final int port = canonicalizePort(u.getPort(), protocol); + final String path = u.getPath(); + final String query = u.getQuery(); + final String file = (query != null) ? (path + "?" + query) : path; + final URL out = new URL(protocol, hostname, port, file); + return out.toString(); + } catch (final IllegalArgumentException e) { + return uri; + } catch (final MalformedURLException e) { + return uri; + } + } + + private int canonicalizePort(final int port, final String protocol) { + if (port == -1 && "http".equalsIgnoreCase(protocol)) { + return 80; + } else if (port == -1 && "https".equalsIgnoreCase(protocol)) { + return 443; + } + return port; + } + + private boolean isRelativeRequest(final HttpRequest req) { + final String requestUri = req.getRequestLine().getUri(); + return ("*".equals(requestUri) || requestUri.startsWith("/")); + } + + protected String getFullHeaderValue(final Header[] headers) { + if (headers == null) { + return ""; + } + + final StringBuilder buf = new StringBuilder(""); + boolean first = true; + for (final Header hdr : headers) { + if (!first) { + buf.append(", "); + } + buf.append(hdr.getValue().trim()); + first = false; + + } + return buf.toString(); + } + + /** + * For a given {@link HttpHost} and {@link HttpRequest} if the request has a + * VARY header - I need to get an additional URI from the pair of host and + * request so that I can also store the variant into my HttpCache. + * + * @param host The host for this request + * @param req the {@link HttpRequest} + * @param entry the parent entry used to track the variants + * @return String the extracted variant URI + */ + public String getVariantURI(final HttpHost host, final HttpRequest req, final HttpCacheEntry entry) { + if (!entry.hasVariants()) { + return getURI(host, req); + } + return getVariantKey(req, entry) + getURI(host, req); + } + + /** + * Compute a "variant key" from the headers of a given request that are + * covered by the Vary header of a given cache entry. Any request whose + * varying headers match those of this request should have the same + * variant key. + * @param req originating request + * @param entry cache entry in question that has variants + * @return a <code>String</code> variant key + */ + public String getVariantKey(final HttpRequest req, final HttpCacheEntry entry) { + final List<String> variantHeaderNames = new ArrayList<String>(); + for (final Header varyHdr : entry.getHeaders(HeaderConstants.VARY)) { + for (final HeaderElement elt : varyHdr.getElements()) { + variantHeaderNames.add(elt.getName()); + } + } + Collections.sort(variantHeaderNames); + + StringBuilder buf; + try { + buf = new StringBuilder("{"); + boolean first = true; + for (final String headerName : variantHeaderNames) { + if (!first) { + buf.append("&"); + } + buf.append(URLEncoder.encode(headerName, Consts.UTF_8.name())); + buf.append("="); + buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)), + Consts.UTF_8.name())); + first = false; + } + buf.append("}"); + } catch (final UnsupportedEncodingException uee) { + throw new RuntimeException("couldn't encode to UTF-8", uee); + } + return buf.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java new file mode 100644 index 000000000..0ed80920c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java @@ -0,0 +1,50 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.LinkedHashMap; +import java.util.Map; + +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; + +final class CacheMap extends LinkedHashMap<String, HttpCacheEntry> { + + private static final long serialVersionUID = -7750025207539768511L; + + private final int maxEntries; + + CacheMap(final int maxEntries) { + super(20, 0.75f, true); + this.maxEntries = maxEntries; + } + + @Override + protected boolean removeEldestEntry(final Map.Entry<String, HttpCacheEntry> eldest) { + return size() > this.maxEntries; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java new file mode 100644 index 000000000..333dfbbfb --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java @@ -0,0 +1,320 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * @since 4.1 + */ +@Immutable +class CacheValidityPolicy { + + public static final long MAX_AGE = 2147483648L; + + CacheValidityPolicy() { + super(); + } + + public long getCurrentAgeSecs(final HttpCacheEntry entry, final Date now) { + return getCorrectedInitialAgeSecs(entry) + getResidentTimeSecs(entry, now); + } + + public long getFreshnessLifetimeSecs(final HttpCacheEntry entry) { + final long maxage = getMaxAge(entry); + if (maxage > -1) { + return maxage; + } + + final Date dateValue = entry.getDate(); + if (dateValue == null) { + return 0L; + } + + final Date expiry = getExpirationDate(entry); + if (expiry == null) { + return 0; + } + final long diff = expiry.getTime() - dateValue.getTime(); + return (diff / 1000); + } + + public boolean isResponseFresh(final HttpCacheEntry entry, final Date now) { + return (getCurrentAgeSecs(entry, now) < getFreshnessLifetimeSecs(entry)); + } + + /** + * Decides if this response is fresh enough based Last-Modified and Date, if available. + * This entry is meant to be used when isResponseFresh returns false. The algorithm is as follows: + * + * if last-modified and date are defined, freshness lifetime is coefficient*(date-lastModified), + * else freshness lifetime is defaultLifetime + * + * @param entry the cache entry + * @param now what time is it currently (When is right NOW) + * @param coefficient Part of the heuristic for cache entry freshness + * @param defaultLifetime How long can I assume a cache entry is default TTL + * @return {@code true} if the response is fresh + */ + public boolean isResponseHeuristicallyFresh(final HttpCacheEntry entry, + final Date now, final float coefficient, final long defaultLifetime) { + return (getCurrentAgeSecs(entry, now) < getHeuristicFreshnessLifetimeSecs(entry, coefficient, defaultLifetime)); + } + + public long getHeuristicFreshnessLifetimeSecs(final HttpCacheEntry entry, + final float coefficient, final long defaultLifetime) { + final Date dateValue = entry.getDate(); + final Date lastModifiedValue = getLastModifiedValue(entry); + + if (dateValue != null && lastModifiedValue != null) { + final long diff = dateValue.getTime() - lastModifiedValue.getTime(); + if (diff < 0) { + return 0; + } + return (long)(coefficient * (diff / 1000)); + } + + return defaultLifetime; + } + + public boolean isRevalidatable(final HttpCacheEntry entry) { + return entry.getFirstHeader(HeaderConstants.ETAG) != null + || entry.getFirstHeader(HeaderConstants.LAST_MODIFIED) != null; + } + + public boolean mustRevalidate(final HttpCacheEntry entry) { + return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE); + } + + public boolean proxyRevalidate(final HttpCacheEntry entry) { + return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE); + } + + public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, final Date now) { + for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.STALE_WHILE_REVALIDATE.equalsIgnoreCase(elt.getName())) { + try { + final int allowedStalenessLifetime = Integer.parseInt(elt.getValue()); + if (getStalenessSecs(entry, now) <= allowedStalenessLifetime) { + return true; + } + } catch (final NumberFormatException nfe) { + // skip malformed directive + } + } + } + } + + return false; + } + + public boolean mayReturnStaleIfError(final HttpRequest request, + final HttpCacheEntry entry, final Date now) { + final long stalenessSecs = getStalenessSecs(entry, now); + return mayReturnStaleIfError(request.getHeaders(HeaderConstants.CACHE_CONTROL), + stalenessSecs) + || mayReturnStaleIfError(entry.getHeaders(HeaderConstants.CACHE_CONTROL), + stalenessSecs); + } + + private boolean mayReturnStaleIfError(final Header[] headers, final long stalenessSecs) { + boolean result = false; + for(final Header h : headers) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.STALE_IF_ERROR.equals(elt.getName())) { + try { + final int staleIfErrorSecs = Integer.parseInt(elt.getValue()); + if (stalenessSecs <= staleIfErrorSecs) { + result = true; + break; + } + } catch (final NumberFormatException nfe) { + // skip malformed directive + } + } + } + } + return result; + } + + /** + * @deprecated (4.3) use {@link HttpCacheEntry#getDate()}. + * @param entry + * @return the Date of the entry + */ + @Deprecated + protected Date getDateValue(final HttpCacheEntry entry) { + return entry.getDate(); + } + + protected Date getLastModifiedValue(final HttpCacheEntry entry) { + final Header dateHdr = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED); + if (dateHdr == null) { + return null; + } + return DateUtils.parseDate(dateHdr.getValue()); + } + + protected long getContentLengthValue(final HttpCacheEntry entry) { + final Header cl = entry.getFirstHeader(HTTP.CONTENT_LEN); + if (cl == null) { + return -1; + } + + try { + return Long.parseLong(cl.getValue()); + } catch (final NumberFormatException ex) { + return -1; + } + } + + protected boolean hasContentLengthHeader(final HttpCacheEntry entry) { + return null != entry.getFirstHeader(HTTP.CONTENT_LEN); + } + + /** + * This matters for deciding whether the cache entry is valid to serve as a + * response. If these values do not match, we might have a partial response + * + * @param entry The cache entry we are currently working with + * @return boolean indicating whether actual length matches Content-Length + */ + protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) { + return !hasContentLengthHeader(entry) || getContentLengthValue(entry) == entry.getResource().length(); + } + + protected long getApparentAgeSecs(final HttpCacheEntry entry) { + final Date dateValue = entry.getDate(); + if (dateValue == null) { + return MAX_AGE; + } + final long diff = entry.getResponseDate().getTime() - dateValue.getTime(); + if (diff < 0L) { + return 0; + } + return (diff / 1000); + } + + protected long getAgeValue(final HttpCacheEntry entry) { + long ageValue = 0; + for (final Header hdr : entry.getHeaders(HeaderConstants.AGE)) { + long hdrAge; + try { + hdrAge = Long.parseLong(hdr.getValue()); + if (hdrAge < 0) { + hdrAge = MAX_AGE; + } + } catch (final NumberFormatException nfe) { + hdrAge = MAX_AGE; + } + ageValue = (hdrAge > ageValue) ? hdrAge : ageValue; + } + return ageValue; + } + + protected long getCorrectedReceivedAgeSecs(final HttpCacheEntry entry) { + final long apparentAge = getApparentAgeSecs(entry); + final long ageValue = getAgeValue(entry); + return (apparentAge > ageValue) ? apparentAge : ageValue; + } + + protected long getResponseDelaySecs(final HttpCacheEntry entry) { + final long diff = entry.getResponseDate().getTime() - entry.getRequestDate().getTime(); + return (diff / 1000L); + } + + protected long getCorrectedInitialAgeSecs(final HttpCacheEntry entry) { + return getCorrectedReceivedAgeSecs(entry) + getResponseDelaySecs(entry); + } + + protected long getResidentTimeSecs(final HttpCacheEntry entry, final Date now) { + final long diff = now.getTime() - entry.getResponseDate().getTime(); + return (diff / 1000L); + } + + protected long getMaxAge(final HttpCacheEntry entry) { + long maxage = -1; + for (final Header hdr : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for (final HeaderElement elt : hdr.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName()) + || "s-maxage".equals(elt.getName())) { + try { + final long currMaxAge = Long.parseLong(elt.getValue()); + if (maxage == -1 || currMaxAge < maxage) { + maxage = currMaxAge; + } + } catch (final NumberFormatException nfe) { + // be conservative if can't parse + maxage = 0; + } + } + } + } + return maxage; + } + + protected Date getExpirationDate(final HttpCacheEntry entry) { + final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES); + if (expiresHeader == null) { + return null; + } + return DateUtils.parseDate(expiresHeader.getValue()); + } + + public boolean hasCacheControlDirective(final HttpCacheEntry entry, + final String directive) { + for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (directive.equalsIgnoreCase(elt.getName())) { + return true; + } + } + } + return false; + } + + public long getStalenessSecs(final HttpCacheEntry entry, final Date now) { + final long age = getCurrentAgeSecs(entry, now); + final long freshness = getFreshnessLifetimeSecs(entry); + if (age <= freshness) { + return 0L; + } + return (age - freshness); + } + + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java new file mode 100644 index 000000000..500da01dc --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java @@ -0,0 +1,96 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; + +/** + * Determines if an HttpRequest is allowed to be served from the cache. + * + * @since 4.1 + */ +@Immutable +class CacheableRequestPolicy { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** + * Determines if an HttpRequest can be served from the cache. + * + * @param request + * an HttpRequest + * @return boolean Is it possible to serve this request from cache + */ + public boolean isServableFromCache(final HttpRequest request) { + final String method = request.getRequestLine().getMethod(); + + final ProtocolVersion pv = request.getRequestLine().getProtocolVersion(); + if (HttpVersion.HTTP_1_1.compareToVersion(pv) != 0) { + log.trace("non-HTTP/1.1 request was not serveable from cache"); + return false; + } + + if (!method.equals(HeaderConstants.GET_METHOD)) { + log.trace("non-GET request was not serveable from cache"); + return false; + } + + if (request.getHeaders(HeaderConstants.PRAGMA).length > 0) { + log.trace("request with Pragma header was not serveable from cache"); + return false; + } + + final Header[] cacheControlHeaders = request.getHeaders(HeaderConstants.CACHE_CONTROL); + for (final Header cacheControl : cacheControlHeaders) { + for (final HeaderElement cacheControlElement : cacheControl.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_STORE.equalsIgnoreCase(cacheControlElement + .getName())) { + log.trace("Request with no-store was not serveable from cache"); + return false; + } + + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(cacheControlElement + .getName())) { + log.trace("Request with no-cache was not serveable from cache"); + return false; + } + } + } + + log.trace("Request was serveable from cache"); + return true; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java new file mode 100644 index 000000000..f42529e57 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java @@ -0,0 +1,166 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.message.BasicHeader; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * Rebuilds an {@link HttpResponse} from a {@link net.sf.ehcache.CacheEntry} + * + * @since 4.1 + */ +@Immutable +class CachedHttpResponseGenerator { + + private final CacheValidityPolicy validityStrategy; + + CachedHttpResponseGenerator(final CacheValidityPolicy validityStrategy) { + super(); + this.validityStrategy = validityStrategy; + } + + CachedHttpResponseGenerator() { + this(new CacheValidityPolicy()); + } + + /** + * If I was able to use a {@link CacheEntity} to response to the {@link ch.boye.httpclientandroidlib.HttpRequest} then + * generate an {@link HttpResponse} based on the cache entry. + * @param entry + * {@link CacheEntity} to transform into an {@link HttpResponse} + * @return {@link HttpResponse} that was constructed + */ + CloseableHttpResponse generateResponse(final HttpCacheEntry entry) { + + final Date now = new Date(); + final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, entry + .getStatusCode(), entry.getReasonPhrase()); + + response.setHeaders(entry.getAllHeaders()); + + if (entry.getResource() != null) { + final HttpEntity entity = new CacheEntity(entry); + addMissingContentLengthHeader(response, entity); + response.setEntity(entity); + } + + final long age = this.validityStrategy.getCurrentAgeSecs(entry, now); + if (age > 0) { + if (age >= Integer.MAX_VALUE) { + response.setHeader(HeaderConstants.AGE, "2147483648"); + } else { + response.setHeader(HeaderConstants.AGE, "" + ((int) age)); + } + } + + return Proxies.enhanceResponse(response); + } + + /** + * Generate a 304 - Not Modified response from a {@link CacheEntity}. This should be + * used to respond to conditional requests, when the entry exists or has been re-validated. + */ + CloseableHttpResponse generateNotModifiedResponse(final HttpCacheEntry entry) { + + final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, + HttpStatus.SC_NOT_MODIFIED, "Not Modified"); + + // The response MUST include the following headers + // (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) + + // - Date, unless its omission is required by section 14.8.1 + Header dateHeader = entry.getFirstHeader(HTTP.DATE_HEADER); + if (dateHeader == null) { + dateHeader = new BasicHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date())); + } + response.addHeader(dateHeader); + + // - ETag and/or Content-Location, if the header would have been sent + // in a 200 response to the same request + final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG); + if (etagHeader != null) { + response.addHeader(etagHeader); + } + + final Header contentLocationHeader = entry.getFirstHeader("Content-Location"); + if (contentLocationHeader != null) { + response.addHeader(contentLocationHeader); + } + + // - Expires, Cache-Control, and/or Vary, if the field-value might + // differ from that sent in any previous response for the same + // variant + final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES); + if (expiresHeader != null) { + response.addHeader(expiresHeader); + } + + final Header cacheControlHeader = entry.getFirstHeader(HeaderConstants.CACHE_CONTROL); + if (cacheControlHeader != null) { + response.addHeader(cacheControlHeader); + } + + final Header varyHeader = entry.getFirstHeader(HeaderConstants.VARY); + if (varyHeader != null) { + response.addHeader(varyHeader); + } + + return Proxies.enhanceResponse(response); + } + + private void addMissingContentLengthHeader(final HttpResponse response, final HttpEntity entity) { + if (transferEncodingIsPresent(response)) { + return; + } + + Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN); + if (contentLength == null) { + contentLength = new BasicHeader(HTTP.CONTENT_LEN, Long.toString(entity + .getContentLength())); + response.setHeader(contentLength); + } + } + + private boolean transferEncodingIsPresent(final HttpResponse response) { + final Header hdr = response.getFirstHeader(HTTP.TRANSFER_ENCODING); + return hdr != null; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java new file mode 100644 index 000000000..a289729d3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java @@ -0,0 +1,346 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; + +/** + * Determines whether a given {@link HttpCacheEntry} is suitable to be + * used as a response for a given {@link HttpRequest}. + * + * @since 4.1 + */ +@Immutable +class CachedResponseSuitabilityChecker { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final boolean sharedCache; + private final boolean useHeuristicCaching; + private final float heuristicCoefficient; + private final long heuristicDefaultLifetime; + private final CacheValidityPolicy validityStrategy; + + CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy, + final CacheConfig config) { + super(); + this.validityStrategy = validityStrategy; + this.sharedCache = config.isSharedCache(); + this.useHeuristicCaching = config.isHeuristicCachingEnabled(); + this.heuristicCoefficient = config.getHeuristicCoefficient(); + this.heuristicDefaultLifetime = config.getHeuristicDefaultLifetime(); + } + + CachedResponseSuitabilityChecker(final CacheConfig config) { + this(new CacheValidityPolicy(), config); + } + + private boolean isFreshEnough(final HttpCacheEntry entry, final HttpRequest request, final Date now) { + if (validityStrategy.isResponseFresh(entry, now)) { + return true; + } + if (useHeuristicCaching && + validityStrategy.isResponseHeuristicallyFresh(entry, now, heuristicCoefficient, heuristicDefaultLifetime)) { + return true; + } + if (originInsistsOnFreshness(entry)) { + return false; + } + final long maxstale = getMaxStale(request); + if (maxstale == -1) { + return false; + } + return (maxstale > validityStrategy.getStalenessSecs(entry, now)); + } + + private boolean originInsistsOnFreshness(final HttpCacheEntry entry) { + if (validityStrategy.mustRevalidate(entry)) { + return true; + } + if (!sharedCache) { + return false; + } + return validityStrategy.proxyRevalidate(entry) || + validityStrategy.hasCacheControlDirective(entry, "s-maxage"); + } + + private long getMaxStale(final HttpRequest request) { + long maxstale = -1; + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) { + if ((elt.getValue() == null || "".equals(elt.getValue().trim())) + && maxstale == -1) { + maxstale = Long.MAX_VALUE; + } else { + try { + long val = Long.parseLong(elt.getValue()); + if (val < 0) { + val = 0; + } + if (maxstale == -1 || val < maxstale) { + maxstale = val; + } + } catch (final NumberFormatException nfe) { + // err on the side of preserving semantic transparency + maxstale = 0; + } + } + } + } + } + return maxstale; + } + + /** + * Determine if I can utilize a {@link HttpCacheEntry} to respond to the given + * {@link HttpRequest} + * + * @param host + * {@link HttpHost} + * @param request + * {@link HttpRequest} + * @param entry + * {@link HttpCacheEntry} + * @param now + * Right now in time + * @return boolean yes/no answer + */ + public boolean canCachedResponseBeUsed(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry, final Date now) { + + if (!isFreshEnough(entry, request, now)) { + log.trace("Cache entry was not fresh enough"); + return false; + } + + if (!validityStrategy.contentLengthHeaderMatchesActualLength(entry)) { + log.debug("Cache entry Content-Length and header information do not match"); + return false; + } + + if (hasUnsupportedConditionalHeaders(request)) { + log.debug("Request contained conditional headers we don't handle"); + return false; + } + + if (!isConditional(request) && entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { + return false; + } + + if (isConditional(request) && !allConditionalsMatch(request, entry, now)) { + return false; + } + + for (final Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for (final HeaderElement elt : ccHdr.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) { + log.trace("Response contained NO CACHE directive, cache was not suitable"); + return false; + } + + if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) { + log.trace("Response contained NO STORE directive, cache was not suitable"); + return false; + } + + if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) { + try { + final int maxage = Integer.parseInt(elt.getValue()); + if (validityStrategy.getCurrentAgeSecs(entry, now) > maxage) { + log.trace("Response from cache was NOT suitable due to max age"); + return false; + } + } catch (final NumberFormatException ex) { + // err conservatively + log.debug("Response from cache was malformed" + ex.getMessage()); + return false; + } + } + + if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) { + try { + final int maxstale = Integer.parseInt(elt.getValue()); + if (validityStrategy.getFreshnessLifetimeSecs(entry) > maxstale) { + log.trace("Response from cache was not suitable due to Max stale freshness"); + return false; + } + } catch (final NumberFormatException ex) { + // err conservatively + log.debug("Response from cache was malformed: " + ex.getMessage()); + return false; + } + } + + if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())) { + try { + final long minfresh = Long.parseLong(elt.getValue()); + if (minfresh < 0L) { + return false; + } + final long age = validityStrategy.getCurrentAgeSecs(entry, now); + final long freshness = validityStrategy.getFreshnessLifetimeSecs(entry); + if (freshness - age < minfresh) { + log.trace("Response from cache was not suitable due to min fresh " + + "freshness requirement"); + return false; + } + } catch (final NumberFormatException ex) { + // err conservatively + log.debug("Response from cache was malformed: " + ex.getMessage()); + return false; + } + } + } + } + + log.trace("Response from cache was suitable"); + return true; + } + + /** + * Is this request the type of conditional request we support? + * @param request The current httpRequest being made + * @return {@code true} if the request is supported + */ + public boolean isConditional(final HttpRequest request) { + return hasSupportedEtagValidator(request) || hasSupportedLastModifiedValidator(request); + } + + /** + * Check that conditionals that are part of this request match + * @param request The current httpRequest being made + * @param entry the cache entry + * @param now right NOW in time + * @return {@code true} if the request matches all conditionals + */ + public boolean allConditionalsMatch(final HttpRequest request, final HttpCacheEntry entry, final Date now) { + final boolean hasEtagValidator = hasSupportedEtagValidator(request); + final boolean hasLastModifiedValidator = hasSupportedLastModifiedValidator(request); + + final boolean etagValidatorMatches = (hasEtagValidator) && etagValidatorMatches(request, entry); + final boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && lastModifiedValidatorMatches(request, entry, now); + + if ((hasEtagValidator && hasLastModifiedValidator) + && !(etagValidatorMatches && lastModifiedValidatorMatches)) { + return false; + } else if (hasEtagValidator && !etagValidatorMatches) { + return false; + } + + if (hasLastModifiedValidator && !lastModifiedValidatorMatches) { + return false; + } + return true; + } + + private boolean hasUnsupportedConditionalHeaders(final HttpRequest request) { + return (request.getFirstHeader(HeaderConstants.IF_RANGE) != null + || request.getFirstHeader(HeaderConstants.IF_MATCH) != null + || hasValidDateField(request, HeaderConstants.IF_UNMODIFIED_SINCE)); + } + + private boolean hasSupportedEtagValidator(final HttpRequest request) { + return request.containsHeader(HeaderConstants.IF_NONE_MATCH); + } + + private boolean hasSupportedLastModifiedValidator(final HttpRequest request) { + return hasValidDateField(request, HeaderConstants.IF_MODIFIED_SINCE); + } + + /** + * Check entry against If-None-Match + * @param request The current httpRequest being made + * @param entry the cache entry + * @return boolean does the etag validator match + */ + private boolean etagValidatorMatches(final HttpRequest request, final HttpCacheEntry entry) { + final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG); + final String etag = (etagHeader != null) ? etagHeader.getValue() : null; + final Header[] ifNoneMatch = request.getHeaders(HeaderConstants.IF_NONE_MATCH); + if (ifNoneMatch != null) { + for (final Header h : ifNoneMatch) { + for (final HeaderElement elt : h.getElements()) { + final String reqEtag = elt.toString(); + if (("*".equals(reqEtag) && etag != null) + || reqEtag.equals(etag)) { + return true; + } + } + } + } + return false; + } + + /** + * Check entry against If-Modified-Since, if If-Modified-Since is in the future it is invalid as per + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + * @param request The current httpRequest being made + * @param entry the cache entry + * @param now right NOW in time + * @return boolean Does the last modified header match + */ + private boolean lastModifiedValidatorMatches(final HttpRequest request, final HttpCacheEntry entry, final Date now) { + final Header lastModifiedHeader = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED); + Date lastModified = null; + if (lastModifiedHeader != null) { + lastModified = DateUtils.parseDate(lastModifiedHeader.getValue()); + } + if (lastModified == null) { + return false; + } + + for (final Header h : request.getHeaders(HeaderConstants.IF_MODIFIED_SINCE)) { + final Date ifModifiedSince = DateUtils.parseDate(h.getValue()); + if (ifModifiedSince != null) { + if (ifModifiedSince.after(now) || lastModified.after(ifModifiedSince)) { + return false; + } + } + } + return true; + } + + private boolean hasValidDateField(final HttpRequest request, final String headerName) { + for(final Header h : request.getHeaders(headerName)) { + final Date date = DateUtils.parseDate(h.getValue()); + return date != null; + } + return false; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java new file mode 100644 index 000000000..cf9b8154a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java @@ -0,0 +1,870 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.CacheResponseStatus; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheContext; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.VersionInfo; + +/** + * Request executor in the request execution chain that is responsible for + * transparent client-side caching. The current implementation is conditionally + * compliant with HTTP/1.1 (meaning all the MUST and MUST NOTs are obeyed), + * although quite a lot, though not all, of the SHOULDs and SHOULD NOTs + * are obeyed too. + * <p/> + * Folks that would like to experiment with alternative storage backends + * should look at the {@link HttpCacheStorage} interface and the related + * package documentation there. You may also be interested in the provided + * {@link ch.boye.httpclientandroidlib.impl.client.cache.ehcache.EhcacheHttpCacheStorage + * EhCache} and {@link + * ch.boye.httpclientandroidlib.impl.client.cache.memcached.MemcachedHttpCacheStorage + * memcached} storage backends. + * <p/> + * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 4.3 + */ +@ThreadSafe // So long as the responseCache implementation is threadsafe +public class CachingExec implements ClientExecChain { + + private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false; + + private final AtomicLong cacheHits = new AtomicLong(); + private final AtomicLong cacheMisses = new AtomicLong(); + private final AtomicLong cacheUpdates = new AtomicLong(); + + private final Map<ProtocolVersion, String> viaHeaders = new HashMap<ProtocolVersion, String>(4); + + private final CacheConfig cacheConfig; + private final ClientExecChain backend; + private final HttpCache responseCache; + private final CacheValidityPolicy validityPolicy; + private final CachedHttpResponseGenerator responseGenerator; + private final CacheableRequestPolicy cacheableRequestPolicy; + private final CachedResponseSuitabilityChecker suitabilityChecker; + private final ConditionalRequestBuilder conditionalRequestBuilder; + private final ResponseProtocolCompliance responseCompliance; + private final RequestProtocolCompliance requestCompliance; + private final ResponseCachingPolicy responseCachingPolicy; + + private final AsynchronousValidator asynchRevalidator; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + public CachingExec( + final ClientExecChain backend, + final HttpCache cache, + final CacheConfig config) { + this(backend, cache, config, null); + } + + public CachingExec( + final ClientExecChain backend, + final HttpCache cache, + final CacheConfig config, + final AsynchronousValidator asynchRevalidator) { + super(); + Args.notNull(backend, "HTTP backend"); + Args.notNull(cache, "HttpCache"); + this.cacheConfig = config != null ? config : CacheConfig.DEFAULT; + this.backend = backend; + this.responseCache = cache; + this.validityPolicy = new CacheValidityPolicy(); + this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy); + this.cacheableRequestPolicy = new CacheableRequestPolicy(); + this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig); + this.conditionalRequestBuilder = new ConditionalRequestBuilder(); + this.responseCompliance = new ResponseProtocolCompliance(); + this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed()); + this.responseCachingPolicy = new ResponseCachingPolicy( + this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(), + this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled()); + this.asynchRevalidator = asynchRevalidator; + } + + public CachingExec( + final ClientExecChain backend, + final ResourceFactory resourceFactory, + final HttpCacheStorage storage, + final CacheConfig config) { + this(backend, new BasicHttpCache(resourceFactory, storage, config), config); + } + + public CachingExec(final ClientExecChain backend) { + this(backend, new BasicHttpCache(), CacheConfig.DEFAULT); + } + + CachingExec( + final ClientExecChain backend, + final HttpCache responseCache, + final CacheValidityPolicy validityPolicy, + final ResponseCachingPolicy responseCachingPolicy, + final CachedHttpResponseGenerator responseGenerator, + final CacheableRequestPolicy cacheableRequestPolicy, + final CachedResponseSuitabilityChecker suitabilityChecker, + final ConditionalRequestBuilder conditionalRequestBuilder, + final ResponseProtocolCompliance responseCompliance, + final RequestProtocolCompliance requestCompliance, + final CacheConfig config, + final AsynchronousValidator asynchRevalidator) { + this.cacheConfig = config != null ? config : CacheConfig.DEFAULT; + this.backend = backend; + this.responseCache = responseCache; + this.validityPolicy = validityPolicy; + this.responseCachingPolicy = responseCachingPolicy; + this.responseGenerator = responseGenerator; + this.cacheableRequestPolicy = cacheableRequestPolicy; + this.suitabilityChecker = suitabilityChecker; + this.conditionalRequestBuilder = conditionalRequestBuilder; + this.responseCompliance = responseCompliance; + this.requestCompliance = requestCompliance; + this.asynchRevalidator = asynchRevalidator; + } + + /** + * Reports the number of times that the cache successfully responded + * to an {@link HttpRequest} without contacting the origin server. + * @return the number of cache hits + */ + public long getCacheHits() { + return cacheHits.get(); + } + + /** + * Reports the number of times that the cache contacted the origin + * server because it had no appropriate response cached. + * @return the number of cache misses + */ + public long getCacheMisses() { + return cacheMisses.get(); + } + + /** + * Reports the number of times that the cache was able to satisfy + * a response by revalidating an existing but stale cache entry. + * @return the number of cache revalidations + */ + public long getCacheUpdates() { + return cacheUpdates.get(); + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request) throws IOException, HttpException { + return execute(route, request, HttpClientContext.create(), null); + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context) throws IOException, HttpException { + return execute(route, request, context, null); + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + + final HttpHost target = context.getTargetHost(); + final String via = generateViaHeader(request.getOriginal()); + + // default response context + setResponseStatus(context, CacheResponseStatus.CACHE_MISS); + + if (clientRequestsOurOptions(request)) { + setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); + return Proxies.enhanceResponse(new OptionsHttp11Response()); + } + + final HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context); + if (fatalErrorResponse != null) { + return Proxies.enhanceResponse(fatalErrorResponse); + } + + requestCompliance.makeRequestCompliant(request); + request.addHeader("Via",via); + + flushEntriesInvalidatedByRequest(context.getTargetHost(), request); + + if (!cacheableRequestPolicy.isServableFromCache(request)) { + log.debug("Request is not servable from cache"); + return callBackend(route, request, context, execAware); + } + + final HttpCacheEntry entry = satisfyFromCache(target, request); + if (entry == null) { + log.debug("Cache miss"); + return handleCacheMiss(route, request, context, execAware); + } else { + return handleCacheHit(route, request, context, execAware, entry); + } + } + + private CloseableHttpResponse handleCacheHit( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry entry) throws IOException, HttpException { + final HttpHost target = context.getTargetHost(); + recordCacheHit(target, request); + CloseableHttpResponse out = null; + final Date now = getCurrentDate(); + if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) { + log.debug("Cache hit"); + out = generateCachedResponse(request, context, entry, now); + } else if (!mayCallBackend(request)) { + log.debug("Cache entry not suitable but only-if-cached requested"); + out = generateGatewayTimeout(context); + } else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED + && !suitabilityChecker.isConditional(request))) { + log.debug("Revalidating cache entry"); + return revalidateCacheEntry(route, request, context, execAware, entry, now); + } else { + log.debug("Cache entry not usable; calling backend"); + return callBackend(route, request, context, execAware); + } + context.setAttribute(HttpClientContext.HTTP_ROUTE, route); + context.setAttribute(HttpClientContext.HTTP_TARGET_HOST, target); + context.setAttribute(HttpClientContext.HTTP_REQUEST, request); + context.setAttribute(HttpClientContext.HTTP_RESPONSE, out); + context.setAttribute(HttpClientContext.HTTP_REQ_SENT, Boolean.TRUE); + return out; + } + + private CloseableHttpResponse revalidateCacheEntry( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry entry, + final Date now) throws HttpException { + + try { + if (asynchRevalidator != null + && !staleResponseNotAllowed(request, entry, now) + && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) { + log.trace("Serving stale with asynchronous revalidation"); + final CloseableHttpResponse resp = generateCachedResponse(request, context, entry, now); + asynchRevalidator.revalidateCacheEntry(this, route, request, context, execAware, entry); + return resp; + } + return revalidateCacheEntry(route, request, context, execAware, entry); + } catch (final IOException ioex) { + return handleRevalidationFailure(request, context, entry, now); + } + } + + private CloseableHttpResponse handleCacheMiss( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + final HttpHost target = context.getTargetHost(); + recordCacheMiss(target, request); + + if (!mayCallBackend(request)) { + return Proxies.enhanceResponse( + new BasicHttpResponse( + HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout")); + } + + final Map<String, Variant> variants = getExistingCacheVariants(target, request); + if (variants != null && variants.size() > 0) { + return negotiateResponseFromVariants(route, request, context, + execAware, variants); + } + + return callBackend(route, request, context, execAware); + } + + private HttpCacheEntry satisfyFromCache( + final HttpHost target, final HttpRequestWrapper request) { + HttpCacheEntry entry = null; + try { + entry = responseCache.getCacheEntry(target, request); + } catch (final IOException ioe) { + log.warn("Unable to retrieve entries from cache", ioe); + } + return entry; + } + + private HttpResponse getFatallyNoncompliantResponse( + final HttpRequestWrapper request, + final HttpContext context) { + HttpResponse fatalErrorResponse = null; + final List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request); + + for (final RequestProtocolError error : fatalError) { + setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); + fatalErrorResponse = requestCompliance.getErrorForRequest(error); + } + return fatalErrorResponse; + } + + private Map<String, Variant> getExistingCacheVariants( + final HttpHost target, + final HttpRequestWrapper request) { + Map<String,Variant> variants = null; + try { + variants = responseCache.getVariantCacheEntriesWithEtags(target, request); + } catch (final IOException ioe) { + log.warn("Unable to retrieve variant entries from cache", ioe); + } + return variants; + } + + private void recordCacheMiss(final HttpHost target, final HttpRequestWrapper request) { + cacheMisses.getAndIncrement(); + if (log.isTraceEnabled()) { + final RequestLine rl = request.getRequestLine(); + log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]"); + } + } + + private void recordCacheHit(final HttpHost target, final HttpRequestWrapper request) { + cacheHits.getAndIncrement(); + if (log.isTraceEnabled()) { + final RequestLine rl = request.getRequestLine(); + log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]"); + } + } + + private void recordCacheUpdate(final HttpContext context) { + cacheUpdates.getAndIncrement(); + setResponseStatus(context, CacheResponseStatus.VALIDATED); + } + + private void flushEntriesInvalidatedByRequest( + final HttpHost target, + final HttpRequestWrapper request) { + try { + responseCache.flushInvalidatedCacheEntriesFor(target, request); + } catch (final IOException ioe) { + log.warn("Unable to flush invalidated entries from cache", ioe); + } + } + + private CloseableHttpResponse generateCachedResponse(final HttpRequestWrapper request, + final HttpContext context, final HttpCacheEntry entry, final Date now) { + final CloseableHttpResponse cachedResponse; + if (request.containsHeader(HeaderConstants.IF_NONE_MATCH) + || request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) { + cachedResponse = responseGenerator.generateNotModifiedResponse(entry); + } else { + cachedResponse = responseGenerator.generateResponse(entry); + } + setResponseStatus(context, CacheResponseStatus.CACHE_HIT); + if (validityPolicy.getStalenessSecs(entry, now) > 0L) { + cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\""); + } + return cachedResponse; + } + + private CloseableHttpResponse handleRevalidationFailure( + final HttpRequestWrapper request, + final HttpContext context, + final HttpCacheEntry entry, + final Date now) { + if (staleResponseNotAllowed(request, entry, now)) { + return generateGatewayTimeout(context); + } else { + return unvalidatedCacheHit(context, entry); + } + } + + private CloseableHttpResponse generateGatewayTimeout( + final HttpContext context) { + setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); + return Proxies.enhanceResponse(new BasicHttpResponse( + HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, + "Gateway Timeout")); + } + + private CloseableHttpResponse unvalidatedCacheHit( + final HttpContext context, final HttpCacheEntry entry) { + final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(entry); + setResponseStatus(context, CacheResponseStatus.CACHE_HIT); + cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\""); + return cachedResponse; + } + + private boolean staleResponseNotAllowed( + final HttpRequestWrapper request, + final HttpCacheEntry entry, + final Date now) { + return validityPolicy.mustRevalidate(entry) + || (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry)) + || explicitFreshnessRequest(request, entry, now); + } + + private boolean mayCallBackend(final HttpRequestWrapper request) { + for (final Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for (final HeaderElement elt : h.getElements()) { + if ("only-if-cached".equals(elt.getName())) { + log.trace("Request marked only-if-cached"); + return false; + } + } + } + return true; + } + + private boolean explicitFreshnessRequest( + final HttpRequestWrapper request, + final HttpCacheEntry entry, + final Date now) { + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) { + try { + final int maxstale = Integer.parseInt(elt.getValue()); + final long age = validityPolicy.getCurrentAgeSecs(entry, now); + final long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry); + if (age - lifetime > maxstale) { + return true; + } + } catch (final NumberFormatException nfe) { + return true; + } + } else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName()) + || HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) { + return true; + } + } + } + return false; + } + + private String generateViaHeader(final HttpMessage msg) { + + final ProtocolVersion pv = msg.getProtocolVersion(); + final String existingEntry = viaHeaders.get(pv); + if (existingEntry != null) { + return existingEntry; + } + + final VersionInfo vi = VersionInfo.loadVersionInfo("ch.boye.httpclientandroidlib.client", getClass().getClassLoader()); + final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; + + String value; + if ("http".equalsIgnoreCase(pv.getProtocol())) { + value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getMajor(), pv.getMinor(), + release); + } else { + value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), pv.getMajor(), + pv.getMinor(), release); + } + viaHeaders.put(pv, value); + + return value; + } + + private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) { + if (context != null) { + context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value); + } + } + + /** + * Reports whether this {@code CachingHttpClient} implementation + * supports byte-range requests as specified by the {@code Range} + * and {@code Content-Range} headers. + * @return {@code true} if byte-range requests are supported + */ + public boolean supportsRangeAndContentRangeHeaders() { + return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS; + } + + Date getCurrentDate() { + return new Date(); + } + + boolean clientRequestsOurOptions(final HttpRequest request) { + final RequestLine line = request.getRequestLine(); + + if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod())) { + return false; + } + + if (!"*".equals(line.getUri())) { + return false; + } + + if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue())) { + return false; + } + + return true; + } + + CloseableHttpResponse callBackend( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + + final Date requestDate = getCurrentDate(); + + log.trace("Calling the backend"); + final CloseableHttpResponse backendResponse = backend.execute(route, request, context, execAware); + try { + backendResponse.addHeader("Via", generateViaHeader(backendResponse)); + return handleBackendResponse(route, request, context, execAware, + requestDate, getCurrentDate(), backendResponse); + } catch (final IOException ex) { + backendResponse.close(); + throw ex; + } catch (final RuntimeException ex) { + backendResponse.close(); + throw ex; + } + } + + private boolean revalidationResponseIsTooOld(final HttpResponse backendResponse, + final HttpCacheEntry cacheEntry) { + final Header entryDateHeader = cacheEntry.getFirstHeader(HTTP.DATE_HEADER); + final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER); + if (entryDateHeader != null && responseDateHeader != null) { + final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue()); + final Date respDate = DateUtils.parseDate(responseDateHeader.getValue()); + if (entryDate == null || respDate == null) { + // either backend response or cached entry did not have a valid + // Date header, so we can't tell if they are out of order + // according to the origin clock; thus we can skip the + // unconditional retry recommended in 13.2.6 of RFC 2616. + return false; + } + if (respDate.before(entryDate)) { + return true; + } + } + return false; + } + + CloseableHttpResponse negotiateResponseFromVariants( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final Map<String, Variant> variants) throws IOException, HttpException { + final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder + .buildConditionalRequestFromVariants(request, variants); + + final Date requestDate = getCurrentDate(); + final CloseableHttpResponse backendResponse = backend.execute( + route, conditionalRequest, context, execAware); + try { + final Date responseDate = getCurrentDate(); + + backendResponse.addHeader("Via", generateViaHeader(backendResponse)); + + if (backendResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_MODIFIED) { + return handleBackendResponse( + route, request, context, execAware, + requestDate, responseDate, backendResponse); + } + + final Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG); + if (resultEtagHeader == null) { + log.warn("304 response did not contain ETag"); + IOUtils.consume(backendResponse.getEntity()); + backendResponse.close(); + return callBackend(route, request, context, execAware); + } + + final String resultEtag = resultEtagHeader.getValue(); + final Variant matchingVariant = variants.get(resultEtag); + if (matchingVariant == null) { + log.debug("304 response did not contain ETag matching one sent in If-None-Match"); + IOUtils.consume(backendResponse.getEntity()); + backendResponse.close(); + return callBackend(route, request, context, execAware); + } + + final HttpCacheEntry matchedEntry = matchingVariant.getEntry(); + + if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) { + IOUtils.consume(backendResponse.getEntity()); + backendResponse.close(); + return retryRequestUnconditionally(route, request, context, execAware, matchedEntry); + } + + recordCacheUpdate(context); + + final HttpCacheEntry responseEntry = getUpdatedVariantEntry( + context.getTargetHost(), conditionalRequest, requestDate, responseDate, + backendResponse, matchingVariant, matchedEntry); + backendResponse.close(); + + final CloseableHttpResponse resp = responseGenerator.generateResponse(responseEntry); + tryToUpdateVariantMap(context.getTargetHost(), request, matchingVariant); + + if (shouldSendNotModifiedResponse(request, responseEntry)) { + return responseGenerator.generateNotModifiedResponse(responseEntry); + } + return resp; + } catch (final IOException ex) { + backendResponse.close(); + throw ex; + } catch (final RuntimeException ex) { + backendResponse.close(); + throw ex; + } + } + + private CloseableHttpResponse retryRequestUnconditionally( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry matchedEntry) throws IOException, HttpException { + final HttpRequestWrapper unconditional = conditionalRequestBuilder + .buildUnconditionalRequest(request, matchedEntry); + return callBackend(route, unconditional, context, execAware); + } + + private HttpCacheEntry getUpdatedVariantEntry( + final HttpHost target, + final HttpRequestWrapper conditionalRequest, + final Date requestDate, + final Date responseDate, + final CloseableHttpResponse backendResponse, + final Variant matchingVariant, + final HttpCacheEntry matchedEntry) throws IOException { + HttpCacheEntry responseEntry = matchedEntry; + try { + responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest, + matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey()); + } catch (final IOException ioe) { + log.warn("Could not update cache entry", ioe); + } finally { + backendResponse.close(); + } + return responseEntry; + } + + private void tryToUpdateVariantMap( + final HttpHost target, + final HttpRequestWrapper request, + final Variant matchingVariant) { + try { + responseCache.reuseVariantEntryFor(target, request, matchingVariant); + } catch (final IOException ioe) { + log.warn("Could not update cache entry to reuse variant", ioe); + } + } + + private boolean shouldSendNotModifiedResponse( + final HttpRequestWrapper request, + final HttpCacheEntry responseEntry) { + return (suitabilityChecker.isConditional(request) + && suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date())); + } + + CloseableHttpResponse revalidateCacheEntry( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final HttpCacheEntry cacheEntry) throws IOException, HttpException { + + final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry); + + Date requestDate = getCurrentDate(); + CloseableHttpResponse backendResponse = backend.execute( + route, conditionalRequest, context, execAware); + Date responseDate = getCurrentDate(); + + if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) { + backendResponse.close(); + final HttpRequestWrapper unconditional = conditionalRequestBuilder + .buildUnconditionalRequest(request, cacheEntry); + requestDate = getCurrentDate(); + backendResponse = backend.execute(route, unconditional, context, execAware); + responseDate = getCurrentDate(); + } + + backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse)); + + final int statusCode = backendResponse.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) { + recordCacheUpdate(context); + } + + if (statusCode == HttpStatus.SC_NOT_MODIFIED) { + final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry( + context.getTargetHost(), request, cacheEntry, + backendResponse, requestDate, responseDate); + if (suitabilityChecker.isConditional(request) + && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) { + return responseGenerator + .generateNotModifiedResponse(updatedEntry); + } + return responseGenerator.generateResponse(updatedEntry); + } + + if (staleIfErrorAppliesTo(statusCode) + && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate()) + && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) { + try { + final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry); + cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\""); + return cachedResponse; + } finally { + backendResponse.close(); + } + } + return handleBackendResponse( + route, conditionalRequest, context, execAware, + requestDate, responseDate, backendResponse); + } + + private boolean staleIfErrorAppliesTo(final int statusCode) { + return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR + || statusCode == HttpStatus.SC_BAD_GATEWAY + || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE + || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT; + } + + CloseableHttpResponse handleBackendResponse( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware, + final Date requestDate, + final Date responseDate, + final CloseableHttpResponse backendResponse) throws IOException { + + log.trace("Handling Backend response"); + responseCompliance.ensureProtocolCompliance(request, backendResponse); + + final HttpHost target = context.getTargetHost(); + final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse); + responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse); + if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) { + storeRequestIfModifiedSinceFor304Response(request, backendResponse); + return responseCache.cacheAndReturnResponse(target, request, + backendResponse, requestDate, responseDate); + } + if (!cacheable) { + try { + responseCache.flushCacheEntriesFor(target, request); + } catch (final IOException ioe) { + log.warn("Unable to flush invalid cache entries", ioe); + } + } + return backendResponse; + } + + /** + * For 304 Not modified responses, adds a "Last-Modified" header with the + * value of the "If-Modified-Since" header passed in the request. This + * header is required to be able to reuse match the cache entry for + * subsequent requests but as defined in http specifications it is not + * included in 304 responses by backend servers. This header will not be + * included in the resulting response. + */ + private void storeRequestIfModifiedSinceFor304Response( + final HttpRequest request, final HttpResponse backendResponse) { + if (backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { + final Header h = request.getFirstHeader("If-Modified-Since"); + if (h != null) { + backendResponse.addHeader("Last-Modified", h.getValue()); + } + } + } + + private boolean alreadyHaveNewerCacheEntry(final HttpHost target, final HttpRequestWrapper request, + final HttpResponse backendResponse) { + HttpCacheEntry existing = null; + try { + existing = responseCache.getCacheEntry(target, request); + } catch (final IOException ioe) { + // nop + } + if (existing == null) { + return false; + } + final Header entryDateHeader = existing.getFirstHeader(HTTP.DATE_HEADER); + if (entryDateHeader == null) { + return false; + } + final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER); + if (responseDateHeader == null) { + return false; + } + final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue()); + final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue()); + if (entryDate == null || responseDate == null) { + return false; + } + return responseDate.before(entryDate); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java new file mode 100644 index 000000000..385324f32 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java @@ -0,0 +1,149 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; + +import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder; +import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain; + +/** + * Builder for {@link ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient} + * instances capable of client-side caching. + * + * @since 4.3 + */ +public class CachingHttpClientBuilder extends HttpClientBuilder { + + private ResourceFactory resourceFactory; + private HttpCacheStorage storage; + private File cacheDir; + private CacheConfig cacheConfig; + private SchedulingStrategy schedulingStrategy; + private HttpCacheInvalidator httpCacheInvalidator; + + public static CachingHttpClientBuilder create() { + return new CachingHttpClientBuilder(); + } + + protected CachingHttpClientBuilder() { + super(); + } + + public final CachingHttpClientBuilder setResourceFactory( + final ResourceFactory resourceFactory) { + this.resourceFactory = resourceFactory; + return this; + } + + public final CachingHttpClientBuilder setHttpCacheStorage( + final HttpCacheStorage storage) { + this.storage = storage; + return this; + } + + public final CachingHttpClientBuilder setCacheDir( + final File cacheDir) { + this.cacheDir = cacheDir; + return this; + } + + public final CachingHttpClientBuilder setCacheConfig( + final CacheConfig cacheConfig) { + this.cacheConfig = cacheConfig; + return this; + } + + public final CachingHttpClientBuilder setSchedulingStrategy( + final SchedulingStrategy schedulingStrategy) { + this.schedulingStrategy = schedulingStrategy; + return this; + } + + public final CachingHttpClientBuilder setHttpCacheInvalidator( + final HttpCacheInvalidator cacheInvalidator) { + this.httpCacheInvalidator = cacheInvalidator; + return this; + } + + @Override + protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) { + final CacheConfig config = this.cacheConfig != null ? this.cacheConfig : CacheConfig.DEFAULT; + ResourceFactory resourceFactory = this.resourceFactory; + if (resourceFactory == null) { + if (this.cacheDir == null) { + resourceFactory = new HeapResourceFactory(); + } else { + resourceFactory = new FileResourceFactory(cacheDir); + } + } + HttpCacheStorage storage = this.storage; + if (storage == null) { + if (this.cacheDir == null) { + storage = new BasicHttpCacheStorage(config); + } else { + final ManagedHttpCacheStorage managedStorage = new ManagedHttpCacheStorage(config); + addCloseable(managedStorage); + storage = managedStorage; + } + } + final AsynchronousValidator revalidator = createAsynchronousRevalidator(config); + final CacheKeyGenerator uriExtractor = new CacheKeyGenerator(); + + HttpCacheInvalidator cacheInvalidator = this.httpCacheInvalidator; + if (cacheInvalidator == null) { + cacheInvalidator = new CacheInvalidator(uriExtractor, storage); + } + + return new CachingExec(mainExec, + new BasicHttpCache( + resourceFactory, + storage, config, + uriExtractor, + cacheInvalidator), config, revalidator); + } + + private AsynchronousValidator createAsynchronousRevalidator(final CacheConfig config) { + if (config.getAsynchronousWorkersMax() > 0) { + final SchedulingStrategy configuredSchedulingStrategy = createSchedulingStrategy(config); + final AsynchronousValidator revalidator = new AsynchronousValidator( + configuredSchedulingStrategy); + addCloseable(revalidator); + return revalidator; + } + return null; + } + + @SuppressWarnings("resource") + private SchedulingStrategy createSchedulingStrategy(final CacheConfig config) { + return schedulingStrategy != null ? schedulingStrategy : new ImmediateSchedulingStrategy(config); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java new file mode 100644 index 000000000..328c147f3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java @@ -0,0 +1,74 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; + +/** + * Factory methods for {@link CloseableHttpClient} instances + * capable of client-side caching. + * + * @since 4.3 + */ +@Immutable +public class CachingHttpClients { + + private CachingHttpClients() { + super(); + } + + /** + * Creates builder object for construction of custom + * {@link CloseableHttpClient} instances. + */ + public static CachingHttpClientBuilder custom() { + return CachingHttpClientBuilder.create(); + } + + /** + * Creates {@link CloseableHttpClient} instance that uses a memory bound + * response cache. + */ + public static CloseableHttpClient createMemoryBound() { + return CachingHttpClientBuilder.create().build(); + } + + /** + * Creates {@link CloseableHttpClient} instance that uses a file system + * bound response cache. + * + * @param cacheDir location of response cache. + */ + public static CloseableHttpClient createFileBound(final File cacheDir) { + return CachingHttpClientBuilder.create().setCacheDir(cacheDir).build(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java new file mode 100644 index 000000000..62bfaff3e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java @@ -0,0 +1,104 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity; +import ch.boye.httpclientandroidlib.util.Args; + +@NotThreadSafe +class CombinedEntity extends AbstractHttpEntity { + + private final Resource resource; + private final InputStream combinedStream; + + CombinedEntity(final Resource resource, final InputStream instream) throws IOException { + super(); + this.resource = resource; + this.combinedStream = new SequenceInputStream( + new ResourceStream(resource.getInputStream()), instream); + } + + public long getContentLength() { + return -1; + } + + public boolean isRepeatable() { + return false; + } + + public boolean isStreaming() { + return true; + } + + public InputStream getContent() throws IOException, IllegalStateException { + return this.combinedStream; + } + + public void writeTo(final OutputStream outstream) throws IOException { + Args.notNull(outstream, "Output stream"); + final InputStream instream = getContent(); + try { + int l; + final byte[] tmp = new byte[2048]; + while ((l = instream.read(tmp)) != -1) { + outstream.write(tmp, 0, l); + } + } finally { + instream.close(); + } + } + + private void dispose() { + this.resource.dispose(); + } + + class ResourceStream extends FilterInputStream { + + protected ResourceStream(final InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + dispose(); + } + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java new file mode 100644 index 000000000..0edc4fb85 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java @@ -0,0 +1,140 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; + +/** + * @since 4.1 + */ +@Immutable +class ConditionalRequestBuilder { + + /** + * When a {@link HttpCacheEntry} is stale but 'might' be used as a response + * to an {@link ch.boye.httpclientandroidlib.HttpRequest} we will attempt to revalidate + * the entry with the origin. Build the origin {@link ch.boye.httpclientandroidlib.HttpRequest} + * here and return it. + * + * @param request the original request from the caller + * @param cacheEntry the entry that needs to be re-validated + * @return the wrapped request + * @throws ProtocolException when I am unable to build a new origin request. + */ + public HttpRequestWrapper buildConditionalRequest(final HttpRequestWrapper request, final HttpCacheEntry cacheEntry) + throws ProtocolException { + final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal()); + newRequest.setHeaders(request.getAllHeaders()); + final Header eTag = cacheEntry.getFirstHeader(HeaderConstants.ETAG); + if (eTag != null) { + newRequest.setHeader(HeaderConstants.IF_NONE_MATCH, eTag.getValue()); + } + final Header lastModified = cacheEntry.getFirstHeader(HeaderConstants.LAST_MODIFIED); + if (lastModified != null) { + newRequest.setHeader(HeaderConstants.IF_MODIFIED_SINCE, lastModified.getValue()); + } + boolean mustRevalidate = false; + for(final Header h : cacheEntry.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE.equalsIgnoreCase(elt.getName()) + || HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE.equalsIgnoreCase(elt.getName())) { + mustRevalidate = true; + break; + } + } + } + if (mustRevalidate) { + newRequest.addHeader(HeaderConstants.CACHE_CONTROL, HeaderConstants.CACHE_CONTROL_MAX_AGE + "=0"); + } + return newRequest; + + } + + /** + * When a {@link HttpCacheEntry} does not exist for a specific + * {@link ch.boye.httpclientandroidlib.HttpRequest} we attempt to see if an existing + * {@link HttpCacheEntry} is appropriate by building a conditional + * {@link ch.boye.httpclientandroidlib.HttpRequest} using the variants' ETag values. + * If no such values exist, the request is unmodified + * + * @param request the original request from the caller + * @param variants + * @return the wrapped request + */ + public HttpRequestWrapper buildConditionalRequestFromVariants(final HttpRequestWrapper request, + final Map<String, Variant> variants) { + final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal()); + newRequest.setHeaders(request.getAllHeaders()); + + // we do not support partial content so all etags are used + final StringBuilder etags = new StringBuilder(); + boolean first = true; + for(final String etag : variants.keySet()) { + if (!first) { + etags.append(","); + } + first = false; + etags.append(etag); + } + + newRequest.setHeader(HeaderConstants.IF_NONE_MATCH, etags.toString()); + return newRequest; + } + + /** + * Returns a request to unconditionally validate a cache entry with + * the origin. In certain cases (due to multiple intervening caches) + * our cache may actually receive a response to a normal conditional + * validation where the Date header is actually older than that of + * our current cache entry. In this case, the protocol recommendation + * is to retry the validation and force syncup with the origin. + * @param request client request we are trying to satisfy + * @param entry existing cache entry we are trying to validate + * @return an unconditional validation request + */ + public HttpRequestWrapper buildUnconditionalRequest(final HttpRequestWrapper request, final HttpCacheEntry entry) { + final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal()); + newRequest.setHeaders(request.getAllHeaders()); + newRequest.addHeader(HeaderConstants.CACHE_CONTROL,HeaderConstants.CACHE_CONTROL_NO_CACHE); + newRequest.addHeader(HeaderConstants.PRAGMA,HeaderConstants.CACHE_CONTROL_NO_CACHE); + newRequest.removeHeaders(HeaderConstants.IF_RANGE); + newRequest.removeHeaders(HeaderConstants.IF_MATCH); + newRequest.removeHeaders(HeaderConstants.IF_NONE_MATCH); + newRequest.removeHeaders(HeaderConstants.IF_UNMODIFIED_SINCE); + newRequest.removeHeaders(HeaderConstants.IF_MODIFIED_SINCE); + return newRequest; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java new file mode 100644 index 000000000..3646c3c32 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java @@ -0,0 +1,143 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Implements a bounded failure cache. The oldest entries are discarded when + * the maximum size is exceeded. + * + * @since 4.3 + */ +@ThreadSafe +public class DefaultFailureCache implements FailureCache { + + static final int DEFAULT_MAX_SIZE = 1000; + static final int MAX_UPDATE_TRIES = 10; + + private final int maxSize; + private final ConcurrentMap<String, FailureCacheValue> storage; + + /** + * Create a new failure cache with the maximum size of + * {@link #DEFAULT_MAX_SIZE}. + */ + public DefaultFailureCache() { + this(DEFAULT_MAX_SIZE); + } + + /** + * Creates a new failure cache with the specified maximum size. + * @param maxSize the maximum number of entries the cache should store + */ + public DefaultFailureCache(final int maxSize) { + this.maxSize = maxSize; + this.storage = new ConcurrentHashMap<String, FailureCacheValue>(); + } + + public int getErrorCount(final String identifier) { + if (identifier == null) { + throw new IllegalArgumentException("identifier may not be null"); + } + final FailureCacheValue storedErrorCode = storage.get(identifier); + return storedErrorCode != null ? storedErrorCode.getErrorCount() : 0; + } + + public void resetErrorCount(final String identifier) { + if (identifier == null) { + throw new IllegalArgumentException("identifier may not be null"); + } + storage.remove(identifier); + } + + public void increaseErrorCount(final String identifier) { + if (identifier == null) { + throw new IllegalArgumentException("identifier may not be null"); + } + updateValue(identifier); + removeOldestEntryIfMapSizeExceeded(); + } + + private void updateValue(final String identifier) { + /** + * Due to concurrency it is possible that someone else is modifying an + * entry before we could write back our updated value. So we keep + * trying until it is our turn. + * + * In case there is a lot of contention on that identifier, a thread + * might starve. Thus it gives up after a certain number of failed + * update tries. + */ + for (int i = 0; i < MAX_UPDATE_TRIES; i++) { + final FailureCacheValue oldValue = storage.get(identifier); + if (oldValue == null) { + final FailureCacheValue newValue = new FailureCacheValue(identifier, 1); + if (storage.putIfAbsent(identifier, newValue) == null) { + return; + } + } + else { + final int errorCount = oldValue.getErrorCount(); + if (errorCount == Integer.MAX_VALUE) { + return; + } + final FailureCacheValue newValue = new FailureCacheValue(identifier, errorCount + 1); + if (storage.replace(identifier, oldValue, newValue)) { + return; + } + } + } + } + + private void removeOldestEntryIfMapSizeExceeded() { + if (storage.size() > maxSize) { + final FailureCacheValue valueWithOldestTimestamp = findValueWithOldestTimestamp(); + if (valueWithOldestTimestamp != null) { + storage.remove(valueWithOldestTimestamp.getKey(), valueWithOldestTimestamp); + } + } + } + + private FailureCacheValue findValueWithOldestTimestamp() { + long oldestTimestamp = Long.MAX_VALUE; + FailureCacheValue oldestValue = null; + for (final Map.Entry<String, FailureCacheValue> storageEntry : storage.entrySet()) { + final FailureCacheValue value = storageEntry.getValue(); + final long creationTimeInNanos = value.getCreationTimeInNanos(); + if (creationTimeInNanos < oldestTimestamp) { + oldestTimestamp = creationTimeInNanos; + oldestValue = storageEntry.getValue(); + } + } + return oldestValue; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java new file mode 100644 index 000000000..0d78a2eb1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntrySerializationException; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntrySerializer; + +/** + * {@link HttpCacheEntrySerializer} implementation that uses the default (native) + * serialization. + * + * @see java.io.Serializable + * + * @since 4.1 + */ +@Immutable +public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer { + + public void writeTo(final HttpCacheEntry cacheEntry, final OutputStream os) throws IOException { + final ObjectOutputStream oos = new ObjectOutputStream(os); + try { + oos.writeObject(cacheEntry); + } finally { + oos.close(); + } + } + + public HttpCacheEntry readFrom(final InputStream is) throws IOException { + final ObjectInputStream ois = new ObjectInputStream(is); + try { + return (HttpCacheEntry) ois.readObject(); + } catch (final ClassNotFoundException ex) { + throw new HttpCacheEntrySerializationException("Class not found: " + ex.getMessage(), ex); + } finally { + ois.close(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java new file mode 100644 index 000000000..286205583 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java @@ -0,0 +1,174 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * An implementation that backs off exponentially based on the number of + * consecutive failed attempts stored in the + * {@link AsynchronousValidationRequest}. It uses the following defaults: + * <pre> + * no delay in case it was never tried or didn't fail so far + * 6 secs delay for one failed attempt (= {@link #getInitialExpiryInMillis()}) + * 60 secs delay for two failed attempts + * 10 mins delay for three failed attempts + * 100 mins delay for four failed attempts + * ~16 hours delay for five failed attempts + * 24 hours delay for six or more failed attempts (= {@link #getMaxExpiryInMillis()}) + * </pre> + * + * The following equation is used to calculate the delay for a specific revalidation request: + * <pre> + * delay = {@link #getInitialExpiryInMillis()} * Math.pow({@link #getBackOffRate()}, {@link AsynchronousValidationRequest#getConsecutiveFailedAttempts()} - 1)) + * </pre> + * The resulting delay won't exceed {@link #getMaxExpiryInMillis()}. + * + * @since 4.3 + */ +@ThreadSafe +public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy { + + public static final long DEFAULT_BACK_OFF_RATE = 10; + public static final long DEFAULT_INITIAL_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(6); + public static final long DEFAULT_MAX_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(86400); + + private final long backOffRate; + private final long initialExpiryInMillis; + private final long maxExpiryInMillis; + + private final ScheduledExecutorService executor; + + /** + * Create a new scheduling strategy using a fixed pool of worker threads. + * @param cacheConfig the thread pool configuration to be used; not <code>null</code> + * @see ch.boye.httpclientandroidlib.impl.client.cache.CacheConfig#getAsynchronousWorkersMax() + * @see #DEFAULT_BACK_OFF_RATE + * @see #DEFAULT_INITIAL_EXPIRY_IN_MILLIS + * @see #DEFAULT_MAX_EXPIRY_IN_MILLIS + */ + public ExponentialBackOffSchedulingStrategy(final CacheConfig cacheConfig) { + this(cacheConfig, + DEFAULT_BACK_OFF_RATE, + DEFAULT_INITIAL_EXPIRY_IN_MILLIS, + DEFAULT_MAX_EXPIRY_IN_MILLIS); + } + + /** + * Create a new scheduling strategy by using a fixed pool of worker threads and the + * given parameters to calculated the delay. + * + * @param cacheConfig the thread pool configuration to be used; not <code>null</code> + * @param backOffRate the back off rate to be used; not negative + * @param initialExpiryInMillis the initial expiry in milli seconds; not negative + * @param maxExpiryInMillis the upper limit of the delay in milli seconds; not negative + * @see ch.boye.httpclientandroidlib.impl.client.cache.CacheConfig#getAsynchronousWorkersMax() + * @see ExponentialBackOffSchedulingStrategy + */ + public ExponentialBackOffSchedulingStrategy( + final CacheConfig cacheConfig, + final long backOffRate, + final long initialExpiryInMillis, + final long maxExpiryInMillis) { + this(createThreadPoolFromCacheConfig(cacheConfig), + backOffRate, + initialExpiryInMillis, + maxExpiryInMillis); + } + + private static ScheduledThreadPoolExecutor createThreadPoolFromCacheConfig( + final CacheConfig cacheConfig) { + final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor( + cacheConfig.getAsynchronousWorkersMax()); + scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + return scheduledThreadPoolExecutor; + } + + ExponentialBackOffSchedulingStrategy( + final ScheduledExecutorService executor, + final long backOffRate, + final long initialExpiryInMillis, + final long maxExpiryInMillis) { + this.executor = checkNotNull("executor", executor); + this.backOffRate = checkNotNegative("backOffRate", backOffRate); + this.initialExpiryInMillis = checkNotNegative("initialExpiryInMillis", initialExpiryInMillis); + this.maxExpiryInMillis = checkNotNegative("maxExpiryInMillis", maxExpiryInMillis); + } + + public void schedule( + final AsynchronousValidationRequest revalidationRequest) { + checkNotNull("revalidationRequest", revalidationRequest); + final int consecutiveFailedAttempts = revalidationRequest.getConsecutiveFailedAttempts(); + final long delayInMillis = calculateDelayInMillis(consecutiveFailedAttempts); + executor.schedule(revalidationRequest, delayInMillis, TimeUnit.MILLISECONDS); + } + + public void close() { + executor.shutdown(); + } + + public long getBackOffRate() { + return backOffRate; + } + + public long getInitialExpiryInMillis() { + return initialExpiryInMillis; + } + + public long getMaxExpiryInMillis() { + return maxExpiryInMillis; + } + + protected long calculateDelayInMillis(final int consecutiveFailedAttempts) { + if (consecutiveFailedAttempts > 0) { + final long delayInSeconds = (long) (initialExpiryInMillis * + Math.pow(backOffRate, consecutiveFailedAttempts - 1)); + return Math.min(delayInSeconds, maxExpiryInMillis); + } + else { + return 0; + } + } + + protected static <T> T checkNotNull(final String parameterName, final T value) { + if (value == null) { + throw new IllegalArgumentException(parameterName + " may not be null"); + } + return value; + } + + protected static long checkNotNegative(final String parameterName, final long value) { + if (value < 0) { + throw new IllegalArgumentException(parameterName + " may not be negative"); + } + return value; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java new file mode 100644 index 000000000..a30fc54d9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +/** + * Increase and reset the number of errors associated with a specific + * identifier. + * + * @since 4.3 + */ +public interface FailureCache { + + /** + * Get the current error count. + * @param identifier the identifier for which the error count is requested + * @return the currently known error count or zero if there is no record + */ + int getErrorCount(String identifier); + + /** + * Reset the error count back to zero. + * @param identifier the identifier for which the error count should be + * reset + */ + void resetErrorCount(String identifier); + + /** + * Increases the error count by one. + * @param identifier the identifier for which the error count should be + * increased + */ + void increaseErrorCount(String identifier); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java new file mode 100644 index 000000000..48bbf55fc --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * The error count with a creation timestamp and its associated key. + * + * @since 4.3 + */ +@Immutable +public class FailureCacheValue { + + private final long creationTimeInNanos; + private final String key; + private final int errorCount; + + public FailureCacheValue(final String key, final int errorCount) { + this.creationTimeInNanos = System.nanoTime(); + this.key = key; + this.errorCount = errorCount; + } + + public long getCreationTimeInNanos() { + return creationTimeInNanos; + } + + public String getKey() + { + return key; + } + + public int getErrorCount() { + return errorCount; + } + + @Override + public String toString() { + return "[entry creationTimeInNanos=" + creationTimeInNanos + "; " + + "key=" + key + "; errorCount=" + errorCount + ']'; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java new file mode 100644 index 000000000..934ecd13d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java @@ -0,0 +1,77 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.Resource; + +/** + * Cache resource backed by a file. + * + * @since 4.1 + */ +@ThreadSafe +public class FileResource implements Resource { + + private static final long serialVersionUID = 4132244415919043397L; + + private final File file; + + private volatile boolean disposed; + + public FileResource(final File file) { + super(); + this.file = file; + this.disposed = false; + } + + synchronized File getFile() { + return this.file; + } + + public synchronized InputStream getInputStream() throws IOException { + return new FileInputStream(this.file); + } + + public synchronized long length() { + return this.file.length(); + } + + public synchronized void dispose() { + if (this.disposed) { + return; + } + this.disposed = true; + this.file.delete(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java new file mode 100644 index 000000000..8522c54b5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java @@ -0,0 +1,111 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.InputLimit; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; + +/** + * Generates {@link Resource} instances whose body is stored in a temporary file. + * + * @since 4.1 + */ +@Immutable +public class FileResourceFactory implements ResourceFactory { + + private final File cacheDir; + private final BasicIdGenerator idgen; + + public FileResourceFactory(final File cacheDir) { + super(); + this.cacheDir = cacheDir; + this.idgen = new BasicIdGenerator(); + } + + private File generateUniqueCacheFile(final String requestId) { + final StringBuilder buffer = new StringBuilder(); + this.idgen.generate(buffer); + buffer.append('.'); + final int len = Math.min(requestId.length(), 100); + for (int i = 0; i < len; i++) { + final char ch = requestId.charAt(i); + if (Character.isLetterOrDigit(ch) || ch == '.') { + buffer.append(ch); + } else { + buffer.append('-'); + } + } + return new File(this.cacheDir, buffer.toString()); + } + + public Resource generate( + final String requestId, + final InputStream instream, + final InputLimit limit) throws IOException { + final File file = generateUniqueCacheFile(requestId); + final FileOutputStream outstream = new FileOutputStream(file); + try { + final byte[] buf = new byte[2048]; + long total = 0; + int l; + while ((l = instream.read(buf)) != -1) { + outstream.write(buf, 0, l); + total += l; + if (limit != null && total > limit.getValue()) { + limit.reached(); + break; + } + } + } finally { + outstream.close(); + } + return new FileResource(file); + } + + public Resource copy( + final String requestId, + final Resource resource) throws IOException { + final File file = generateUniqueCacheFile(requestId); + + if (resource instanceof FileResource) { + final File src = ((FileResource) resource).getFile(); + IOUtils.copyFile(src, file); + } else { + final FileOutputStream out = new FileOutputStream(file); + IOUtils.copyAndClose(resource.getInputStream(), out); + } + return new FileResource(file); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java new file mode 100644 index 000000000..e4b353a3c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.Resource; + +/** + * Cache resource backed by a byte array on the heap. + * + * @since 4.1 + */ +@Immutable +public class HeapResource implements Resource { + + private static final long serialVersionUID = -2078599905620463394L; + + private final byte[] b; + + public HeapResource(final byte[] b) { + super(); + this.b = b; + } + + byte[] getByteArray() { + return this.b; + } + + public InputStream getInputStream() { + return new ByteArrayInputStream(this.b); + } + + public long length() { + return this.b.length; + } + + public void dispose() { + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java new file mode 100644 index 000000000..f45cc0965 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java @@ -0,0 +1,83 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.InputLimit; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; + +/** + * Generates {@link Resource} instances stored entirely in heap. + * + * @since 4.1 + */ +@Immutable +public class HeapResourceFactory implements ResourceFactory { + + public Resource generate( + final String requestId, + final InputStream instream, + final InputLimit limit) throws IOException { + final ByteArrayOutputStream outstream = new ByteArrayOutputStream(); + final byte[] buf = new byte[2048]; + long total = 0; + int l; + while ((l = instream.read(buf)) != -1) { + outstream.write(buf, 0, l); + total += l; + if (limit != null && total > limit.getValue()) { + limit.reached(); + break; + } + } + return createResource(outstream.toByteArray()); + } + + public Resource copy( + final String requestId, + final Resource resource) throws IOException { + byte[] body; + if (resource instanceof HeapResource) { + body = ((HeapResource) resource).getByteArray(); + } else { + final ByteArrayOutputStream outstream = new ByteArrayOutputStream(); + IOUtils.copyAndClose(resource.getInputStream(), outstream); + body = outstream.toByteArray(); + } + return createResource(body); + } + + Resource createResource(final byte[] buf) { + return new HeapResource(buf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java new file mode 100644 index 000000000..48037b5f5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java @@ -0,0 +1,166 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; + +/** + * @since 4.1 + */ +interface HttpCache { + + /** + * Clear all matching {@link HttpCacheEntry}s. + * @param host + * @param request + * @throws IOException + */ + void flushCacheEntriesFor(HttpHost host, HttpRequest request) + throws IOException; + + /** + * Clear invalidated matching {@link HttpCacheEntry}s + * @param host + * @param request + * @throws IOException + */ + void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request) + throws IOException; + + /** Clear any entries that may be invalidated by the given response to + * a particular request. + * @param host + * @param request + * @param response + */ + void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request, + HttpResponse response); + + /** + * Retrieve matching {@link HttpCacheEntry} from the cache if it exists + * @param host + * @param request + * @return the matching {@link HttpCacheEntry} or {@code null} + * @throws IOException + */ + HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request) + throws IOException; + + /** + * Retrieve all variants from the cache, if there are no variants then an empty + * {@link Map} is returned + * @param host + * @param request + * @return a <code>Map</code> mapping Etags to variant cache entries + * @throws IOException + */ + Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request) + throws IOException; + + /** + * Store a {@link HttpResponse} in the cache if possible, and return + * @param host + * @param request + * @param originResponse + * @param requestSent + * @param responseReceived + * @return the {@link HttpResponse} + * @throws IOException + */ + HttpResponse cacheAndReturnResponse( + HttpHost host, HttpRequest request, HttpResponse originResponse, + Date requestSent, Date responseReceived) + throws IOException; + + /** + * Store a {@link HttpResponse} in the cache if possible, and return + * @param host + * @param request + * @param originResponse + * @param requestSent + * @param responseReceived + * @return the {@link HttpResponse} + * @throws IOException + */ + CloseableHttpResponse cacheAndReturnResponse(HttpHost host, + HttpRequest request, CloseableHttpResponse originResponse, + Date requestSent, Date responseReceived) + throws IOException; + + /** + * Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}. + * @param target + * @param request + * @param stale + * @param originResponse + * @param requestSent + * @param responseReceived + * @return the updated {@link HttpCacheEntry} + * @throws IOException + */ + HttpCacheEntry updateCacheEntry( + HttpHost target, HttpRequest request, HttpCacheEntry stale, HttpResponse originResponse, + Date requestSent, Date responseReceived) + throws IOException; + + /** + * Update a specific {@link HttpCacheEntry} representing a cached variant + * using a 304 {@link HttpResponse}. + * @param target host for client request + * @param request actual request from upstream client + * @param stale current variant cache entry + * @param originResponse 304 response received from origin + * @param requestSent when the validating request was sent + * @param responseReceived when the validating response was received + * @param cacheKey where in the cache this entry is currently stored + * @return the updated {@link HttpCacheEntry} + * @throws IOException + */ + HttpCacheEntry updateVariantCacheEntry(HttpHost target, HttpRequest request, + HttpCacheEntry stale, HttpResponse originResponse, Date requestSent, + Date responseReceived, String cacheKey) + throws IOException; + + /** + * Specifies cache should reuse the given cached variant to satisfy + * requests whose varying headers match those of the given client request. + * @param target host of the upstream client request + * @param req request sent by upstream client + * @param variant variant cache entry to reuse + * @throws IOException may be thrown during cache update + */ + void reuseVariantEntryFor(HttpHost target, final HttpRequest req, + final Variant variant) throws IOException; +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java new file mode 100644 index 000000000..aaf41b2c9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java @@ -0,0 +1,109 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +@Immutable +class IOUtils { + + static void consume(final HttpEntity entity) throws IOException { + if (entity == null) { + return; + } + if (entity.isStreaming()) { + final InputStream instream = entity.getContent(); + if (instream != null) { + instream.close(); + } + } + } + + static void copy(final InputStream in, final OutputStream out) throws IOException { + final byte[] buf = new byte[2048]; + int len; + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + } + + static void closeSilently(final Closeable closable) { + try { + closable.close(); + } catch (final IOException ignore) { + } + } + + static void copyAndClose(final InputStream in, final OutputStream out) throws IOException { + try { + copy(in, out); + in.close(); + out.close(); + } catch (final IOException ex) { + closeSilently(in); + closeSilently(out); + // Propagate the original exception + throw ex; + } + } + + static void copyFile(final File in, final File out) throws IOException { + final RandomAccessFile f1 = new RandomAccessFile(in, "r"); + final RandomAccessFile f2 = new RandomAccessFile(out, "rw"); + try { + final FileChannel c1 = f1.getChannel(); + final FileChannel c2 = f2.getChannel(); + try { + c1.transferTo(0, f1.length(), c2); + c1.close(); + c2.close(); + } catch (final IOException ex) { + closeSilently(c1); + closeSilently(c2); + // Propagate the original exception + throw ex; + } + f1.close(); + f2.close(); + } catch (final IOException ex) { + closeSilently(f1); + closeSilently(f2); + // Propagate the original exception + throw ex; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java new file mode 100644 index 000000000..71d633825 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Immediately schedules any incoming validation request. Relies on + * {@link CacheConfig} to configure the used {@link java.util.concurrent.ThreadPoolExecutor}. + * + * @since 4.3 + */ +@ThreadSafe +public class ImmediateSchedulingStrategy implements SchedulingStrategy { + + private final ExecutorService executor; + + /** + * Uses a {@link java.util.concurrent.ThreadPoolExecutor} which is configured according to the + * given {@link CacheConfig}. + * @param cacheConfig specifies thread pool settings. See + * {@link CacheConfig#getAsynchronousWorkersMax()}, + * {@link CacheConfig#getAsynchronousWorkersCore()}, + * {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()}, + * and {@link CacheConfig#getRevalidationQueueSize()}. + */ + public ImmediateSchedulingStrategy(final CacheConfig cacheConfig) { + this(new ThreadPoolExecutor( + cacheConfig.getAsynchronousWorkersCore(), + cacheConfig.getAsynchronousWorkersMax(), + cacheConfig.getAsynchronousWorkerIdleLifetimeSecs(), + TimeUnit.SECONDS, + new ArrayBlockingQueue<Runnable>(cacheConfig.getRevalidationQueueSize())) + ); + } + + ImmediateSchedulingStrategy(final ExecutorService executor) { + this.executor = executor; + } + + public void schedule(final AsynchronousValidationRequest revalidationRequest) { + if (revalidationRequest == null) { + throw new IllegalArgumentException("AsynchronousValidationRequest may not be null"); + } + + executor.execute(revalidationRequest); + } + + public void close() { + executor.shutdown(); + } + + /** + * Visible for testing. + */ + void awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { + executor.awaitTermination(timeout, unit); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java new file mode 100644 index 000000000..3a2589cc3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java @@ -0,0 +1,163 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * {@link HttpCacheStorage} implementation capable of deallocating resources associated with + * the cache entries. This cache keeps track of cache entries using + * {@link java.lang.ref.PhantomReference} and maintains a collection of all resources that + * are no longer in use. The cache, however, does not automatically deallocates associated + * resources by invoking {@link Resource#dispose()} method. The consumer MUST periodically + * call {@link #cleanResources()} method to trigger resource deallocation. The cache can be + * permanently shut down using {@link #shutdown()} method. All resources associated with + * the entries used by the cache will be deallocated. + * + * This {@link HttpCacheStorage} implementation is intended for use with {@link FileResource} + * and similar. + * + * @since 4.1 + */ +@ThreadSafe +public class ManagedHttpCacheStorage implements HttpCacheStorage, Closeable { + + private final CacheMap entries; + private final ReferenceQueue<HttpCacheEntry> morque; + private final Set<ResourceReference> resources; + private final AtomicBoolean active; + + public ManagedHttpCacheStorage(final CacheConfig config) { + super(); + this.entries = new CacheMap(config.getMaxCacheEntries()); + this.morque = new ReferenceQueue<HttpCacheEntry>(); + this.resources = new HashSet<ResourceReference>(); + this.active = new AtomicBoolean(true); + } + + private void ensureValidState() throws IllegalStateException { + if (!this.active.get()) { + throw new IllegalStateException("Cache has been shut down"); + } + } + + private void keepResourceReference(final HttpCacheEntry entry) { + final Resource resource = entry.getResource(); + if (resource != null) { + // Must deallocate the resource when the entry is no longer in used + final ResourceReference ref = new ResourceReference(entry, this.morque); + this.resources.add(ref); + } + } + + public void putEntry(final String url, final HttpCacheEntry entry) throws IOException { + Args.notNull(url, "URL"); + Args.notNull(entry, "Cache entry"); + ensureValidState(); + synchronized (this) { + this.entries.put(url, entry); + keepResourceReference(entry); + } + } + + public HttpCacheEntry getEntry(final String url) throws IOException { + Args.notNull(url, "URL"); + ensureValidState(); + synchronized (this) { + return this.entries.get(url); + } + } + + public void removeEntry(final String url) throws IOException { + Args.notNull(url, "URL"); + ensureValidState(); + synchronized (this) { + // Cannot deallocate the associated resources immediately as the + // cache entry may still be in use + this.entries.remove(url); + } + } + + public void updateEntry( + final String url, + final HttpCacheUpdateCallback callback) throws IOException { + Args.notNull(url, "URL"); + Args.notNull(callback, "Callback"); + ensureValidState(); + synchronized (this) { + final HttpCacheEntry existing = this.entries.get(url); + final HttpCacheEntry updated = callback.update(existing); + this.entries.put(url, updated); + if (existing != updated) { + keepResourceReference(updated); + } + } + } + + public void cleanResources() { + if (this.active.get()) { + ResourceReference ref; + while ((ref = (ResourceReference) this.morque.poll()) != null) { + synchronized (this) { + this.resources.remove(ref); + } + ref.getResource().dispose(); + } + } + } + + public void shutdown() { + if (this.active.compareAndSet(true, false)) { + synchronized (this) { + this.entries.clear(); + for (final ResourceReference ref: this.resources) { + ref.getResource().dispose(); + } + this.resources.clear(); + while (this.morque.poll() != null) { + } + } + } + } + + public void close() { + shutdown(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java new file mode 100644 index 000000000..c2e07b52d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java @@ -0,0 +1,182 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderIterator; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.message.AbstractHttpMessage; +import ch.boye.httpclientandroidlib.message.BasicStatusLine; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * @since 4.1 + */ +@SuppressWarnings("deprecation") +@Immutable +final class OptionsHttp11Response extends AbstractHttpMessage implements HttpResponse { + + private final StatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_NOT_IMPLEMENTED, ""); + private final ProtocolVersion version = HttpVersion.HTTP_1_1; + + public StatusLine getStatusLine() { + return statusLine; + } + + public void setStatusLine(final StatusLine statusline) { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setStatusLine(final ProtocolVersion ver, final int code) { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setStatusLine(final ProtocolVersion ver, final int code, final String reason) { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setStatusCode(final int code) throws IllegalStateException { + // No-op on purpose, this class is not going to be doing any work. + } + + public void setReasonPhrase(final String reason) throws IllegalStateException { + // No-op on purpose, this class is not going to be doing any work. + } + + public HttpEntity getEntity() { + return null; + } + + public void setEntity(final HttpEntity entity) { + // No-op on purpose, this class is not going to be doing any work. + } + + public Locale getLocale() { + return null; + } + + public void setLocale(final Locale loc) { + // No-op on purpose, this class is not going to be doing any work. + } + + public ProtocolVersion getProtocolVersion() { + return version; + } + + @Override + public boolean containsHeader(final String name) { + return this.headergroup.containsHeader(name); + } + + @Override + public Header[] getHeaders(final String name) { + return this.headergroup.getHeaders(name); + } + + @Override + public Header getFirstHeader(final String name) { + return this.headergroup.getFirstHeader(name); + } + + @Override + public Header getLastHeader(final String name) { + return this.headergroup.getLastHeader(name); + } + + @Override + public Header[] getAllHeaders() { + return this.headergroup.getAllHeaders(); + } + + @Override + public void addHeader(final Header header) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void addHeader(final String name, final String value) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void setHeader(final Header header) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void setHeader(final String name, final String value) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void setHeaders(final Header[] headers) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void removeHeader(final Header header) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public void removeHeaders(final String name) { + // No-op on purpose, this class is not going to be doing any work. + } + + @Override + public HeaderIterator headerIterator() { + return this.headergroup.iterator(); + } + + @Override + public HeaderIterator headerIterator(final String name) { + return this.headergroup.iterator(name); + } + + @Override + public HttpParams getParams() { + if (this.params == null) { + this.params = new BasicHttpParams(); + } + return this.params; + } + + @Override + public void setParams(final HttpParams params) { + // No-op on purpose, this class is not going to be doing any work. + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java new file mode 100644 index 000000000..4499d8288 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java @@ -0,0 +1,56 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.lang.reflect.Proxy; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Proxies for HTTP message objects. + * + * @since 4.3 + */ +@NotThreadSafe +class Proxies { + + public static CloseableHttpResponse enhanceResponse(final HttpResponse original) { + Args.notNull(original, "HTTP response"); + if (original instanceof CloseableHttpResponse) { + return (CloseableHttpResponse) original; + } else { + return (CloseableHttpResponse) Proxy.newProxyInstance( + ResponseProxyHandler.class.getClassLoader(), + new Class<?>[] { CloseableHttpResponse.class }, + new ResponseProxyHandler(original)); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java new file mode 100644 index 000000000..ac7ab439c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java @@ -0,0 +1,376 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity; +import ch.boye.httpclientandroidlib.entity.ContentType; +import ch.boye.httpclientandroidlib.message.BasicHeader; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; +import ch.boye.httpclientandroidlib.message.BasicStatusLine; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * @since 4.1 + */ +@Immutable +class RequestProtocolCompliance { + private final boolean weakETagOnPutDeleteAllowed; + + public RequestProtocolCompliance() { + super(); + this.weakETagOnPutDeleteAllowed = false; + } + + public RequestProtocolCompliance(final boolean weakETagOnPutDeleteAllowed) { + super(); + this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed; + } + + private static final List<String> disallowedWithNoCache = + Arrays.asList(HeaderConstants.CACHE_CONTROL_MIN_FRESH, HeaderConstants.CACHE_CONTROL_MAX_STALE, HeaderConstants.CACHE_CONTROL_MAX_AGE); + + /** + * Test to see if the {@link HttpRequest} is HTTP1.1 compliant or not + * and if not, we can not continue. + * + * @param request the HttpRequest Object + * @return list of {@link RequestProtocolError} + */ + public List<RequestProtocolError> requestIsFatallyNonCompliant(final HttpRequest request) { + final List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>(); + + RequestProtocolError anError = requestHasWeakETagAndRange(request); + if (anError != null) { + theErrors.add(anError); + } + + if (!weakETagOnPutDeleteAllowed) { + anError = requestHasWeekETagForPUTOrDELETEIfMatch(request); + if (anError != null) { + theErrors.add(anError); + } + } + + anError = requestContainsNoCacheDirectiveWithFieldName(request); + if (anError != null) { + theErrors.add(anError); + } + + return theErrors; + } + + /** + * If the {@link HttpRequest} is non-compliant but 'fixable' we go ahead and + * fix the request here. + * + * @param request the request to check for compliance + * @throws ClientProtocolException when we have trouble making the request compliant + */ + public void makeRequestCompliant(final HttpRequestWrapper request) + throws ClientProtocolException { + + if (requestMustNotHaveEntity(request)) { + ((HttpEntityEnclosingRequest) request).setEntity(null); + } + + verifyRequestWithExpectContinueFlagHas100continueHeader(request); + verifyOPTIONSRequestWithBodyHasContentType(request); + decrementOPTIONSMaxForwardsIfGreaterThen0(request); + stripOtherFreshnessDirectivesWithNoCache(request); + + if (requestVersionIsTooLow(request) + || requestMinorVersionIsTooHighMajorVersionsMatch(request)) { + request.setProtocolVersion(HttpVersion.HTTP_1_1); + } + } + + private void stripOtherFreshnessDirectivesWithNoCache(final HttpRequest request) { + final List<HeaderElement> outElts = new ArrayList<HeaderElement>(); + boolean shouldStrip = false; + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (!disallowedWithNoCache.contains(elt.getName())) { + outElts.add(elt); + } + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) { + shouldStrip = true; + } + } + } + if (!shouldStrip) { + return; + } + request.removeHeaders(HeaderConstants.CACHE_CONTROL); + request.setHeader(HeaderConstants.CACHE_CONTROL, buildHeaderFromElements(outElts)); + } + + private String buildHeaderFromElements(final List<HeaderElement> outElts) { + final StringBuilder newHdr = new StringBuilder(""); + boolean first = true; + for(final HeaderElement elt : outElts) { + if (!first) { + newHdr.append(","); + } else { + first = false; + } + newHdr.append(elt.toString()); + } + return newHdr.toString(); + } + + private boolean requestMustNotHaveEntity(final HttpRequest request) { + return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod()) + && request instanceof HttpEntityEnclosingRequest; + } + + private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) { + if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) { + return; + } + + final Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS); + if (maxForwards == null) { + return; + } + + request.removeHeaders(HeaderConstants.MAX_FORWARDS); + final int currentMaxForwards = Integer.parseInt(maxForwards.getValue()); + + request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1)); + } + + private void verifyOPTIONSRequestWithBodyHasContentType(final HttpRequest request) { + if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) { + return; + } + + if (!(request instanceof HttpEntityEnclosingRequest)) { + return; + } + + addContentTypeHeaderIfMissing((HttpEntityEnclosingRequest) request); + } + + private void addContentTypeHeaderIfMissing(final HttpEntityEnclosingRequest request) { + if (request.getEntity().getContentType() == null) { + ((AbstractHttpEntity) request.getEntity()).setContentType( + ContentType.APPLICATION_OCTET_STREAM.getMimeType()); + } + } + + private void verifyRequestWithExpectContinueFlagHas100continueHeader(final HttpRequest request) { + if (request instanceof HttpEntityEnclosingRequest) { + + if (((HttpEntityEnclosingRequest) request).expectContinue() + && ((HttpEntityEnclosingRequest) request).getEntity() != null) { + add100ContinueHeaderIfMissing(request); + } else { + remove100ContinueHeaderIfExists(request); + } + } else { + remove100ContinueHeaderIfExists(request); + } + } + + private void remove100ContinueHeaderIfExists(final HttpRequest request) { + boolean hasHeader = false; + + final Header[] expectHeaders = request.getHeaders(HTTP.EXPECT_DIRECTIVE); + List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>(); + + for (final Header h : expectHeaders) { + for (final HeaderElement elt : h.getElements()) { + if (!(HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName()))) { + expectElementsThatAreNot100Continue.add(elt); + } else { + hasHeader = true; + } + } + + if (hasHeader) { + request.removeHeader(h); + for (final HeaderElement elt : expectElementsThatAreNot100Continue) { + final BasicHeader newHeader = new BasicHeader(HTTP.EXPECT_DIRECTIVE, elt.getName()); + request.addHeader(newHeader); + } + return; + } else { + expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>(); + } + } + } + + private void add100ContinueHeaderIfMissing(final HttpRequest request) { + boolean hasHeader = false; + + for (final Header h : request.getHeaders(HTTP.EXPECT_DIRECTIVE)) { + for (final HeaderElement elt : h.getElements()) { + if (HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName())) { + hasHeader = true; + } + } + } + + if (!hasHeader) { + request.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE); + } + } + + protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) { + final ProtocolVersion requestProtocol = request.getProtocolVersion(); + if (requestProtocol.getMajor() != HttpVersion.HTTP_1_1.getMajor()) { + return false; + } + + if (requestProtocol.getMinor() > HttpVersion.HTTP_1_1.getMinor()) { + return true; + } + + return false; + } + + protected boolean requestVersionIsTooLow(final HttpRequest request) { + return request.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) < 0; + } + + /** + * Extract error information about the {@link HttpRequest} telling the 'caller' + * that a problem occured. + * + * @param errorCheck What type of error should I get + * @return The {@link HttpResponse} that is the error generated + */ + public HttpResponse getErrorForRequest(final RequestProtocolError errorCheck) { + switch (errorCheck) { + case BODY_BUT_NO_LENGTH_ERROR: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_LENGTH_REQUIRED, "")); + + case WEAK_ETAG_AND_RANGE_ERROR: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_BAD_REQUEST, "Weak eTag not compatible with byte range")); + + case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_BAD_REQUEST, + "Weak eTag not compatible with PUT or DELETE requests")); + + case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME: + return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, + HttpStatus.SC_BAD_REQUEST, + "No-Cache directive MUST NOT include a field name")); + + default: + throw new IllegalStateException( + "The request was compliant, therefore no error can be generated for it."); + + } + } + + private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) { + // TODO: Should these be looking at all the headers marked as Range? + final String method = request.getRequestLine().getMethod(); + if (!(HeaderConstants.GET_METHOD.equals(method))) { + return null; + } + + final Header range = request.getFirstHeader(HeaderConstants.RANGE); + if (range == null) { + return null; + } + + final Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE); + if (ifRange == null) { + return null; + } + + final String val = ifRange.getValue(); + if (val.startsWith("W/")) { + return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR; + } + + return null; + } + + private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request) { + // TODO: Should these be looking at all the headers marked as If-Match/If-None-Match? + + final String method = request.getRequestLine().getMethod(); + if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD + .equals(method))) { + return null; + } + + final Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH); + if (ifMatch != null) { + final String val = ifMatch.getValue(); + if (val.startsWith("W/")) { + return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR; + } + } else { + final Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH); + if (ifNoneMatch == null) { + return null; + } + + final String val2 = ifNoneMatch.getValue(); + if (val2.startsWith("W/")) { + return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR; + } + } + + return null; + } + + private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(final HttpRequest request) { + for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) { + for(final HeaderElement elt : h.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName()) + && elt.getValue() != null) { + return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME; + } + } + } + return null; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java new file mode 100644 index 000000000..1cc5668c7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java @@ -0,0 +1,40 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +/** + * @since 4.1 + */ +enum RequestProtocolError { + + UNKNOWN, + BODY_BUT_NO_LENGTH_ERROR, + WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR, + WEAK_ETAG_AND_RANGE_ERROR, + NO_CACHE_DIRECTIVE_WITH_FIELD_NAME + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java new file mode 100644 index 000000000..5e3f368f1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.util.Args; + +@Immutable +class ResourceReference extends PhantomReference<HttpCacheEntry> { + + private final Resource resource; + + public ResourceReference(final HttpCacheEntry entry, final ReferenceQueue<HttpCacheEntry> q) { + super(entry, q); + Args.notNull(entry.getResource(), "Resource"); + this.resource = entry.getResource(); + } + + public Resource getResource() { + return this.resource; + } + + @Override + public int hashCode() { + return this.resource.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return this.resource.equals(obj); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java new file mode 100644 index 000000000..abadb88d4 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java @@ -0,0 +1,311 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * Determines if an HttpResponse can be cached. + * + * @since 4.1 + */ +@Immutable +class ResponseCachingPolicy { + + private static final String[] AUTH_CACHEABLE_PARAMS = { + "s-maxage", HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, HeaderConstants.PUBLIC + }; + private final long maxObjectSizeBytes; + private final boolean sharedCache; + private final boolean neverCache1_0ResponsesWithQueryString; + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + private static final Set<Integer> cacheableStatuses = + new HashSet<Integer>(Arrays.asList(HttpStatus.SC_OK, + HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, + HttpStatus.SC_MULTIPLE_CHOICES, + HttpStatus.SC_MOVED_PERMANENTLY, + HttpStatus.SC_GONE)); + private final Set<Integer> uncacheableStatuses; + /** + * Define a cache policy that limits the size of things that should be stored + * in the cache to a maximum of {@link HttpResponse} bytes in size. + * + * @param maxObjectSizeBytes the size to limit items into the cache + * @param sharedCache whether to behave as a shared cache (true) or a + * non-shared/private cache (false) + * @param neverCache1_0ResponsesWithQueryString true to never cache HTTP 1.0 responses with a query string, false + * to cache if explicit cache headers are found. + * @param allow303Caching if this policy is permitted to cache 303 response + */ + public ResponseCachingPolicy(final long maxObjectSizeBytes, + final boolean sharedCache, + final boolean neverCache1_0ResponsesWithQueryString, + final boolean allow303Caching) { + this.maxObjectSizeBytes = maxObjectSizeBytes; + this.sharedCache = sharedCache; + this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString; + if (allow303Caching) { + uncacheableStatuses = new HashSet<Integer>( + Arrays.asList(HttpStatus.SC_PARTIAL_CONTENT)); + } else { + uncacheableStatuses = new HashSet<Integer>(Arrays.asList( + HttpStatus.SC_PARTIAL_CONTENT, HttpStatus.SC_SEE_OTHER)); + } + } + + /** + * Determines if an HttpResponse can be cached. + * + * @param httpMethod What type of request was this, a GET, PUT, other? + * @param response The origin response + * @return <code>true</code> if response is cacheable + */ + public boolean isResponseCacheable(final String httpMethod, final HttpResponse response) { + boolean cacheable = false; + + if (!HeaderConstants.GET_METHOD.equals(httpMethod)) { + log.debug("Response was not cacheable."); + return false; + } + + final int status = response.getStatusLine().getStatusCode(); + if (cacheableStatuses.contains(status)) { + // these response codes MAY be cached + cacheable = true; + } else if (uncacheableStatuses.contains(status)) { + return false; + } else if (unknownStatusCode(status)) { + // a response with an unknown status code MUST NOT be + // cached + return false; + } + + final Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN); + if (contentLength != null) { + final int contentLengthValue = Integer.parseInt(contentLength.getValue()); + if (contentLengthValue > this.maxObjectSizeBytes) { + return false; + } + } + + final Header[] ageHeaders = response.getHeaders(HeaderConstants.AGE); + + if (ageHeaders.length > 1) { + return false; + } + + final Header[] expiresHeaders = response.getHeaders(HeaderConstants.EXPIRES); + + if (expiresHeaders.length > 1) { + return false; + } + + final Header[] dateHeaders = response.getHeaders(HTTP.DATE_HEADER); + + if (dateHeaders.length != 1) { + return false; + } + + final Date date = DateUtils.parseDate(dateHeaders[0].getValue()); + if (date == null) { + return false; + } + + for (final Header varyHdr : response.getHeaders(HeaderConstants.VARY)) { + for (final HeaderElement elem : varyHdr.getElements()) { + if ("*".equals(elem.getName())) { + return false; + } + } + } + + if (isExplicitlyNonCacheable(response)) { + return false; + } + + return (cacheable || isExplicitlyCacheable(response)); + } + + private boolean unknownStatusCode(final int status) { + if (status >= 100 && status <= 101) { + return false; + } + if (status >= 200 && status <= 206) { + return false; + } + if (status >= 300 && status <= 307) { + return false; + } + if (status >= 400 && status <= 417) { + return false; + } + if (status >= 500 && status <= 505) { + return false; + } + return true; + } + + protected boolean isExplicitlyNonCacheable(final HttpResponse response) { + final Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL); + for (final Header header : cacheControlHeaders) { + for (final HeaderElement elem : header.getElements()) { + if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elem.getName()) + || HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elem.getName()) + || (sharedCache && HeaderConstants.PRIVATE.equals(elem.getName()))) { + return true; + } + } + } + return false; + } + + protected boolean hasCacheControlParameterFrom(final HttpMessage msg, final String[] params) { + final Header[] cacheControlHeaders = msg.getHeaders(HeaderConstants.CACHE_CONTROL); + for (final Header header : cacheControlHeaders) { + for (final HeaderElement elem : header.getElements()) { + for (final String param : params) { + if (param.equalsIgnoreCase(elem.getName())) { + return true; + } + } + } + } + return false; + } + + protected boolean isExplicitlyCacheable(final HttpResponse response) { + if (response.getFirstHeader(HeaderConstants.EXPIRES) != null) { + return true; + } + final String[] cacheableParams = { HeaderConstants.CACHE_CONTROL_MAX_AGE, "s-maxage", + HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, + HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE, + HeaderConstants.PUBLIC + }; + return hasCacheControlParameterFrom(response, cacheableParams); + } + + /** + * Determine if the {@link HttpResponse} gotten from the origin is a + * cacheable response. + * + * @param request the {@link HttpRequest} that generated an origin hit + * @param response the {@link HttpResponse} from the origin + * @return <code>true</code> if response is cacheable + */ + public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response) { + if (requestProtocolGreaterThanAccepted(request)) { + log.debug("Response was not cacheable."); + return false; + } + + final String[] uncacheableRequestDirectives = { HeaderConstants.CACHE_CONTROL_NO_STORE }; + if (hasCacheControlParameterFrom(request,uncacheableRequestDirectives)) { + return false; + } + + if (request.getRequestLine().getUri().contains("?")) { + if (neverCache1_0ResponsesWithQueryString && from1_0Origin(response)) { + log.debug("Response was not cacheable as it had a query string."); + return false; + } else if (!isExplicitlyCacheable(response)) { + log.debug("Response was not cacheable as it is missing explicit caching headers."); + return false; + } + } + + if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response)) { + return false; + } + + if (sharedCache) { + final Header[] authNHeaders = request.getHeaders(HeaderConstants.AUTHORIZATION); + if (authNHeaders != null && authNHeaders.length > 0 + && !hasCacheControlParameterFrom(response, AUTH_CACHEABLE_PARAMS)) { + return false; + } + } + + final String method = request.getRequestLine().getMethod(); + return isResponseCacheable(method, response); + } + + private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl( + final HttpResponse response) { + if (response.getFirstHeader(HeaderConstants.CACHE_CONTROL) != null) { + return false; + } + final Header expiresHdr = response.getFirstHeader(HeaderConstants.EXPIRES); + final Header dateHdr = response.getFirstHeader(HTTP.DATE_HEADER); + if (expiresHdr == null || dateHdr == null) { + return false; + } + final Date expires = DateUtils.parseDate(expiresHdr.getValue()); + final Date date = DateUtils.parseDate(dateHdr.getValue()); + if (expires == null || date == null) { + return false; + } + return expires.equals(date) || expires.before(date); + } + + private boolean from1_0Origin(final HttpResponse response) { + final Header via = response.getFirstHeader(HeaderConstants.VIA); + if (via != null) { + for(final HeaderElement elt : via.getElements()) { + final String proto = elt.toString().split("\\s")[0]; + if (proto.contains("/")) { + return proto.equals("HTTP/1.0"); + } else { + return proto.equals("1.0"); + } + } + } + return HttpVersion.HTTP_1_0.equals(response.getProtocolVersion()); + } + + private boolean requestProtocolGreaterThanAccepted(final HttpRequest req) { + return req.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) > 0; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java new file mode 100644 index 000000000..7f5ebb483 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java @@ -0,0 +1,251 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpStatus; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ClientProtocolException; +import ch.boye.httpclientandroidlib.client.cache.HeaderConstants; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.message.BasicHeader; +import ch.boye.httpclientandroidlib.protocol.HTTP; + +/** + * @since 4.1 + */ +@Immutable +class ResponseProtocolCompliance { + + private static final String UNEXPECTED_100_CONTINUE = "The incoming request did not contain a " + + "100-continue header, but the response was a Status 100, continue."; + private static final String UNEXPECTED_PARTIAL_CONTENT = "partial content was returned for a request that did not ask for it"; + + /** + * When we get a response from a down stream server (Origin Server) + * we attempt to see if it is HTTP 1.1 Compliant and if not, attempt to + * make it so. + * + * @param request The {@link HttpRequest} that generated an origin hit and response + * @param response The {@link HttpResponse} from the origin server + * @throws IOException Bad things happened + */ + public void ensureProtocolCompliance(final HttpRequestWrapper request, final HttpResponse response) + throws IOException { + if (backendResponseMustNotHaveBody(request, response)) { + consumeBody(response); + response.setEntity(null); + } + + requestDidNotExpect100ContinueButResponseIsOne(request, response); + + transferEncodingIsNotReturnedTo1_0Client(request, response); + + ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(request, response); + + ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response); + + ensure206ContainsDateHeader(response); + + ensure304DoesNotContainExtraEntityHeaders(response); + + identityIsNotUsedInContentEncoding(response); + + warningsWithNonMatchingWarnDatesAreRemoved(response); + } + + private void consumeBody(final HttpResponse response) throws IOException { + final HttpEntity body = response.getEntity(); + if (body != null) { + IOUtils.consume(body); + } + } + + private void warningsWithNonMatchingWarnDatesAreRemoved( + final HttpResponse response) { + final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER).getValue()); + if (responseDate == null) { + return; + } + + final Header[] warningHeaders = response.getHeaders(HeaderConstants.WARNING); + + if (warningHeaders == null || warningHeaders.length == 0) { + return; + } + + final List<Header> newWarningHeaders = new ArrayList<Header>(); + boolean modified = false; + for(final Header h : warningHeaders) { + for(final WarningValue wv : WarningValue.getWarningValues(h)) { + final Date warnDate = wv.getWarnDate(); + if (warnDate == null || warnDate.equals(responseDate)) { + newWarningHeaders.add(new BasicHeader(HeaderConstants.WARNING,wv.toString())); + } else { + modified = true; + } + } + } + if (modified) { + response.removeHeaders(HeaderConstants.WARNING); + for(final Header h : newWarningHeaders) { + response.addHeader(h); + } + } + } + + private void identityIsNotUsedInContentEncoding(final HttpResponse response) { + final Header[] hdrs = response.getHeaders(HTTP.CONTENT_ENCODING); + if (hdrs == null || hdrs.length == 0) { + return; + } + final List<Header> newHeaders = new ArrayList<Header>(); + boolean modified = false; + for (final Header h : hdrs) { + final StringBuilder buf = new StringBuilder(); + boolean first = true; + for (final HeaderElement elt : h.getElements()) { + if ("identity".equalsIgnoreCase(elt.getName())) { + modified = true; + } else { + if (!first) { + buf.append(","); + } + buf.append(elt.toString()); + first = false; + } + } + final String newHeaderValue = buf.toString(); + if (!"".equals(newHeaderValue)) { + newHeaders.add(new BasicHeader(HTTP.CONTENT_ENCODING, newHeaderValue)); + } + } + if (!modified) { + return; + } + response.removeHeaders(HTTP.CONTENT_ENCODING); + for (final Header h : newHeaders) { + response.addHeader(h); + } + } + + private void ensure206ContainsDateHeader(final HttpResponse response) { + if (response.getFirstHeader(HTTP.DATE_HEADER) == null) { + response.addHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date())); + } + + } + + private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(final HttpRequest request, + final HttpResponse response) throws IOException { + if (request.getFirstHeader(HeaderConstants.RANGE) != null + || response.getStatusLine().getStatusCode() != HttpStatus.SC_PARTIAL_CONTENT) { + return; + } + + consumeBody(response); + throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT); + } + + private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(final HttpRequest request, + final HttpResponse response) { + if (!request.getRequestLine().getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) { + return; + } + + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + return; + } + + if (response.getFirstHeader(HTTP.CONTENT_LEN) == null) { + response.addHeader(HTTP.CONTENT_LEN, "0"); + } + } + + private void ensure304DoesNotContainExtraEntityHeaders(final HttpResponse response) { + final String[] disallowedEntityHeaders = { HeaderConstants.ALLOW, HTTP.CONTENT_ENCODING, + "Content-Language", HTTP.CONTENT_LEN, "Content-MD5", + "Content-Range", HTTP.CONTENT_TYPE, HeaderConstants.LAST_MODIFIED + }; + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { + for(final String hdr : disallowedEntityHeaders) { + response.removeHeaders(hdr); + } + } + } + + private boolean backendResponseMustNotHaveBody(final HttpRequest request, final HttpResponse backendResponse) { + return HeaderConstants.HEAD_METHOD.equals(request.getRequestLine().getMethod()) + || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT + || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_RESET_CONTENT + || backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED; + } + + private void requestDidNotExpect100ContinueButResponseIsOne(final HttpRequestWrapper request, + final HttpResponse response) throws IOException { + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) { + return; + } + + final HttpRequest originalRequest = request.getOriginal(); + if (originalRequest instanceof HttpEntityEnclosingRequest) { + if (((HttpEntityEnclosingRequest)originalRequest).expectContinue()) { + return; + } + } + consumeBody(response); + throw new ClientProtocolException(UNEXPECTED_100_CONTINUE); + } + + private void transferEncodingIsNotReturnedTo1_0Client(final HttpRequestWrapper request, + final HttpResponse response) { + final HttpRequest originalRequest = request.getOriginal(); + if (originalRequest.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) >= 0) { + return; + } + + removeResponseTransferEncoding(response); + } + + private void removeResponseTransferEncoding(final HttpResponse response) { + response.removeHeaders("TE"); + response.removeHeaders(HTTP.TRANSFER_ENCODING); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java new file mode 100644 index 000000000..a541b2778 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +/** + * A proxy class that can enhance an arbitrary {@link HttpResponse} with + * {@link Closeable#close()} method. + * + * @since 4.3 + */ +@NotThreadSafe +class ResponseProxyHandler implements InvocationHandler { + + private static final Method CLOSE_METHOD; + + static { + try { + CLOSE_METHOD = Closeable.class.getMethod("close"); + } catch (final NoSuchMethodException ex) { + throw new Error(ex); + } + } + + private final HttpResponse original; + + ResponseProxyHandler(final HttpResponse original) { + super(); + this.original = original; + } + + public void close() throws IOException { + IOUtils.consume(original.getEntity()); + } + + public Object invoke( + final Object proxy, final Method method, final Object[] args) throws Throwable { + if (method.equals(CLOSE_METHOD)) { + close(); + return null; + } else { + try { + return method.invoke(this.original, args); + } catch (final InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause != null) { + throw cause; + } else { + throw ex; + } + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java new file mode 100644 index 000000000..0940fbc83 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java @@ -0,0 +1,45 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.Closeable; + +/** + * Specifies when revalidation requests are scheduled. + * + * @since 4.3 + */ +public interface SchedulingStrategy extends Closeable +{ + /** + * Schedule an {@link AsynchronousValidationRequest} to be executed. + * + * @param revalidationRequest the request to be executed; not <code>null</code> + * @throws java.util.concurrent.RejectedExecutionException if the request could not be scheduled for execution + */ + void schedule(AsynchronousValidationRequest revalidationRequest); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java new file mode 100644 index 000000000..4c36ebb48 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java @@ -0,0 +1,150 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Proxy; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.cache.InputLimit; +import ch.boye.httpclientandroidlib.client.cache.Resource; +import ch.boye.httpclientandroidlib.client.cache.ResourceFactory; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; + +/** + * @since 4.1 + */ +@NotThreadSafe +class SizeLimitedResponseReader { + + private final ResourceFactory resourceFactory; + private final long maxResponseSizeBytes; + private final HttpRequest request; + private final CloseableHttpResponse response; + + private InputStream instream; + private InputLimit limit; + private Resource resource; + private boolean consumed; + + /** + * Create an {@link HttpResponse} that is limited in size, this allows for checking + * the size of objects that will be stored in the cache. + */ + public SizeLimitedResponseReader( + final ResourceFactory resourceFactory, + final long maxResponseSizeBytes, + final HttpRequest request, + final CloseableHttpResponse response) { + super(); + this.resourceFactory = resourceFactory; + this.maxResponseSizeBytes = maxResponseSizeBytes; + this.request = request; + this.response = response; + } + + protected void readResponse() throws IOException { + if (!consumed) { + doConsume(); + } + } + + private void ensureNotConsumed() { + if (consumed) { + throw new IllegalStateException("Response has already been consumed"); + } + } + + private void ensureConsumed() { + if (!consumed) { + throw new IllegalStateException("Response has not been consumed"); + } + } + + private void doConsume() throws IOException { + ensureNotConsumed(); + consumed = true; + + limit = new InputLimit(maxResponseSizeBytes); + + final HttpEntity entity = response.getEntity(); + if (entity == null) { + return; + } + final String uri = request.getRequestLine().getUri(); + instream = entity.getContent(); + try { + resource = resourceFactory.generate(uri, instream, limit); + } finally { + if (!limit.isReached()) { + instream.close(); + } + } + } + + boolean isLimitReached() { + ensureConsumed(); + return limit.isReached(); + } + + Resource getResource() { + ensureConsumed(); + return resource; + } + + CloseableHttpResponse getReconstructedResponse() throws IOException { + ensureConsumed(); + final HttpResponse reconstructed = new BasicHttpResponse(response.getStatusLine()); + reconstructed.setHeaders(response.getAllHeaders()); + + final CombinedEntity combinedEntity = new CombinedEntity(resource, instream); + final HttpEntity entity = response.getEntity(); + if (entity != null) { + combinedEntity.setContentType(entity.getContentType()); + combinedEntity.setContentEncoding(entity.getContentEncoding()); + combinedEntity.setChunked(entity.isChunked()); + } + reconstructed.setEntity(combinedEntity); + return (CloseableHttpResponse) Proxy.newProxyInstance( + ResponseProxyHandler.class.getClassLoader(), + new Class<?>[] { CloseableHttpResponse.class }, + new ResponseProxyHandler(reconstructed) { + + @Override + public void close() throws IOException { + response.close(); + } + + }); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Variant.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Variant.java new file mode 100644 index 000000000..f631b0211 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/Variant.java @@ -0,0 +1,55 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry; + +/** Records a set of information describing a cached variant. */ +class Variant { + + private final String variantKey; + private final String cacheKey; + private final HttpCacheEntry entry; + + public Variant(final String variantKey, final String cacheKey, final HttpCacheEntry entry) { + this.variantKey = variantKey; + this.cacheKey = cacheKey; + this.entry = entry; + } + + public String getVariantKey() { + return variantKey; + } + + public String getCacheKey() { + return cacheKey; + } + + public HttpCacheEntry getEntry() { + return entry; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java new file mode 100644 index 000000000..a3c2ce67d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java @@ -0,0 +1,370 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.client.cache; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; + +/** This class provides for parsing and understanding Warning headers. As + * the Warning header can be multi-valued, but the values can contain + * separators like commas inside quoted strings, we cannot use the regular + * {@link Header#getElements()} call to access the values. + */ +class WarningValue { + + private int offs; + private int init_offs; + private final String src; + private int warnCode; + private String warnAgent; + private String warnText; + private Date warnDate; + + WarningValue(final String s) { + this(s, 0); + } + + WarningValue(final String s, final int offs) { + this.offs = this.init_offs = offs; + this.src = s; + consumeWarnValue(); + } + + /** Returns an array of the parseable warning values contained + * in the given header value, which is assumed to be a + * Warning header. Improperly formatted warning values will be + * skipped, in keeping with the philosophy of "ignore what you + * cannot understand." + * @param h Warning {@link Header} to parse + * @return array of <code>WarnValue</code> objects + */ + public static WarningValue[] getWarningValues(final Header h) { + final List<WarningValue> out = new ArrayList<WarningValue>(); + final String src = h.getValue(); + int offs = 0; + while(offs < src.length()) { + try { + final WarningValue wv = new WarningValue(src, offs); + out.add(wv); + offs = wv.offs; + } catch (final IllegalArgumentException e) { + final int nextComma = src.indexOf(',', offs); + if (nextComma == -1) { + break; + } + offs = nextComma + 1; + } + } + final WarningValue[] wvs = {}; + return out.toArray(wvs); + } + + /* + * LWS = [CRLF] 1*( SP | HT ) + * CRLF = CR LF + */ + protected void consumeLinearWhitespace() { + while(offs < src.length()) { + switch(src.charAt(offs)) { + case '\r': + if (offs+2 >= src.length() + || src.charAt(offs+1) != '\n' + || (src.charAt(offs+2) != ' ' + && src.charAt(offs+2) != '\t')) { + return; + } + offs += 2; + break; + case ' ': + case '\t': + break; + default: + return; + } + offs++; + } + } + + /* + * CHAR = <any US-ASCII character (octets 0 - 127)> + */ + private boolean isChar(final char c) { + final int i = c; + return (i >= 0 && i <= 127); + } + + /* + * CTL = <any US-ASCII control character + (octets 0 - 31) and DEL (127)> + */ + private boolean isControl(final char c) { + final int i = c; + return (i == 127 || (i >=0 && i <= 31)); + } + + /* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ + private boolean isSeparator(final char c) { + return (c == '(' || c == ')' || c == '<' || c == '>' + || c == '@' || c == ',' || c == ';' || c == ':' + || c == '\\' || c == '\"' || c == '/' + || c == '[' || c == ']' || c == '?' || c == '=' + || c == '{' || c == '}' || c == ' ' || c == '\t'); + } + + /* + * token = 1*<any CHAR except CTLs or separators> + */ + protected void consumeToken() { + if (!isTokenChar(src.charAt(offs))) { + parseError(); + } + while(offs < src.length()) { + if (!isTokenChar(src.charAt(offs))) { + break; + } + offs++; + } + } + + private boolean isTokenChar(final char c) { + return (isChar(c) && !isControl(c) && !isSeparator(c)); + } + + private static final String TOPLABEL = "\\p{Alpha}([\\p{Alnum}-]*\\p{Alnum})?"; + private static final String DOMAINLABEL = "\\p{Alnum}([\\p{Alnum}-]*\\p{Alnum})?"; + private static final String HOSTNAME = "(" + DOMAINLABEL + "\\.)*" + TOPLABEL + "\\.?"; + private static final String IPV4ADDRESS = "\\d+\\.\\d+\\.\\d+\\.\\d+"; + private static final String HOST = "(" + HOSTNAME + ")|(" + IPV4ADDRESS + ")"; + private static final String PORT = "\\d*"; + private static final String HOSTPORT = "(" + HOST + ")(\\:" + PORT + ")?"; + private static final Pattern HOSTPORT_PATTERN = Pattern.compile(HOSTPORT); + + protected void consumeHostPort() { + final Matcher m = HOSTPORT_PATTERN.matcher(src.substring(offs)); + if (!m.find()) { + parseError(); + } + if (m.start() != 0) { + parseError(); + } + offs += m.end(); + } + + + /* + * warn-agent = ( host [ ":" port ] ) | pseudonym + * pseudonym = token + */ + protected void consumeWarnAgent() { + final int curr_offs = offs; + try { + consumeHostPort(); + warnAgent = src.substring(curr_offs, offs); + consumeCharacter(' '); + return; + } catch (final IllegalArgumentException e) { + offs = curr_offs; + } + consumeToken(); + warnAgent = src.substring(curr_offs, offs); + consumeCharacter(' '); + } + + /* + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + */ + protected void consumeQuotedString() { + if (src.charAt(offs) != '\"') { + parseError(); + } + offs++; + boolean foundEnd = false; + while(offs < src.length() && !foundEnd) { + final char c = src.charAt(offs); + if (offs + 1 < src.length() && c == '\\' + && isChar(src.charAt(offs+1))) { + offs += 2; // consume quoted-pair + } else if (c == '\"') { + foundEnd = true; + offs++; + } else if (c != '\"' && !isControl(c)) { + offs++; + } else { + parseError(); + } + } + if (!foundEnd) { + parseError(); + } + } + + /* + * warn-text = quoted-string + */ + protected void consumeWarnText() { + final int curr = offs; + consumeQuotedString(); + warnText = src.substring(curr, offs); + } + + private static final String MONTH = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; + private static final String WEEKDAY = "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday"; + private static final String WKDAY = "Mon|Tue|Wed|Thu|Fri|Sat|Sun"; + private static final String TIME = "\\d{2}:\\d{2}:\\d{2}"; + private static final String DATE3 = "(" + MONTH + ") ( |\\d)\\d"; + private static final String DATE2 = "\\d{2}-(" + MONTH + ")-\\d{2}"; + private static final String DATE1 = "\\d{2} (" + MONTH + ") \\d{4}"; + private static final String ASCTIME_DATE = "(" + WKDAY + ") (" + DATE3 + ") (" + TIME + ") \\d{4}"; + private static final String RFC850_DATE = "(" + WEEKDAY + "), (" + DATE2 + ") (" + TIME + ") GMT"; + private static final String RFC1123_DATE = "(" + WKDAY + "), (" + DATE1 + ") (" + TIME + ") GMT"; + private static final String HTTP_DATE = "(" + RFC1123_DATE + ")|(" + RFC850_DATE + ")|(" + ASCTIME_DATE + ")"; + private static final String WARN_DATE = "\"(" + HTTP_DATE + ")\""; + private static final Pattern WARN_DATE_PATTERN = Pattern.compile(WARN_DATE); + + /* + * warn-date = <"> HTTP-date <"> + */ + protected void consumeWarnDate() { + final int curr = offs; + final Matcher m = WARN_DATE_PATTERN.matcher(src.substring(offs)); + if (!m.lookingAt()) { + parseError(); + } + offs += m.end(); + warnDate = DateUtils.parseDate(src.substring(curr+1,offs-1)); + } + + /* + * warning-value = warn-code SP warn-agent SP warn-text [SP warn-date] + */ + protected void consumeWarnValue() { + consumeLinearWhitespace(); + consumeWarnCode(); + consumeWarnAgent(); + consumeWarnText(); + if (offs + 1 < src.length() && src.charAt(offs) == ' ' && src.charAt(offs+1) == '\"') { + consumeCharacter(' '); + consumeWarnDate(); + } + consumeLinearWhitespace(); + if (offs != src.length()) { + consumeCharacter(','); + } + } + + protected void consumeCharacter(final char c) { + if (offs + 1 > src.length() + || c != src.charAt(offs)) { + parseError(); + } + offs++; + } + + /* + * warn-code = 3DIGIT + */ + protected void consumeWarnCode() { + if (offs + 4 > src.length() + || !Character.isDigit(src.charAt(offs)) + || !Character.isDigit(src.charAt(offs + 1)) + || !Character.isDigit(src.charAt(offs + 2)) + || src.charAt(offs + 3) != ' ') { + parseError(); + } + warnCode = Integer.parseInt(src.substring(offs,offs+3)); + offs += 4; + } + + private void parseError() { + final String s = src.substring(init_offs); + throw new IllegalArgumentException("Bad warn code \"" + s + "\""); + } + + /** Returns the 3-digit code associated with this warning. + * @return <code>int</code> + */ + public int getWarnCode() { return warnCode; } + + /** Returns the "warn-agent" string associated with this warning, + * which is either the name or pseudonym of the server that added + * this particular Warning header. + * @return {@link String} + */ + public String getWarnAgent() { return warnAgent; } + + /** Returns the human-readable warning text for this warning. Note + * that the original quoted-string is returned here, including + * escaping for any contained characters. In other words, if the + * header was: + * <pre> + * Warning: 110 fred "Response is stale" + * </pre> + * then this method will return <code>"\"Response is stale\""</code> + * (surrounding quotes included). + * @return {@link String} + */ + public String getWarnText() { return warnText; } + + /** Returns the date and time when this warning was added, or + * <code>null</code> if a warning date was not supplied in the + * header. + * @return {@link Date} + */ + public Date getWarnDate() { return warnDate; } + + /** Formats a <code>WarningValue</code> as a {@link String} + * suitable for including in a header. For example, you can: + * <pre> + * WarningValue wv = ...; + * HttpResponse resp = ...; + * resp.addHeader("Warning", wv.toString()); + * </pre> + * @return {@link String} + */ + @Override + public String toString() { + if (warnDate != null) { + return String.format("%d %s %s \"%s\"", warnCode, + warnAgent, warnText, DateUtils.formatDate(warnDate)); + } else { + return String.format("%d %s %s", warnCode, warnAgent, warnText); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/package.html b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/package.html new file mode 100644 index 000000000..4c208e7a9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/cache/package.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<html> +<head> +<!-- +==================================================================== +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 +<http://www.apache.org/>. +--> +</head> +<body bgcolor="white"> + +<p> +This package contains a cache module that can be used for HTTP/1.1 +client-side caching. The primary classes in this package are the +{@link org.apache.http.impl.client.cache.CachingHttpClient}, +which is a drop-in replacement for +a {@link org.apache.http.impl.client.DefaultHttpClient} that adds +caching, and the {@link org.apache.http.impl.client.cache.CacheConfig} +class that can be used for configuring it. +</p> +</body> +</html> diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/package-info.java new file mode 100644 index 000000000..1efbf4cec --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/client/package-info.java @@ -0,0 +1,51 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default HTTP client implementation. + * <p/> + * The usual execution flow can be demonstrated by the code snippet below: + * <pre> + * CloseableHttpClient httpclient = HttpClients.createDefault(); + * try { + * HttpGet httpGet = new HttpGet("http://targethost/homepage"); + * CloseableHttpResponse response = httpclient.execute(httpGet); + * try { + * System.out.println(response.getStatusLine()); + * HttpEntity entity = response.getEntity(); + * // do something useful with the response body + * // and ensure it is fully consumed + * EntityUtils.consume(entity); + * } finally { + * response.close(); + * } + * } finally { + * httpclient.close(); + * } + * </pre> + */ +package ch.boye.httpclientandroidlib.impl.client; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractClientConnAdapter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractClientConnAdapter.java new file mode 100644 index 000000000..ea2524f6a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractClientConnAdapter.java @@ -0,0 +1,369 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Abstract adapter from {@link OperatedClientConnection operated} to + * {@link ManagedClientConnection managed} client connections. + * Read and write methods are delegated to the wrapped connection. + * Operations affecting the connection state have to be implemented + * by derived classes. Operations for querying the connection state + * are delegated to the wrapped connection if there is one, or + * return a default value if there is none. + * <p> + * This adapter tracks the checkpoints for reusable communication states, + * as indicated by {@link #markReusable markReusable} and queried by + * {@link #isMarkedReusable isMarkedReusable}. + * All send and receive operations will automatically clear the mark. + * <p> + * Connection release calls are delegated to the connection manager, + * if there is one. {@link #abortConnection abortConnection} will + * clear the reusability mark first. The connection manager is + * expected to tolerate multiple calls to the release method. + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +@NotThreadSafe +public abstract class AbstractClientConnAdapter implements ManagedClientConnection, HttpContext { + + /** + * The connection manager. + */ + private final ClientConnectionManager connManager; + + /** The wrapped connection. */ + private volatile OperatedClientConnection wrappedConnection; + + /** The reusability marker. */ + private volatile boolean markedReusable; + + /** True if the connection has been shut down or released. */ + private volatile boolean released; + + /** The duration this is valid for while idle (in ms). */ + private volatile long duration; + + /** + * Creates a new connection adapter. + * The adapter is initially <i>not</i> + * {@link #isMarkedReusable marked} as reusable. + * + * @param mgr the connection manager, or <code>null</code> + * @param conn the connection to wrap, or <code>null</code> + */ + protected AbstractClientConnAdapter(final ClientConnectionManager mgr, + final OperatedClientConnection conn) { + super(); + connManager = mgr; + wrappedConnection = conn; + markedReusable = false; + released = false; + duration = Long.MAX_VALUE; + } + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + protected synchronized void detach() { + wrappedConnection = null; + duration = Long.MAX_VALUE; + } + + protected OperatedClientConnection getWrappedConnection() { + return wrappedConnection; + } + + protected ClientConnectionManager getManager() { + return connManager; + } + + /** + * @deprecated (4.1) use {@link #assertValid(OperatedClientConnection)} + */ + @Deprecated + protected final void assertNotAborted() throws InterruptedIOException { + if (isReleased()) { + throw new InterruptedIOException("Connection has been shut down"); + } + } + + /** + * @since 4.1 + * @return value of released flag + */ + protected boolean isReleased() { + return released; + } + + /** + * Asserts that there is a valid wrapped connection to delegate to. + * + * @throws ConnectionShutdownException if there is no wrapped connection + * or connection has been aborted + */ + protected final void assertValid( + final OperatedClientConnection wrappedConn) throws ConnectionShutdownException { + if (isReleased() || wrappedConn == null) { + throw new ConnectionShutdownException(); + } + } + + public boolean isOpen() { + final OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) { + return false; + } + + return conn.isOpen(); + } + + public boolean isStale() { + if (isReleased()) { + return true; + } + final OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) { + return true; + } + + return conn.isStale(); + } + + public void setSocketTimeout(final int timeout) { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + conn.setSocketTimeout(timeout); + } + + public int getSocketTimeout() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getSocketTimeout(); + } + + public HttpConnectionMetrics getMetrics() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getMetrics(); + } + + public void flush() throws IOException { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + conn.flush(); + } + + public boolean isResponseAvailable(final int timeout) throws IOException { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.isResponseAvailable(timeout); + } + + public void receiveResponseEntity(final HttpResponse response) + throws HttpException, IOException { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + unmarkReusable(); + conn.receiveResponseEntity(response); + } + + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + unmarkReusable(); + return conn.receiveResponseHeader(); + } + + public void sendRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + unmarkReusable(); + conn.sendRequestEntity(request); + } + + public void sendRequestHeader(final HttpRequest request) + throws HttpException, IOException { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + unmarkReusable(); + conn.sendRequestHeader(request); + } + + public InetAddress getLocalAddress() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalAddress(); + } + + public int getLocalPort() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalPort(); + } + + public InetAddress getRemoteAddress() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemoteAddress(); + } + + public int getRemotePort() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemotePort(); + } + + public boolean isSecure() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.isSecure(); + } + + public void bind(final Socket socket) throws IOException { + throw new UnsupportedOperationException(); + } + + public Socket getSocket() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (!isOpen()) { + return null; + } + return conn.getSocket(); + } + + public SSLSession getSSLSession() { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (!isOpen()) { + return null; + } + + SSLSession result = null; + final Socket sock = conn.getSocket(); + if (sock instanceof SSLSocket) { + result = ((SSLSocket)sock).getSession(); + } + return result; + } + + public void markReusable() { + markedReusable = true; + } + + public void unmarkReusable() { + markedReusable = false; + } + + public boolean isMarkedReusable() { + return markedReusable; + } + + public void setIdleDuration(final long duration, final TimeUnit unit) { + if(duration > 0) { + this.duration = unit.toMillis(duration); + } else { + this.duration = -1; + } + } + + public synchronized void releaseConnection() { + if (released) { + return; + } + released = true; + connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS); + } + + public synchronized void abortConnection() { + if (released) { + return; + } + released = true; + unmarkReusable(); + try { + shutdown(); + } catch (final IOException ignore) { + } + connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS); + } + + public Object getAttribute(final String id) { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (conn instanceof HttpContext) { + return ((HttpContext) conn).getAttribute(id); + } else { + return null; + } + } + + public Object removeAttribute(final String id) { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (conn instanceof HttpContext) { + return ((HttpContext) conn).removeAttribute(id); + } else { + return null; + } + } + + public void setAttribute(final String id, final Object obj) { + final OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (conn instanceof HttpContext) { + ((HttpContext) conn).setAttribute(id, obj); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractPoolEntry.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractPoolEntry.java new file mode 100644 index 000000000..cbbe727b5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractPoolEntry.java @@ -0,0 +1,262 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.io.InterruptedIOException; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * A pool entry for use by connection manager implementations. + * Pool entries work in conjunction with an + * {@link AbstractClientConnAdapter adapter}. + * The adapter is handed out to applications that obtain a connection. + * The pool entry stores the underlying connection and tracks the + * {@link HttpRoute route} established. + * The adapter delegates methods for establishing the route to + * its pool entry. + * <p> + * If the managed connections is released or revoked, the adapter + * gets disconnected, but the pool entry still contains the + * underlying connection and the established route. + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +public abstract class AbstractPoolEntry { + + /** The connection operator. */ + protected final ClientConnectionOperator connOperator; + + /** The underlying connection being pooled or used. */ + protected final OperatedClientConnection connection; + + /** The route for which this entry gets allocated. */ + //@@@ currently accessed from connection manager(s) as attribute + //@@@ avoid that, derived classes should decide whether update is allowed + //@@@ SCCM: yes, TSCCM: no + protected volatile HttpRoute route; + + /** Connection state object */ + protected volatile Object state; + + /** The tracked route, or <code>null</code> before tracking starts. */ + protected volatile RouteTracker tracker; + + + /** + * Creates a new pool entry. + * + * @param connOperator the Connection Operator for this entry + * @param route the planned route for the connection, + * or <code>null</code> + */ + protected AbstractPoolEntry(final ClientConnectionOperator connOperator, + final HttpRoute route) { + super(); + Args.notNull(connOperator, "Connection operator"); + this.connOperator = connOperator; + this.connection = connOperator.createConnection(); + this.route = route; + this.tracker = null; + } + + /** + * Returns the state object associated with this pool entry. + * + * @return The state object + */ + public Object getState() { + return state; + } + + /** + * Assigns a state object to this pool entry. + * + * @param state The state object + */ + public void setState(final Object state) { + this.state = state; + } + + /** + * Opens the underlying connection. + * + * @param route the route along which to open the connection + * @param context the context for opening the connection + * @param params the parameters for opening the connection + * + * @throws IOException in case of a problem + */ + public void open(final HttpRoute route, + final HttpContext context, final HttpParams params) + throws IOException { + + Args.notNull(route, "Route"); + Args.notNull(params, "HTTP parameters"); + if (this.tracker != null) { + Asserts.check(!this.tracker.isConnected(), "Connection already open"); + } + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // opening of the connection will be tracked. + + this.tracker = new RouteTracker(route); + final HttpHost proxy = route.getProxyHost(); + + connOperator.openConnection + (this.connection, + (proxy != null) ? proxy : route.getTargetHost(), + route.getLocalAddress(), + context, params); + + final RouteTracker localTracker = tracker; // capture volatile + + // If this tracker was reset while connecting, + // fail early. + if (localTracker == null) { + throw new InterruptedIOException("Request aborted"); + } + + if (proxy == null) { + localTracker.connectTarget(this.connection.isSecure()); + } else { + localTracker.connectProxy(proxy, this.connection.isSecure()); + } + + } + + /** + * Tracks tunnelling of the connection to the target. + * The tunnel has to be established outside by sending a CONNECT + * request to the (last) proxy. + * + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelTarget(final boolean secure, final HttpParams params) + throws IOException { + + Args.notNull(params, "HTTP parameters"); + Asserts.notNull(this.tracker, "Route tracker"); + Asserts.check(this.tracker.isConnected(), "Connection not open"); + Asserts.check(!this.tracker.isTunnelled(), "Connection is already tunnelled"); + + this.connection.update(null, tracker.getTargetHost(), + secure, params); + this.tracker.tunnelTarget(secure); + } + + /** + * Tracks tunnelling of the connection to a chained proxy. + * The tunnel has to be established outside by sending a CONNECT + * request to the previous proxy. + * + * @param next the proxy to which the tunnel was established. + * See {@link ch.boye.httpclientandroidlib.conn.ManagedClientConnection#tunnelProxy + * ManagedClientConnection.tunnelProxy} + * for details. + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelProxy(final HttpHost next, final boolean secure, final HttpParams params) + throws IOException { + + Args.notNull(next, "Next proxy"); + Args.notNull(params, "Parameters"); + + Asserts.notNull(this.tracker, "Route tracker"); + Asserts.check(this.tracker.isConnected(), "Connection not open"); + + this.connection.update(null, next, secure, params); + this.tracker.tunnelProxy(next, secure); + } + + /** + * Layers a protocol on top of an established tunnel. + * + * @param context the context for layering + * @param params the parameters for layering + * + * @throws IOException in case of a problem + */ + public void layerProtocol(final HttpContext context, final HttpParams params) + throws IOException { + + //@@@ is context allowed to be null? depends on operator? + Args.notNull(params, "HTTP parameters"); + Asserts.notNull(this.tracker, "Route tracker"); + Asserts.check(this.tracker.isConnected(), "Connection not open"); + Asserts.check(this.tracker.isTunnelled(), "Protocol layering without a tunnel not supported"); + Asserts.check(!this.tracker.isLayered(), "Multiple protocol layering not supported"); + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // layering on top of the connection will be tracked. + + final HttpHost target = tracker.getTargetHost(); + + connOperator.updateSecureConnection(this.connection, target, + context, params); + + this.tracker.layerProtocol(this.connection.isSecure()); + + } + + /** + * Shuts down the entry. + * + * If {@link #open(HttpRoute, HttpContext, HttpParams)} is in progress, + * this will cause that open to possibly throw an {@link IOException}. + */ + protected void shutdownEntry() { + tracker = null; + state = null; + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractPooledConnAdapter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractPooledConnAdapter.java new file mode 100644 index 000000000..47f0feda5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/AbstractPooledConnAdapter.java @@ -0,0 +1,191 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Abstract adapter from pool {@link AbstractPoolEntry entries} to + * {@link ch.boye.httpclientandroidlib.conn.ManagedClientConnection managed} + * client connections. + * The connection in the pool entry is used to initialize the base class. + * In addition, methods to establish a route are delegated to the + * pool entry. {@link #shutdown shutdown} and {@link #close close} + * will clear the tracked route in the pool entry and call the + * respective method of the wrapped connection. + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapter { + + /** The wrapped pool entry. */ + protected volatile AbstractPoolEntry poolEntry; + + /** + * Creates a new connection adapter. + * + * @param manager the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected AbstractPooledConnAdapter(final ClientConnectionManager manager, + final AbstractPoolEntry entry) { + super(manager, entry.connection); + this.poolEntry = entry; + } + + public String getId() { + return null; + } + + /** + * Obtains the pool entry. + * + * @return the pool entry, or <code>null</code> if detached + * + * @deprecated (4.0.1) + */ + @Deprecated + protected AbstractPoolEntry getPoolEntry() { + return this.poolEntry; + } + + /** + * Asserts that there is a valid pool entry. + * + * @throws ConnectionShutdownException if there is no pool entry + * or connection has been aborted + * + * @see #assertValid(OperatedClientConnection) + */ + protected void assertValid(final AbstractPoolEntry entry) { + if (isReleased() || entry == null) { + throw new ConnectionShutdownException(); + } + } + + /** + * @deprecated (4.1) use {@link #assertValid(AbstractPoolEntry)} + */ + @Deprecated + protected final void assertAttached() { + if (poolEntry == null) { + throw new ConnectionShutdownException(); + } + } + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + @Override + protected synchronized void detach() { + poolEntry = null; + super.detach(); + } + + public HttpRoute getRoute() { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + return (entry.tracker == null) ? null : entry.tracker.toRoute(); + } + + public void open(final HttpRoute route, + final HttpContext context, final HttpParams params) + throws IOException { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + entry.open(route, context, params); + } + + public void tunnelTarget(final boolean secure, final HttpParams params) + throws IOException { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + entry.tunnelTarget(secure, params); + } + + public void tunnelProxy(final HttpHost next, final boolean secure, final HttpParams params) + throws IOException { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + entry.tunnelProxy(next, secure, params); + } + + public void layerProtocol(final HttpContext context, final HttpParams params) + throws IOException { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + entry.layerProtocol(context, params); + } + + public void close() throws IOException { + final AbstractPoolEntry entry = getPoolEntry(); + if (entry != null) { + entry.shutdownEntry(); + } + + final OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.close(); + } + } + + public void shutdown() throws IOException { + final AbstractPoolEntry entry = getPoolEntry(); + if (entry != null) { + entry.shutdownEntry(); + } + + final OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.shutdown(); + } + } + + public Object getState() { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + return entry.getState(); + } + + public void setState(final Object state) { + final AbstractPoolEntry entry = getPoolEntry(); + assertValid(entry); + entry.setState(state); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/BasicClientConnectionManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/BasicClientConnectionManager.java new file mode 100644 index 000000000..176e28150 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/BasicClientConnectionManager.java @@ -0,0 +1,276 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * A connection manager for a single connection. This connection manager maintains only one active + * connection. Even though this class is fully thread-safe it ought to be used by one execution + * thread only, as only one thread a time can lease the connection at a time. + * <p/> + * This connection manager will make an effort to reuse the connection for subsequent requests + * with the same {@link HttpRoute route}. It will, however, close the existing connection and + * open it for the given route, if the route of the persistent connection does not match that + * of the connection request. If the connection has been already been allocated + * {@link IllegalStateException} is thrown. + * <p/> + * This connection manager implementation should be used inside an EJB container instead of + * {@link PoolingClientConnectionManager}. + * + * @since 4.2 + * + * @deprecated (4.3) use {@link BasicHttpClientConnectionManager}. + */ +@ThreadSafe +@Deprecated +public class BasicClientConnectionManager implements ClientConnectionManager { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private static final AtomicLong COUNTER = new AtomicLong(); + + /** The message to be logged on multiple allocation. */ + public final static String MISUSE_MESSAGE = + "Invalid use of BasicClientConnManager: connection still allocated.\n" + + "Make sure to release the connection before allocating another one."; + + /** The schemes supported by this connection manager. */ + private final SchemeRegistry schemeRegistry; + + /** The operator for opening and updating connections. */ + private final ClientConnectionOperator connOperator; + + /** The one and only entry in this pool. */ + @GuardedBy("this") + private HttpPoolEntry poolEntry; + + /** The currently issued managed connection, if any. */ + @GuardedBy("this") + private ManagedClientConnectionImpl conn; + + /** Indicates whether this connection manager is shut down. */ + @GuardedBy("this") + private volatile boolean shutdown; + + /** + * Creates a new simple connection manager. + * + * @param schreg the scheme registry + */ + public BasicClientConnectionManager(final SchemeRegistry schreg) { + Args.notNull(schreg, "Scheme registry"); + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + } + + public BasicClientConnectionManager() { + this(SchemeRegistryFactory.createDefault()); + } + + @Override + protected void finalize() throws Throwable { + try { + shutdown(); + } finally { // Make sure we call overridden method even if shutdown barfs + super.finalize(); + } + } + + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + protected ClientConnectionOperator createConnectionOperator(final SchemeRegistry schreg) { + return new DefaultClientConnectionOperator(schreg); + } + + public final ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + return new ClientConnectionRequest() { + + public void abortRequest() { + // Nothing to abort, since requests are immediate. + } + + public ManagedClientConnection getConnection( + final long timeout, final TimeUnit tunit) { + return BasicClientConnectionManager.this.getConnection( + route, state); + } + + }; + } + + private void assertNotShutdown() { + Asserts.check(!this.shutdown, "Connection manager has been shut down"); + } + + ManagedClientConnection getConnection(final HttpRoute route, final Object state) { + Args.notNull(route, "Route"); + synchronized (this) { + assertNotShutdown(); + if (this.log.isDebugEnabled()) { + this.log.debug("Get connection for route " + route); + } + Asserts.check(this.conn == null, MISUSE_MESSAGE); + if (this.poolEntry != null && !this.poolEntry.getPlannedRoute().equals(route)) { + this.poolEntry.close(); + this.poolEntry = null; + } + if (this.poolEntry == null) { + final String id = Long.toString(COUNTER.getAndIncrement()); + final OperatedClientConnection conn = this.connOperator.createConnection(); + this.poolEntry = new HttpPoolEntry(this.log, id, route, conn, 0, TimeUnit.MILLISECONDS); + } + final long now = System.currentTimeMillis(); + if (this.poolEntry.isExpired(now)) { + this.poolEntry.close(); + this.poolEntry.getTracker().reset(); + } + this.conn = new ManagedClientConnectionImpl(this, this.connOperator, this.poolEntry); + return this.conn; + } + } + + private void shutdownConnection(final HttpClientConnection conn) { + try { + conn.shutdown(); + } catch (final IOException iox) { + if (this.log.isDebugEnabled()) { + this.log.debug("I/O exception shutting down connection", iox); + } + } + } + + public void releaseConnection(final ManagedClientConnection conn, final long keepalive, final TimeUnit tunit) { + Args.check(conn instanceof ManagedClientConnectionImpl, "Connection class mismatch, " + + "connection not obtained from this manager"); + final ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn; + synchronized (managedConn) { + if (this.log.isDebugEnabled()) { + this.log.debug("Releasing connection " + conn); + } + if (managedConn.getPoolEntry() == null) { + return; // already released + } + final ClientConnectionManager manager = managedConn.getManager(); + Asserts.check(manager == this, "Connection not obtained from this manager"); + synchronized (this) { + if (this.shutdown) { + shutdownConnection(managedConn); + return; + } + try { + if (managedConn.isOpen() && !managedConn.isMarkedReusable()) { + shutdownConnection(managedConn); + } + if (managedConn.isMarkedReusable()) { + this.poolEntry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS); + if (this.log.isDebugEnabled()) { + final String s; + if (keepalive > 0) { + s = "for " + keepalive + " " + tunit; + } else { + s = "indefinitely"; + } + this.log.debug("Connection can be kept alive " + s); + } + } + } finally { + managedConn.detach(); + this.conn = null; + if (this.poolEntry.isClosed()) { + this.poolEntry = null; + } + } + } + } + } + + public void closeExpiredConnections() { + synchronized (this) { + assertNotShutdown(); + final long now = System.currentTimeMillis(); + if (this.poolEntry != null && this.poolEntry.isExpired(now)) { + this.poolEntry.close(); + this.poolEntry.getTracker().reset(); + } + } + } + + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + Args.notNull(tunit, "Time unit"); + synchronized (this) { + assertNotShutdown(); + long time = tunit.toMillis(idletime); + if (time < 0) { + time = 0; + } + final long deadline = System.currentTimeMillis() - time; + if (this.poolEntry != null && this.poolEntry.getUpdated() <= deadline) { + this.poolEntry.close(); + this.poolEntry.getTracker().reset(); + } + } + } + + public void shutdown() { + synchronized (this) { + this.shutdown = true; + try { + if (this.poolEntry != null) { + this.poolEntry.close(); + } + } finally { + this.poolEntry = null; + this.conn = null; + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/BasicHttpClientConnectionManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/BasicHttpClientConnectionManager.java new file mode 100644 index 000000000..1049a4151 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/BasicHttpClientConnectionManager.java @@ -0,0 +1,370 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.config.Registry; +import ch.boye.httpclientandroidlib.config.RegistryBuilder; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.conn.ConnectionRequest; +import ch.boye.httpclientandroidlib.conn.DnsResolver; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.ssl.SSLConnectionSocketFactory; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.LangUtils; + +/** + * A connection manager for a single connection. This connection manager maintains only one active + * connection. Even though this class is fully thread-safe it ought to be used by one execution + * thread only, as only one thread a time can lease the connection at a time. + * <p/> + * This connection manager will make an effort to reuse the connection for subsequent requests + * with the same {@link HttpRoute route}. It will, however, close the existing connection and + * open it for the given route, if the route of the persistent connection does not match that + * of the connection request. If the connection has been already been allocated + * {@link IllegalStateException} is thrown. + * <p/> + * This connection manager implementation should be used inside an EJB container instead of + * {@link PoolingHttpClientConnectionManager}. + * + * @since 4.3 + */ +@ThreadSafe +public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final HttpClientConnectionOperator connectionOperator; + private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; + + @GuardedBy("this") + private ManagedHttpClientConnection conn; + + @GuardedBy("this") + private HttpRoute route; + + @GuardedBy("this") + private Object state; + + @GuardedBy("this") + private long updated; + + @GuardedBy("this") + private long expiry; + + @GuardedBy("this") + private boolean leased; + + @GuardedBy("this") + private SocketConfig socketConfig; + + @GuardedBy("this") + private ConnectionConfig connConfig; + + private final AtomicBoolean isShutdown; + + private static Registry<ConnectionSocketFactory> getDefaultRegistry() { + return RegistryBuilder.<ConnectionSocketFactory>create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", SSLConnectionSocketFactory.getSocketFactory()) + .build(); + } + + public BasicHttpClientConnectionManager( + final Lookup<ConnectionSocketFactory> socketFactoryRegistry, + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, + final SchemePortResolver schemePortResolver, + final DnsResolver dnsResolver) { + super(); + this.connectionOperator = new HttpClientConnectionOperator( + socketFactoryRegistry, schemePortResolver, dnsResolver); + this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; + this.expiry = Long.MAX_VALUE; + this.socketConfig = SocketConfig.DEFAULT; + this.connConfig = ConnectionConfig.DEFAULT; + this.isShutdown = new AtomicBoolean(false); + } + + public BasicHttpClientConnectionManager( + final Lookup<ConnectionSocketFactory> socketFactoryRegistry, + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { + this(socketFactoryRegistry, connFactory, null, null); + } + + public BasicHttpClientConnectionManager( + final Lookup<ConnectionSocketFactory> socketFactoryRegistry) { + this(socketFactoryRegistry, null, null, null); + } + + public BasicHttpClientConnectionManager() { + this(getDefaultRegistry(), null, null, null); + } + + @Override + protected void finalize() throws Throwable { + try { + shutdown(); + } finally { // Make sure we call overridden method even if shutdown barfs + super.finalize(); + } + } + + public void close() { + shutdown(); + } + + HttpRoute getRoute() { + return route; + } + + Object getState() { + return state; + } + + public synchronized SocketConfig getSocketConfig() { + return socketConfig; + } + + public synchronized void setSocketConfig(final SocketConfig socketConfig) { + this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; + } + + public synchronized ConnectionConfig getConnectionConfig() { + return connConfig; + } + + public synchronized void setConnectionConfig(final ConnectionConfig connConfig) { + this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT; + } + + public final ConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + Args.notNull(route, "Route"); + return new ConnectionRequest() { + + public boolean cancel() { + // Nothing to abort, since requests are immediate. + return false; + } + + public HttpClientConnection get(final long timeout, final TimeUnit tunit) { + return BasicHttpClientConnectionManager.this.getConnection( + route, state); + } + + }; + } + + private void closeConnection() { + if (this.conn != null) { + this.log.debug("Closing connection"); + try { + this.conn.close(); + } catch (final IOException iox) { + if (this.log.isDebugEnabled()) { + this.log.debug("I/O exception closing connection", iox); + } + } + this.conn = null; + } + } + + private void shutdownConnection() { + if (this.conn != null) { + this.log.debug("Shutting down connection"); + try { + this.conn.shutdown(); + } catch (final IOException iox) { + if (this.log.isDebugEnabled()) { + this.log.debug("I/O exception shutting down connection", iox); + } + } + this.conn = null; + } + } + + private void checkExpiry() { + if (this.conn != null && System.currentTimeMillis() >= this.expiry) { + if (this.log.isDebugEnabled()) { + this.log.debug("Connection expired @ " + new Date(this.expiry)); + } + closeConnection(); + } + } + + synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) { + Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down"); + if (this.log.isDebugEnabled()) { + this.log.debug("Get connection for route " + route); + } + Asserts.check(!this.leased, "Connection is still allocated"); + if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) { + closeConnection(); + } + this.route = route; + this.state = state; + checkExpiry(); + if (this.conn == null) { + this.conn = this.connFactory.create(route, this.connConfig); + } + this.leased = true; + return this.conn; + } + + public synchronized void releaseConnection( + final HttpClientConnection conn, + final Object state, + final long keepalive, final TimeUnit tunit) { + Args.notNull(conn, "Connection"); + Asserts.check(conn == this.conn, "Connection not obtained from this manager"); + if (this.log.isDebugEnabled()) { + this.log.debug("Releasing connection " + conn); + } + if (this.isShutdown.get()) { + return; + } + try { + this.updated = System.currentTimeMillis(); + if (!this.conn.isOpen()) { + this.conn = null; + this.route = null; + this.conn = null; + this.expiry = Long.MAX_VALUE; + } else { + this.state = state; + if (this.log.isDebugEnabled()) { + final String s; + if (keepalive > 0) { + s = "for " + keepalive + " " + tunit; + } else { + s = "indefinitely"; + } + this.log.debug("Connection can be kept alive " + s); + } + if (keepalive > 0) { + this.expiry = this.updated + tunit.toMillis(keepalive); + } else { + this.expiry = Long.MAX_VALUE; + } + } + } finally { + this.leased = false; + } + } + + public void connect( + final HttpClientConnection conn, + final HttpRoute route, + final int connectTimeout, + final HttpContext context) throws IOException { + Args.notNull(conn, "Connection"); + Args.notNull(route, "HTTP route"); + Asserts.check(conn == this.conn, "Connection not obtained from this manager"); + final HttpHost host; + if (route.getProxyHost() != null) { + host = route.getProxyHost(); + } else { + host = route.getTargetHost(); + } + final InetSocketAddress localAddress = route.getLocalSocketAddress(); + this.connectionOperator.connect(this.conn, host, localAddress, + connectTimeout, this.socketConfig, context); + } + + public void upgrade( + final HttpClientConnection conn, + final HttpRoute route, + final HttpContext context) throws IOException { + Args.notNull(conn, "Connection"); + Args.notNull(route, "HTTP route"); + Asserts.check(conn == this.conn, "Connection not obtained from this manager"); + this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context); + } + + public void routeComplete( + final HttpClientConnection conn, + final HttpRoute route, + final HttpContext context) throws IOException { + } + + public synchronized void closeExpiredConnections() { + if (this.isShutdown.get()) { + return; + } + if (!this.leased) { + checkExpiry(); + } + } + + public synchronized void closeIdleConnections(final long idletime, final TimeUnit tunit) { + Args.notNull(tunit, "Time unit"); + if (this.isShutdown.get()) { + return; + } + if (!this.leased) { + long time = tunit.toMillis(idletime); + if (time < 0) { + time = 0; + } + final long deadline = System.currentTimeMillis() - time; + if (this.updated <= deadline) { + closeConnection(); + } + } + } + + public synchronized void shutdown() { + if (this.isShutdown.compareAndSet(false, true)) { + shutdownConnection(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPool.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPool.java new file mode 100644 index 000000000..040960c71 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPool.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.pool.AbstractConnPool; +import ch.boye.httpclientandroidlib.pool.ConnFactory; + +/** + * @since 4.3 + */ +@ThreadSafe +class CPool extends AbstractConnPool<HttpRoute, ManagedHttpClientConnection, CPoolEntry> { + + private static final AtomicLong COUNTER = new AtomicLong(); + + public HttpClientAndroidLog log = new HttpClientAndroidLog(CPool.class); + private final long timeToLive; + private final TimeUnit tunit; + + public CPool( + final ConnFactory<HttpRoute, ManagedHttpClientConnection> connFactory, + final int defaultMaxPerRoute, final int maxTotal, + final long timeToLive, final TimeUnit tunit) { + super(connFactory, defaultMaxPerRoute, maxTotal); + this.timeToLive = timeToLive; + this.tunit = tunit; + } + + @Override + protected CPoolEntry createEntry(final HttpRoute route, final ManagedHttpClientConnection conn) { + final String id = Long.toString(COUNTER.getAndIncrement()); + return new CPoolEntry(this.log, id, route, conn, this.timeToLive, this.tunit); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPoolEntry.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPoolEntry.java new file mode 100644 index 000000000..f5ecfdc5e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPoolEntry.java @@ -0,0 +1,101 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.pool.PoolEntry; + +/** + * @since 4.3 + */ +@ThreadSafe +class CPoolEntry extends PoolEntry<HttpRoute, ManagedHttpClientConnection> { + + public HttpClientAndroidLog log; + private volatile boolean routeComplete; + + public CPoolEntry( + final HttpClientAndroidLog log, + final String id, + final HttpRoute route, + final ManagedHttpClientConnection conn, + final long timeToLive, final TimeUnit tunit) { + super(id, route, conn, timeToLive, tunit); + this.log = log; + } + + public void markRouteComplete() { + this.routeComplete = true; + } + + public boolean isRouteComplete() { + return this.routeComplete; + } + + public void closeConnection() throws IOException { + final HttpClientConnection conn = getConnection(); + conn.close(); + } + + public void shutdownConnection() throws IOException { + final HttpClientConnection conn = getConnection(); + conn.shutdown(); + } + + @Override + public boolean isExpired(final long now) { + final boolean expired = super.isExpired(now); + if (expired && this.log.isDebugEnabled()) { + this.log.debug("Connection " + this + " expired @ " + new Date(getExpiry())); + } + return expired; + } + + @Override + public boolean isClosed() { + final HttpClientConnection conn = getConnection(); + return !conn.isOpen(); + } + + @Override + public void close() { + try { + closeConnection(); + } catch (final IOException ex) { + this.log.debug("I/O error closing connection", ex); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPoolProxy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPoolProxy.java new file mode 100644 index 000000000..9ac67a10d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/CPoolProxy.java @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +import javax.net.ssl.SSLSession; + +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * @since 4.3 + */ +@NotThreadSafe +class CPoolProxy implements ManagedHttpClientConnection, HttpContext { + + private volatile CPoolEntry poolEntry; + + CPoolProxy(final CPoolEntry entry) { + super(); + this.poolEntry = entry; + } + + CPoolEntry getPoolEntry() { + return this.poolEntry; + } + + CPoolEntry detach() { + final CPoolEntry local = this.poolEntry; + this.poolEntry = null; + return local; + } + + ManagedHttpClientConnection getConnection() { + final CPoolEntry local = this.poolEntry; + if (local == null) { + return null; + } + return local.getConnection(); + } + + ManagedHttpClientConnection getValidConnection() { + final ManagedHttpClientConnection conn = getConnection(); + if (conn == null) { + throw new ConnectionShutdownException(); + } + return conn; + } + + public void close() throws IOException { + final CPoolEntry local = this.poolEntry; + if (local != null) { + local.closeConnection(); + } + } + + public void shutdown() throws IOException { + final CPoolEntry local = this.poolEntry; + if (local != null) { + local.shutdownConnection(); + } + } + + public boolean isOpen() { + final CPoolEntry local = this.poolEntry; + if (local != null) { + return !local.isClosed(); + } else { + return false; + } + } + + public boolean isStale() { + final HttpClientConnection conn = getConnection(); + if (conn != null) { + return conn.isStale(); + } else { + return true; + } + } + + public void setSocketTimeout(final int timeout) { + getValidConnection().setSocketTimeout(timeout); + } + + public int getSocketTimeout() { + return getValidConnection().getSocketTimeout(); + } + + public String getId() { + return getValidConnection().getId(); + } + + public void bind(final Socket socket) throws IOException { + getValidConnection().bind(socket); + } + + public Socket getSocket() { + return getValidConnection().getSocket(); + } + + public SSLSession getSSLSession() { + return getValidConnection().getSSLSession(); + } + + public boolean isResponseAvailable(final int timeout) throws IOException { + return getValidConnection().isResponseAvailable(timeout); + } + + public void sendRequestHeader(final HttpRequest request) throws HttpException, IOException { + getValidConnection().sendRequestHeader(request); + } + + public void sendRequestEntity(final HttpEntityEnclosingRequest request) throws HttpException, IOException { + getValidConnection().sendRequestEntity(request); + } + + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + return getValidConnection().receiveResponseHeader(); + } + + public void receiveResponseEntity(final HttpResponse response) throws HttpException, IOException { + getValidConnection().receiveResponseEntity(response); + } + + public void flush() throws IOException { + getValidConnection().flush(); + } + + public HttpConnectionMetrics getMetrics() { + return getValidConnection().getMetrics(); + } + + public InetAddress getLocalAddress() { + return getValidConnection().getLocalAddress(); + } + + public int getLocalPort() { + return getValidConnection().getLocalPort(); + } + + public InetAddress getRemoteAddress() { + return getValidConnection().getRemoteAddress(); + } + + public int getRemotePort() { + return getValidConnection().getRemotePort(); + } + + public Object getAttribute(final String id) { + final ManagedHttpClientConnection conn = getValidConnection(); + if (conn instanceof HttpContext) { + return ((HttpContext) conn).getAttribute(id); + } else { + return null; + } + } + + public void setAttribute(final String id, final Object obj) { + final ManagedHttpClientConnection conn = getValidConnection(); + if (conn instanceof HttpContext) { + ((HttpContext) conn).setAttribute(id, obj); + } + } + + public Object removeAttribute(final String id) { + final ManagedHttpClientConnection conn = getValidConnection(); + if (conn instanceof HttpContext) { + return ((HttpContext) conn).removeAttribute(id); + } else { + return null; + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("CPoolProxy{"); + final ManagedHttpClientConnection conn = getConnection(); + if (conn != null) { + sb.append(conn); + } else { + sb.append("detached"); + } + sb.append('}'); + return sb.toString(); + } + + public static HttpClientConnection newProxy(final CPoolEntry poolEntry) { + return new CPoolProxy(poolEntry); + } + + private static CPoolProxy getProxy(final HttpClientConnection conn) { + if (!CPoolProxy.class.isInstance(conn)) { + throw new IllegalStateException("Unexpected connection proxy class: " + conn.getClass()); + } + return CPoolProxy.class.cast(conn); + } + + public static CPoolEntry getPoolEntry(final HttpClientConnection proxy) { + final CPoolEntry entry = getProxy(proxy).getPoolEntry(); + if (entry == null) { + throw new ConnectionShutdownException(); + } + return entry; + } + + public static CPoolEntry detach(final HttpClientConnection conn) { + return getProxy(conn).detach(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ConnectionShutdownException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ConnectionShutdownException.java new file mode 100644 index 000000000..03820a38f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ConnectionShutdownException.java @@ -0,0 +1,50 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Signals that the connection has been shut down or released back to the + * the connection pool + * + * @since 4.1 + */ +@Immutable +public class ConnectionShutdownException extends IllegalStateException { + + private static final long serialVersionUID = 5868657401162844497L; + + /** + * Creates a new ConnectionShutdownException with a <tt>null</tt> detail message. + */ + public ConnectionShutdownException() { + super(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnection.java new file mode 100644 index 000000000..f9003b144 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnection.java @@ -0,0 +1,292 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.impl.SocketHttpClientConnection; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.params.BasicHttpParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.params.HttpProtocolParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of an operated client connection. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link ManagedHttpClientConnectionFactory}. + */ +@NotThreadSafe // connSecure, targetHost +@Deprecated +public class DefaultClientConnection extends SocketHttpClientConnection + implements OperatedClientConnection, ManagedHttpClientConnection, HttpContext { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + public HttpClientAndroidLog headerLog = new HttpClientAndroidLog("ch.boye.httpclientandroidlib.headers"); + public HttpClientAndroidLog wireLog = new HttpClientAndroidLog("ch.boye.httpclientandroidlib.wire"); + + /** The unconnected socket */ + private volatile Socket socket; + + /** The target host of this connection. */ + private HttpHost targetHost; + + /** Whether this connection is secure. */ + private boolean connSecure; + + /** True if this connection was shutdown. */ + private volatile boolean shutdown; + + /** connection specific attributes */ + private final Map<String, Object> attributes; + + public DefaultClientConnection() { + super(); + this.attributes = new HashMap<String, Object>(); + } + + public String getId() { + return null; + } + + public final HttpHost getTargetHost() { + return this.targetHost; + } + + public final boolean isSecure() { + return this.connSecure; + } + + @Override + public final Socket getSocket() { + return this.socket; + } + + public SSLSession getSSLSession() { + if (this.socket instanceof SSLSocket) { + return ((SSLSocket) this.socket).getSession(); + } else { + return null; + } + } + + public void opening(final Socket sock, final HttpHost target) throws IOException { + assertNotOpen(); + this.socket = sock; + this.targetHost = target; + + // Check for shutdown after assigning socket, so that + if (this.shutdown) { + sock.close(); // allow this to throw... + // ...but if it doesn't, explicitly throw one ourselves. + throw new InterruptedIOException("Connection already shutdown"); + } + } + + public void openCompleted(final boolean secure, final HttpParams params) throws IOException { + Args.notNull(params, "Parameters"); + assertNotOpen(); + this.connSecure = secure; + bind(this.socket, params); + } + + /** + * Force-closes this connection. + * If the connection is still in the process of being open (the method + * {@link #opening opening} was already called but + * {@link #openCompleted openCompleted} was not), the associated + * socket that is being connected to a remote address will be closed. + * That will interrupt a thread that is blocked on connecting + * the socket. + * If the connection is not yet open, this will prevent the connection + * from being opened. + * + * @throws IOException in case of a problem + */ + @Override + public void shutdown() throws IOException { + shutdown = true; + try { + super.shutdown(); + if (log.isDebugEnabled()) { + log.debug("Connection " + this + " shut down"); + } + final Socket sock = this.socket; // copy volatile attribute + if (sock != null) { + sock.close(); + } + } catch (final IOException ex) { + log.debug("I/O error shutting down connection", ex); + } + } + + @Override + public void close() throws IOException { + try { + super.close(); + if (log.isDebugEnabled()) { + log.debug("Connection " + this + " closed"); + } + } catch (final IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + + @Override + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + SessionInputBuffer inbuffer = super.createSessionInputBuffer( + socket, + buffersize > 0 ? buffersize : 8192, + params); + if (wireLog.isDebugEnabled()) { + inbuffer = new LoggingSessionInputBuffer( + inbuffer, + new Wire(wireLog), + HttpProtocolParams.getHttpElementCharset(params)); + } + return inbuffer; + } + + @Override + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + SessionOutputBuffer outbuffer = super.createSessionOutputBuffer( + socket, + buffersize > 0 ? buffersize : 8192, + params); + if (wireLog.isDebugEnabled()) { + outbuffer = new LoggingSessionOutputBuffer( + outbuffer, + new Wire(wireLog), + HttpProtocolParams.getHttpElementCharset(params)); + } + return outbuffer; + } + + @Override + protected HttpMessageParser<HttpResponse> createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new DefaultHttpResponseParser + (buffer, null, responseFactory, params); + } + + public void bind(final Socket socket) throws IOException { + bind(socket, new BasicHttpParams()); + } + + public void update(final Socket sock, final HttpHost target, + final boolean secure, final HttpParams params) + throws IOException { + + assertOpen(); + Args.notNull(target, "Target host"); + Args.notNull(params, "Parameters"); + + if (sock != null) { + this.socket = sock; + bind(sock, params); + } + targetHost = target; + connSecure = secure; + } + + @Override + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + final HttpResponse response = super.receiveResponseHeader(); + if (log.isDebugEnabled()) { + log.debug("Receiving response: " + response.getStatusLine()); + } + if (headerLog.isDebugEnabled()) { + headerLog.debug("<< " + response.getStatusLine().toString()); + final Header[] headers = response.getAllHeaders(); + for (final Header header : headers) { + headerLog.debug("<< " + header.toString()); + } + } + return response; + } + + @Override + public void sendRequestHeader(final HttpRequest request) throws HttpException, IOException { + if (log.isDebugEnabled()) { + log.debug("Sending request: " + request.getRequestLine()); + } + super.sendRequestHeader(request); + if (headerLog.isDebugEnabled()) { + headerLog.debug(">> " + request.getRequestLine().toString()); + final Header[] headers = request.getAllHeaders(); + for (final Header header : headers) { + headerLog.debug(">> " + header.toString()); + } + } + } + + public Object getAttribute(final String id) { + return this.attributes.get(id); + } + + public Object removeAttribute(final String id) { + return this.attributes.remove(id); + } + + public void setAttribute(final String id, final Object obj) { + this.attributes.put(id, obj); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnectionOperator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnectionOperator.java new file mode 100644 index 000000000..3cdf706ec --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnectionOperator.java @@ -0,0 +1,263 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.client.protocol.ClientContext; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ConnectTimeoutException; +import ch.boye.httpclientandroidlib.conn.DnsResolver; +import ch.boye.httpclientandroidlib.conn.HttpInetSocketAddress; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeLayeredSocketFactory; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeSocketFactory; +import ch.boye.httpclientandroidlib.params.HttpConnectionParams; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Default implementation of a {@link ClientConnectionOperator}. It uses a {@link SchemeRegistry} + * to look up {@link SchemeSocketFactory} objects. + * <p> + * This connection operator is multihome network aware and will attempt to retry failed connects + * against all known IP addresses sequentially until the connect is successful or all known + * addresses fail to respond. Please note the same + * {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT} value will be used + * for each connection attempt, so in the worst case the total elapsed time before timeout + * can be <code>CONNECTION_TIMEOUT * n</code> where <code>n</code> is the number of IP addresses + * of the given host. One can disable multihome support by overriding + * the {@link #resolveHostname(String)} method and returning only one IP address for the given + * host name. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link PoolingHttpClientConnectionManager}. + */ +@Deprecated +@ThreadSafe +public class DefaultClientConnectionOperator implements ClientConnectionOperator { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** The scheme registry for looking up socket factories. */ + protected final SchemeRegistry schemeRegistry; // @ThreadSafe + + /** the custom-configured DNS lookup mechanism. */ + protected final DnsResolver dnsResolver; + + /** + * Creates a new client connection operator for the given scheme registry. + * + * @param schemes the scheme registry + * + * @since 4.2 + */ + public DefaultClientConnectionOperator(final SchemeRegistry schemes) { + Args.notNull(schemes, "Scheme registry"); + this.schemeRegistry = schemes; + this.dnsResolver = new SystemDefaultDnsResolver(); + } + + /** + * Creates a new client connection operator for the given scheme registry + * and the given custom DNS lookup mechanism. + * + * @param schemes + * the scheme registry + * @param dnsResolver + * the custom DNS lookup mechanism + */ + public DefaultClientConnectionOperator(final SchemeRegistry schemes,final DnsResolver dnsResolver) { + Args.notNull(schemes, "Scheme registry"); + + Args.notNull(dnsResolver, "DNS resolver"); + + this.schemeRegistry = schemes; + this.dnsResolver = dnsResolver; + } + + public OperatedClientConnection createConnection() { + return new DefaultClientConnection(); + } + + private SchemeRegistry getSchemeRegistry(final HttpContext context) { + SchemeRegistry reg = (SchemeRegistry) context.getAttribute( + ClientContext.SCHEME_REGISTRY); + if (reg == null) { + reg = this.schemeRegistry; + } + return reg; + } + + public void openConnection( + final OperatedClientConnection conn, + final HttpHost target, + final InetAddress local, + final HttpContext context, + final HttpParams params) throws IOException { + Args.notNull(conn, "Connection"); + Args.notNull(target, "Target host"); + Args.notNull(params, "HTTP parameters"); + Asserts.check(!conn.isOpen(), "Connection must not be open"); + + final SchemeRegistry registry = getSchemeRegistry(context); + final Scheme schm = registry.getScheme(target.getSchemeName()); + final SchemeSocketFactory sf = schm.getSchemeSocketFactory(); + + final InetAddress[] addresses = resolveHostname(target.getHostName()); + final int port = schm.resolvePort(target.getPort()); + for (int i = 0; i < addresses.length; i++) { + final InetAddress address = addresses[i]; + final boolean last = i == addresses.length - 1; + + Socket sock = sf.createSocket(params); + conn.opening(sock, target); + + final InetSocketAddress remoteAddress = new HttpInetSocketAddress(target, address, port); + InetSocketAddress localAddress = null; + if (local != null) { + localAddress = new InetSocketAddress(local, 0); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Connecting to " + remoteAddress); + } + try { + final Socket connsock = sf.connectSocket(sock, remoteAddress, localAddress, params); + if (sock != connsock) { + sock = connsock; + conn.opening(sock, target); + } + prepareSocket(sock, context, params); + conn.openCompleted(sf.isSecure(sock), params); + return; + } catch (final ConnectException ex) { + if (last) { + throw ex; + } + } catch (final ConnectTimeoutException ex) { + if (last) { + throw ex; + } + } + if (this.log.isDebugEnabled()) { + this.log.debug("Connect to " + remoteAddress + " timed out. " + + "Connection will be retried using another IP address"); + } + } + } + + public void updateSecureConnection( + final OperatedClientConnection conn, + final HttpHost target, + final HttpContext context, + final HttpParams params) throws IOException { + Args.notNull(conn, "Connection"); + Args.notNull(target, "Target host"); + Args.notNull(params, "Parameters"); + Asserts.check(conn.isOpen(), "Connection must be open"); + + final SchemeRegistry registry = getSchemeRegistry(context); + final Scheme schm = registry.getScheme(target.getSchemeName()); + Asserts.check(schm.getSchemeSocketFactory() instanceof SchemeLayeredSocketFactory, + "Socket factory must implement SchemeLayeredSocketFactory"); + final SchemeLayeredSocketFactory lsf = (SchemeLayeredSocketFactory) schm.getSchemeSocketFactory(); + final Socket sock = lsf.createLayeredSocket( + conn.getSocket(), target.getHostName(), schm.resolvePort(target.getPort()), params); + prepareSocket(sock, context, params); + conn.update(sock, target, lsf.isSecure(sock), params); + } + + /** + * Performs standard initializations on a newly created socket. + * + * @param sock the socket to prepare + * @param context the context for the connection + * @param params the parameters from which to prepare the socket + * + * @throws IOException in case of an IO problem + */ + protected void prepareSocket( + final Socket sock, + final HttpContext context, + final HttpParams params) throws IOException { + sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + final int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + sock.setSoLinger(linger > 0, linger); + } + } + + /** + * Resolves the given host name to an array of corresponding IP addresses, based on the + * configured name service on the provided DNS resolver. If one wasn't provided, the system + * configuration is used. + * + * @param host host name to resolve + * @return array of IP addresses + * @exception UnknownHostException if no IP address for the host could be determined. + * + * @see DnsResolver + * @see SystemDefaultDnsResolver + * + * @since 4.1 + */ + protected InetAddress[] resolveHostname(final String host) throws UnknownHostException { + return dnsResolver.resolve(host); + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParser.java new file mode 100644 index 000000000..9f249e5f7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParser.java @@ -0,0 +1,168 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.impl.DefaultHttpResponseFactory; +import ch.boye.httpclientandroidlib.impl.io.AbstractMessageParser; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Lenient HTTP response parser implementation that can skip malformed data until + * a valid HTTP response message head is encountered. + * + * @since 4.2 + */ +@SuppressWarnings("deprecation") +@NotThreadSafe +public class DefaultHttpResponseParser extends AbstractMessageParser<HttpResponse> { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + + /** + * @deprecated (4.3) use {@link DefaultHttpResponseParser#DefaultHttpResponseParser( + * SessionInputBuffer, LineParser, HttpResponseFactory, MessageConstraints)} + */ + @Deprecated + public DefaultHttpResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + Args.notNull(responseFactory, "Response factory"); + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * Creates new instance of DefaultHttpResponseParser. + * + * @param buffer the session input buffer. + * @param lineParser the line parser. If <code>null</code> + * {@link ch.boye.httpclientandroidlib.message.BasicLineParser#INSTANCE} will be used. + * @param responseFactory HTTP response factory. If <code>null</code> + * {@link DefaultHttpResponseFactory#INSTANCE} will be used. + * @param constraints the message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * + * @since 4.3 + */ + public DefaultHttpResponseParser( + final SessionInputBuffer buffer, + final LineParser lineParser, + final HttpResponseFactory responseFactory, + final MessageConstraints constraints) { + super(buffer, lineParser, constraints); + this.responseFactory = responseFactory != null ? responseFactory : + DefaultHttpResponseFactory.INSTANCE; + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * Creates new instance of DefaultHttpResponseParser. + * + * @param buffer the session input buffer. + * @param constraints the message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * + * @since 4.3 + */ + public DefaultHttpResponseParser( + final SessionInputBuffer buffer, final MessageConstraints constraints) { + this(buffer, null, null, constraints); + } + + /** + * Creates new instance of DefaultHttpResponseParser. + * + * @param buffer the session input buffer. + * + * @since 4.3 + */ + public DefaultHttpResponseParser(final SessionInputBuffer buffer) { + this(buffer, null, null, MessageConstraints.DEFAULT); + } + + @Override + protected HttpResponse parseHead( + final SessionInputBuffer sessionBuffer) throws IOException, HttpException { + //read out the HTTP status string + int count = 0; + ParserCursor cursor = null; + do { + // clear the buffer + this.lineBuf.clear(); + final int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1 && count == 0) { + // The server just dropped connection on us + throw new NoHttpResponseException("The target server failed to respond"); + } + cursor = new ParserCursor(0, this.lineBuf.length()); + if (lineParser.hasProtocolVersion(this.lineBuf, cursor)) { + // Got one + break; + } else if (i == -1 || reject(this.lineBuf, count)) { + // Giving up + throw new ProtocolException("The server failed to respond with a " + + "valid HTTP response"); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Garbage in response: " + this.lineBuf.toString()); + } + count++; + } while(true); + //create the status line from the status string + final StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + + protected boolean reject(final CharArrayBuffer line, final int count) { + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParserFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParserFactory.java new file mode 100644 index 000000000..a229b2ca6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParserFactory.java @@ -0,0 +1,77 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.impl.DefaultHttpResponseFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineParser; +import ch.boye.httpclientandroidlib.message.LineParser; + +/** + * Default factory for response message parsers. + * + * @since 4.3 + */ +@Immutable +public class DefaultHttpResponseParserFactory implements HttpMessageParserFactory<HttpResponse> { + + public static final DefaultHttpResponseParserFactory INSTANCE = new DefaultHttpResponseParserFactory(); + + private final LineParser lineParser; + private final HttpResponseFactory responseFactory; + + public DefaultHttpResponseParserFactory( + final LineParser lineParser, + final HttpResponseFactory responseFactory) { + super(); + this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE; + this.responseFactory = responseFactory != null ? responseFactory + : DefaultHttpResponseFactory.INSTANCE; + } + + public DefaultHttpResponseParserFactory( + final HttpResponseFactory responseFactory) { + this(null, responseFactory); + } + + public DefaultHttpResponseParserFactory() { + this(null, null); + } + + public HttpMessageParser<HttpResponse> create(final SessionInputBuffer buffer, + final MessageConstraints constraints) { + return new DefaultHttpResponseParser(buffer, lineParser, responseFactory, constraints); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpRoutePlanner.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpRoutePlanner.java new file mode 100644 index 000000000..4b3525329 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultHttpRoutePlanner.java @@ -0,0 +1,123 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + + +import java.net.InetAddress; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.params.ConnRouteParams; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Default implementation of an {@link HttpRoutePlanner}. This implementation + * is based on {@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames parameters}. + * It will not make use of any Java system properties, nor of system or + * browser proxy settings. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultRoutePlanner} + */ +@ThreadSafe +@Deprecated +public class DefaultHttpRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected final SchemeRegistry schemeRegistry; // class is @ThreadSafe + + /** + * Creates a new default route planner. + * + * @param schreg the scheme registry + */ + public DefaultHttpRoutePlanner(final SchemeRegistry schreg) { + Args.notNull(schreg, "Scheme registry"); + schemeRegistry = schreg; + } + + public HttpRoute determineRoute(final HttpHost target, + final HttpRequest request, + final HttpContext context) + throws HttpException { + + Args.notNull(request, "HTTP request"); + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) { + return route; + } + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + Asserts.notNull(target, "Target host"); + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = + ConnRouteParams.getDefaultProxy(request.getParams()); + + final Scheme schm; + try { + schm = this.schemeRegistry.getScheme(target.getSchemeName()); + } catch (final IllegalStateException ex) { + throw new HttpException(ex.getMessage()); + } + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultManagedHttpClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultManagedHttpClientConnection.java new file mode 100644 index 000000000..c46b93405 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultManagedHttpClientConnection.java @@ -0,0 +1,135 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.DefaultBHttpClientConnection; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * Default {@link ManagedHttpClientConnection} implementation. + * @since 4.3 + */ +@NotThreadSafe +public class DefaultManagedHttpClientConnection extends DefaultBHttpClientConnection + implements ManagedHttpClientConnection, HttpContext { + + private final String id; + private final Map<String, Object> attributes; + + private volatile boolean shutdown; + + public DefaultManagedHttpClientConnection( + final String id, + final int buffersize, + final int fragmentSizeHint, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageWriterFactory<HttpRequest> requestWriterFactory, + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + super(buffersize, fragmentSizeHint, chardecoder, charencoder, + constraints, incomingContentStrategy, outgoingContentStrategy, + requestWriterFactory, responseParserFactory); + this.id = id; + this.attributes = new ConcurrentHashMap<String, Object>(); + } + + public DefaultManagedHttpClientConnection( + final String id, + final int buffersize) { + this(id, buffersize, buffersize, null, null, null, null, null, null, null); + } + + public String getId() { + return this.id; + } + + @Override + public void shutdown() throws IOException { + this.shutdown = true; + super.shutdown(); + } + + public Object getAttribute(final String id) { + return this.attributes.get(id); + } + + public Object removeAttribute(final String id) { + return this.attributes.remove(id); + } + + public void setAttribute(final String id, final Object obj) { + this.attributes.put(id, obj); + } + + @Override + public void bind(final Socket socket) throws IOException { + if (this.shutdown) { + socket.close(); // allow this to throw... + // ...but if it doesn't, explicitly throw one ourselves. + throw new InterruptedIOException("Connection already shutdown"); + } + super.bind(socket); + } + + @Override + public Socket getSocket() { + return super.getSocket(); + } + + public SSLSession getSSLSession() { + final Socket socket = super.getSocket(); + if (socket instanceof SSLSocket) { + return ((SSLSocket) socket).getSession(); + } else { + return null; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultProxyRoutePlanner.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultProxyRoutePlanner.java new file mode 100644 index 000000000..fd569556c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultProxyRoutePlanner.java @@ -0,0 +1,66 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Implementation of an {@link ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner} + * that routes requests through a default proxy. + * + * @since 4.3 + */ +@Immutable +public class DefaultProxyRoutePlanner extends DefaultRoutePlanner { + + private final HttpHost proxy; + + public DefaultProxyRoutePlanner(final HttpHost proxy, final SchemePortResolver schemePortResolver) { + super(schemePortResolver); + this.proxy = Args.notNull(proxy, "Proxy host"); + } + + public DefaultProxyRoutePlanner(final HttpHost proxy) { + this(proxy, null); + } + + @Override + protected HttpHost determineProxy( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws HttpException { + return proxy; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultResponseParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultResponseParser.java new file mode 100644 index 000000000..e82452ff5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultResponseParser.java @@ -0,0 +1,125 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.impl.io.AbstractMessageParser; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Default HTTP response parser implementation. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnConnectionPNames#MAX_STATUS_LINE_GARBAGE}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.2) use {@link DefaultHttpResponseParser} + */ +@Deprecated +@ThreadSafe // no public methods +public class DefaultResponseParser extends AbstractMessageParser<HttpMessage> { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + private final int maxGarbageLines; + + public DefaultResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + Args.notNull(responseFactory, "Response factory"); + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + this.maxGarbageLines = getMaxGarbageLines(params); + } + + protected int getMaxGarbageLines(final HttpParams params) { + return params.getIntParameter( + ch.boye.httpclientandroidlib.conn.params.ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, + Integer.MAX_VALUE); + } + + @Override + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) throws IOException, HttpException { + //read out the HTTP status string + int count = 0; + ParserCursor cursor = null; + do { + // clear the buffer + this.lineBuf.clear(); + final int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1 && count == 0) { + // The server just dropped connection on us + throw new NoHttpResponseException("The target server failed to respond"); + } + cursor = new ParserCursor(0, this.lineBuf.length()); + if (lineParser.hasProtocolVersion(this.lineBuf, cursor)) { + // Got one + break; + } else if (i == -1 || count >= this.maxGarbageLines) { + // Giving up + throw new ProtocolException("The server failed to respond with a " + + "valid HTTP response"); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Garbage in response: " + this.lineBuf.toString()); + } + count++; + } while(true); + //create the status line from the status string + final StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultRoutePlanner.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultRoutePlanner.java new file mode 100644 index 000000000..2c08a7118 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultRoutePlanner.java @@ -0,0 +1,107 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.net.InetAddress; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.UnsupportedSchemeException; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of an {@link HttpRoutePlanner}. It will not make use of + * any Java system properties, nor of system or browser proxy settings. + * + * @since 4.3 + */ +@Immutable +public class DefaultRoutePlanner implements HttpRoutePlanner { + + private final SchemePortResolver schemePortResolver; + + public DefaultRoutePlanner(final SchemePortResolver schemePortResolver) { + super(); + this.schemePortResolver = schemePortResolver != null ? schemePortResolver : + DefaultSchemePortResolver.INSTANCE; + } + + public HttpRoute determineRoute( + final HttpHost host, + final HttpRequest request, + final HttpContext context) throws HttpException { + Args.notNull(request, "Request"); + if (host == null) { + throw new ProtocolException("Target host is not specified"); + } + final HttpClientContext clientContext = HttpClientContext.adapt(context); + final RequestConfig config = clientContext.getRequestConfig(); + final InetAddress local = config.getLocalAddress(); + HttpHost proxy = config.getProxy(); + if (proxy == null) { + proxy = determineProxy(host, request, context); + } + + final HttpHost target; + if (host.getPort() <= 0) { + try { + target = new HttpHost( + host.getHostName(), + this.schemePortResolver.resolve(host), + host.getSchemeName()); + } catch (final UnsupportedSchemeException ex) { + throw new HttpException(ex.getMessage()); + } + } else { + target = host; + } + final boolean secure = target.getSchemeName().equalsIgnoreCase("https"); + if (proxy == null) { + return new HttpRoute(target, local, secure); + } else { + return new HttpRoute(target, local, proxy, secure); + } + } + + protected HttpHost determineProxy( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws HttpException { + return null; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultSchemePortResolver.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultSchemePortResolver.java new file mode 100644 index 000000000..4b384f681 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/DefaultSchemePortResolver.java @@ -0,0 +1,61 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.UnsupportedSchemeException; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default {@link SchemePortResolver}. + * + * @since 4.3 + */ +@Immutable +public class DefaultSchemePortResolver implements SchemePortResolver { + + public static final DefaultSchemePortResolver INSTANCE = new DefaultSchemePortResolver(); + + public int resolve(final HttpHost host) throws UnsupportedSchemeException { + Args.notNull(host, "HTTP host"); + final int port = host.getPort(); + if (port > 0) { + return port; + } + final String name = host.getSchemeName(); + if (name.equalsIgnoreCase("http")) { + return 80; + } else if (name.equalsIgnoreCase("https")) { + return 443; + } else { + throw new UnsupportedSchemeException(name + " protocol is not supported"); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpClientConnectionOperator.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpClientConnectionOperator.java new file mode 100644 index 000000000..a13368237 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpClientConnectionOperator.java @@ -0,0 +1,173 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.conn.ConnectTimeoutException; +import ch.boye.httpclientandroidlib.conn.DnsResolver; +import ch.boye.httpclientandroidlib.conn.HttpHostConnectException; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.UnsupportedSchemeException; +import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; + +@Immutable +class HttpClientConnectionOperator { + + static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry"; + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final Lookup<ConnectionSocketFactory> socketFactoryRegistry; + private final SchemePortResolver schemePortResolver; + private final DnsResolver dnsResolver; + + HttpClientConnectionOperator( + final Lookup<ConnectionSocketFactory> socketFactoryRegistry, + final SchemePortResolver schemePortResolver, + final DnsResolver dnsResolver) { + super(); + Args.notNull(socketFactoryRegistry, "Socket factory registry"); + this.socketFactoryRegistry = socketFactoryRegistry; + this.schemePortResolver = schemePortResolver != null ? schemePortResolver : + DefaultSchemePortResolver.INSTANCE; + this.dnsResolver = dnsResolver != null ? dnsResolver : + SystemDefaultDnsResolver.INSTANCE; + } + + @SuppressWarnings("unchecked") + private Lookup<ConnectionSocketFactory> getSocketFactoryRegistry(final HttpContext context) { + Lookup<ConnectionSocketFactory> reg = (Lookup<ConnectionSocketFactory>) context.getAttribute( + SOCKET_FACTORY_REGISTRY); + if (reg == null) { + reg = this.socketFactoryRegistry; + } + return reg; + } + + public void connect( + final ManagedHttpClientConnection conn, + final HttpHost host, + final InetSocketAddress localAddress, + final int connectTimeout, + final SocketConfig socketConfig, + final HttpContext context) throws IOException { + final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context); + final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); + if (sf == null) { + throw new UnsupportedSchemeException(host.getSchemeName() + + " protocol is not supported"); + } + final InetAddress[] addresses = this.dnsResolver.resolve(host.getHostName()); + final int port = this.schemePortResolver.resolve(host); + for (int i = 0; i < addresses.length; i++) { + final InetAddress address = addresses[i]; + final boolean last = i == addresses.length - 1; + + Socket sock = sf.createSocket(context); + sock.setSoTimeout(socketConfig.getSoTimeout()); + sock.setReuseAddress(socketConfig.isSoReuseAddress()); + sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); + sock.setKeepAlive(socketConfig.isSoKeepAlive()); + final int linger = socketConfig.getSoLinger(); + if (linger >= 0) { + sock.setSoLinger(linger > 0, linger); + } + conn.bind(sock); + + final InetSocketAddress remoteAddress = new InetSocketAddress(address, port); + if (this.log.isDebugEnabled()) { + this.log.debug("Connecting to " + remoteAddress); + } + try { + sock = sf.connectSocket( + connectTimeout, sock, host, remoteAddress, localAddress, context); + conn.bind(sock); + if (this.log.isDebugEnabled()) { + this.log.debug("Connection established " + conn); + } + return; + } catch (final SocketTimeoutException ex) { + if (last) { + throw new ConnectTimeoutException(ex, host, addresses); + } + } catch (final ConnectException ex) { + if (last) { + final String msg = ex.getMessage(); + if ("Connection timed out".equals(msg)) { + throw new ConnectTimeoutException(ex, host, addresses); + } else { + throw new HttpHostConnectException(ex, host, addresses); + } + } + } + if (this.log.isDebugEnabled()) { + this.log.debug("Connect to " + remoteAddress + " timed out. " + + "Connection will be retried using another IP address"); + } + } + } + + public void upgrade( + final ManagedHttpClientConnection conn, + final HttpHost host, + final HttpContext context) throws IOException { + final HttpClientContext clientContext = HttpClientContext.adapt(context); + final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(clientContext); + final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); + if (sf == null) { + throw new UnsupportedSchemeException(host.getSchemeName() + + " protocol is not supported"); + } + if (!(sf instanceof LayeredConnectionSocketFactory)) { + throw new UnsupportedSchemeException(host.getSchemeName() + + " protocol does not support connection upgrade"); + } + final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf; + Socket sock = conn.getSocket(); + final int port = this.schemePortResolver.resolve(host); + sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context); + conn.bind(sock); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpConnPool.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpConnPool.java new file mode 100644 index 000000000..e10bd6cf6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpConnPool.java @@ -0,0 +1,84 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.pool.AbstractConnPool; +import ch.boye.httpclientandroidlib.pool.ConnFactory; + +/** + * @since 4.2 + * + * @deprecated (4.3) no longer used. + */ +@Deprecated +class HttpConnPool extends AbstractConnPool<HttpRoute, OperatedClientConnection, HttpPoolEntry> { + + private static final AtomicLong COUNTER = new AtomicLong(); + + public HttpClientAndroidLog log; + private final long timeToLive; + private final TimeUnit tunit; + + public HttpConnPool(final HttpClientAndroidLog log, + final ClientConnectionOperator connOperator, + final int defaultMaxPerRoute, final int maxTotal, + final long timeToLive, final TimeUnit tunit) { + super(new InternalConnFactory(connOperator), defaultMaxPerRoute, maxTotal); + this.log = log; + this.timeToLive = timeToLive; + this.tunit = tunit; + } + + @Override + protected HttpPoolEntry createEntry(final HttpRoute route, final OperatedClientConnection conn) { + final String id = Long.toString(COUNTER.getAndIncrement()); + return new HttpPoolEntry(this.log, id, route, conn, this.timeToLive, this.tunit); + } + + static class InternalConnFactory implements ConnFactory<HttpRoute, OperatedClientConnection> { + + private final ClientConnectionOperator connOperator; + + InternalConnFactory(final ClientConnectionOperator connOperator) { + this.connOperator = connOperator; + } + + public OperatedClientConnection create(final HttpRoute route) throws IOException { + return connOperator.createConnection(); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpPoolEntry.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpPoolEntry.java new file mode 100644 index 000000000..d04023126 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/HttpPoolEntry.java @@ -0,0 +1,98 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; +import ch.boye.httpclientandroidlib.pool.PoolEntry; + +/** + * @since 4.2 + * + * @deprecated (4.3) no longer used. + */ +@Deprecated +class HttpPoolEntry extends PoolEntry<HttpRoute, OperatedClientConnection> { + + public HttpClientAndroidLog log; + private final RouteTracker tracker; + + public HttpPoolEntry( + final HttpClientAndroidLog log, + final String id, + final HttpRoute route, + final OperatedClientConnection conn, + final long timeToLive, final TimeUnit tunit) { + super(id, route, conn, timeToLive, tunit); + this.log = log; + this.tracker = new RouteTracker(route); + } + + @Override + public boolean isExpired(final long now) { + final boolean expired = super.isExpired(now); + if (expired && this.log.isDebugEnabled()) { + this.log.debug("Connection " + this + " expired @ " + new Date(getExpiry())); + } + return expired; + } + + RouteTracker getTracker() { + return this.tracker; + } + + HttpRoute getPlannedRoute() { + return getRoute(); + } + + HttpRoute getEffectiveRoute() { + return this.tracker.toRoute(); + } + + @Override + public boolean isClosed() { + final OperatedClientConnection conn = getConnection(); + return !conn.isOpen(); + } + + @Override + public void close() { + final OperatedClientConnection conn = getConnection(); + try { + conn.close(); + } catch (final IOException ex) { + this.log.debug("I/O error closing connection", ex); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/IdleConnectionHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/IdleConnectionHandler.java new file mode 100644 index 000000000..cbcd5d04f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/IdleConnectionHandler.java @@ -0,0 +1,181 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpConnection; + +// Currently only used by AbstractConnPool +/** + * A helper class for connection managers to track idle connections. + * + * <p>This class is not synchronized.</p> + * + * @see ch.boye.httpclientandroidlib.conn.ClientConnectionManager#closeIdleConnections + * + * @since 4.0 + * + * @deprecated (4.1) no longer used + */ +@Deprecated +public class IdleConnectionHandler { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** Holds connections and the time they were added. */ + private final Map<HttpConnection,TimeValues> connectionToTimes; + + + public IdleConnectionHandler() { + super(); + connectionToTimes = new HashMap<HttpConnection,TimeValues>(); + } + + /** + * Registers the given connection with this handler. The connection will be held until + * {@link #remove} or {@link #closeIdleConnections} is called. + * + * @param connection the connection to add + * + * @see #remove + */ + public void add(final HttpConnection connection, final long validDuration, final TimeUnit unit) { + + final long timeAdded = System.currentTimeMillis(); + + if (log.isDebugEnabled()) { + log.debug("Adding connection at: " + timeAdded); + } + + connectionToTimes.put(connection, new TimeValues(timeAdded, validDuration, unit)); + } + + /** + * Removes the given connection from the list of connections to be closed when idle. + * This will return true if the connection is still valid, and false + * if the connection should be considered expired and not used. + * + * @param connection + * @return True if the connection is still valid. + */ + public boolean remove(final HttpConnection connection) { + final TimeValues times = connectionToTimes.remove(connection); + if(times == null) { + log.warn("Removing a connection that never existed!"); + return true; + } else { + return System.currentTimeMillis() <= times.timeExpires; + } + } + + /** + * Removes all connections referenced by this handler. + */ + public void removeAll() { + this.connectionToTimes.clear(); + } + + /** + * Closes connections that have been idle for at least the given amount of time. + * + * @param idleTime the minimum idle time, in milliseconds, for connections to be closed + */ + public void closeIdleConnections(final long idleTime) { + + // the latest time for which connections will be closed + final long idleTimeout = System.currentTimeMillis() - idleTime; + + if (log.isDebugEnabled()) { + log.debug("Checking for connections, idle timeout: " + idleTimeout); + } + + for (final Entry<HttpConnection, TimeValues> entry : connectionToTimes.entrySet()) { + final HttpConnection conn = entry.getKey(); + final TimeValues times = entry.getValue(); + final long connectionTime = times.timeAdded; + if (connectionTime <= idleTimeout) { + if (log.isDebugEnabled()) { + log.debug("Closing idle connection, connection time: " + connectionTime); + } + try { + conn.close(); + } catch (final IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + + public void closeExpiredConnections() { + final long now = System.currentTimeMillis(); + if (log.isDebugEnabled()) { + log.debug("Checking for expired connections, now: " + now); + } + + for (final Entry<HttpConnection, TimeValues> entry : connectionToTimes.entrySet()) { + final HttpConnection conn = entry.getKey(); + final TimeValues times = entry.getValue(); + if(times.timeExpires <= now) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, expired @: " + times.timeExpires); + } + try { + conn.close(); + } catch (final IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + private static class TimeValues { + private final long timeAdded; + private final long timeExpires; + + /** + * @param now The current time in milliseconds + * @param validDuration The duration this connection is valid for + * @param validUnit The unit of time the duration is specified in. + */ + TimeValues(final long now, final long validDuration, final TimeUnit validUnit) { + this.timeAdded = now; + if(validDuration > 0) { + this.timeExpires = now + validUnit.toMillis(validDuration); + } else { + this.timeExpires = Long.MAX_VALUE; + } + } + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/InMemoryDnsResolver.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/InMemoryDnsResolver.java new file mode 100644 index 000000000..34f02b11b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/InMemoryDnsResolver.java @@ -0,0 +1,94 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.conn.DnsResolver; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * In-memory {@link DnsResolver} implementation. + * + * @since 4.2 + */ +public class InMemoryDnsResolver implements DnsResolver { + + /** Logger associated to this class. */ + public HttpClientAndroidLog log = new HttpClientAndroidLog(InMemoryDnsResolver.class); + + /** + * In-memory collection that will hold the associations between a host name + * and an array of InetAddress instances. + */ + private final Map<String, InetAddress[]> dnsMap; + + /** + * Builds a DNS resolver that will resolve the host names against a + * collection held in-memory. + */ + public InMemoryDnsResolver() { + dnsMap = new ConcurrentHashMap<String, InetAddress[]>(); + } + + /** + * Associates the given array of IP addresses to the given host in this DNS overrider. + * The IP addresses are assumed to be already resolved. + * + * @param host + * The host name to be associated with the given IP. + * @param ips + * array of IP addresses to be resolved by this DNS overrider to the given + * host name. + */ + public void add(final String host, final InetAddress... ips) { + Args.notNull(host, "Host name"); + Args.notNull(ips, "Array of IP addresses"); + dnsMap.put(host, ips); + } + + /** + * {@inheritDoc} + */ + public InetAddress[] resolve(final String host) throws UnknownHostException { + final InetAddress[] resolvedAddresses = dnsMap.get(host); + if (log.isInfoEnabled()) { + log.info("Resolving " + host + " to " + Arrays.deepToString(resolvedAddresses)); + } + if(resolvedAddresses == null){ + throw new UnknownHostException(host + " cannot be resolved"); + } + return resolvedAddresses; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingInputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingInputStream.java new file mode 100644 index 000000000..0c117395c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingInputStream.java @@ -0,0 +1,145 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Internal class. + * + * @since 4.3 + */ +@NotThreadSafe +class LoggingInputStream extends InputStream { + + private final InputStream in; + private final Wire wire; + + public LoggingInputStream(final InputStream in, final Wire wire) { + super(); + this.in = in; + this.wire = wire; + } + + @Override + public int read() throws IOException { + try { + final int b = in.read(); + if (b == -1) { + wire.input("end of stream"); + } else { + wire.input(b); + } + return b; + } catch (IOException ex) { + wire.input("[read] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public int read(final byte[] b) throws IOException { + try { + final int bytesRead = in.read(b); + if (bytesRead == -1) { + wire.input("end of stream"); + } else if (bytesRead > 0) { + wire.input(b, 0, bytesRead); + } + return bytesRead; + } catch (IOException ex) { + wire.input("[read] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + try { + final int bytesRead = in.read(b, off, len); + if (bytesRead == -1) { + wire.input("end of stream"); + } else if (bytesRead > 0) { + wire.input(b, off, bytesRead); + } + return bytesRead; + } catch (IOException ex) { + wire.input("[read] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public long skip(final long n) throws IOException { + try { + return super.skip(n); + } catch (IOException ex) { + wire.input("[skip] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public int available() throws IOException { + try { + return in.available(); + } catch (IOException ex) { + wire.input("[available] I/O error : " + ex.getMessage()); + throw ex; + } + } + + @Override + public void mark(final int readlimit) { + super.mark(readlimit); + } + + @Override + public void reset() throws IOException { + super.reset(); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public void close() throws IOException { + try { + in.close(); + } catch (IOException ex) { + wire.input("[close] I/O error: " + ex.getMessage()); + throw ex; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingManagedHttpClientConnection.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingManagedHttpClientConnection.java new file mode 100644 index 000000000..5b06179c0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingManagedHttpClientConnection.java @@ -0,0 +1,132 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; + +@NotThreadSafe +class LoggingManagedHttpClientConnection extends DefaultManagedHttpClientConnection { + + public HttpClientAndroidLog log; + private final HttpClientAndroidLog headerlog; + private final Wire wire; + + public LoggingManagedHttpClientConnection( + final String id, + final HttpClientAndroidLog log, + final HttpClientAndroidLog headerlog, + final HttpClientAndroidLog wirelog, + final int buffersize, + final int fragmentSizeHint, + final CharsetDecoder chardecoder, + final CharsetEncoder charencoder, + final MessageConstraints constraints, + final ContentLengthStrategy incomingContentStrategy, + final ContentLengthStrategy outgoingContentStrategy, + final HttpMessageWriterFactory<HttpRequest> requestWriterFactory, + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + super(id, buffersize, fragmentSizeHint, chardecoder, charencoder, + constraints, incomingContentStrategy, outgoingContentStrategy, + requestWriterFactory, responseParserFactory); + this.log = log; + this.headerlog = headerlog; + this.wire = new Wire(wirelog, id); + } + + @Override + public void close() throws IOException { + if (this.log.isDebugEnabled()) { + this.log.debug(getId() + ": Close connection"); + } + super.close(); + } + + @Override + public void shutdown() throws IOException { + if (this.log.isDebugEnabled()) { + this.log.debug(getId() + ": Shutdown connection"); + } + super.shutdown(); + } + + @Override + protected InputStream getSocketInputStream(final Socket socket) throws IOException { + InputStream in = super.getSocketInputStream(socket); + if (this.wire.enabled()) { + in = new LoggingInputStream(in, this.wire); + } + return in; + } + + @Override + protected OutputStream getSocketOutputStream(final Socket socket) throws IOException { + OutputStream out = super.getSocketOutputStream(socket); + if (this.wire.enabled()) { + out = new LoggingOutputStream(out, this.wire); + } + return out; + } + + @Override + protected void onResponseReceived(final HttpResponse response) { + if (response != null && this.headerlog.isDebugEnabled()) { + this.headerlog.debug(getId() + " << " + response.getStatusLine().toString()); + final Header[] headers = response.getAllHeaders(); + for (final Header header : headers) { + this.headerlog.debug(getId() + " << " + header.toString()); + } + } + } + + @Override + protected void onRequestSubmitted(final HttpRequest request) { + if (request != null && this.headerlog.isDebugEnabled()) { + this.headerlog.debug(getId() + " >> " + request.getRequestLine().toString()); + final Header[] headers = request.getAllHeaders(); + for (final Header header : headers) { + this.headerlog.debug(getId() + " >> " + header.toString()); + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingOutputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingOutputStream.java new file mode 100644 index 000000000..86c47b502 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingOutputStream.java @@ -0,0 +1,104 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Internal class. + * + * @since 4.3 + */ +@NotThreadSafe +class LoggingOutputStream extends OutputStream { + + private final OutputStream out; + private final Wire wire; + + public LoggingOutputStream(final OutputStream out, final Wire wire) { + super(); + this.out = out; + this.wire = wire; + } + + @Override + public void write(final int b) throws IOException { + try { + wire.output(b); + } catch (IOException ex) { + wire.output("[write] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public void write(final byte[] b) throws IOException { + try { + wire.output(b); + out.write(b); + } catch (IOException ex) { + wire.output("[write] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + try { + wire.output(b, off, len); + out.write(b, off, len); + } catch (IOException ex) { + wire.output("[write] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public void flush() throws IOException { + try { + out.flush(); + } catch (IOException ex) { + wire.output("[flush] I/O error: " + ex.getMessage()); + throw ex; + } + } + + @Override + public void close() throws IOException { + try { + out.close(); + } catch (IOException ex) { + wire.output("[close] I/O error: " + ex.getMessage()); + throw ex; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingSessionInputBuffer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingSessionInputBuffer.java new file mode 100644 index 000000000..95617206e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingSessionInputBuffer.java @@ -0,0 +1,138 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.io.EofSensor; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Logs all data read to the wire LOG. + * + * @since 4.0 + * + * @deprecated (4.3) no longer used. + */ +@Immutable +@Deprecated +public class LoggingSessionInputBuffer implements SessionInputBuffer, EofSensor { + + /** Original session input buffer. */ + private final SessionInputBuffer in; + + private final EofSensor eofSensor; + + /** The wire log to use for writing. */ + private final Wire wire; + + private final String charset; + + /** + * Create an instance that wraps the specified session input buffer. + * @param in The session input buffer. + * @param wire The wire log to use. + * @param charset protocol charset, <code>ASCII</code> if <code>null</code> + */ + public LoggingSessionInputBuffer( + final SessionInputBuffer in, final Wire wire, final String charset) { + super(); + this.in = in; + this.eofSensor = in instanceof EofSensor ? (EofSensor) in : null; + this.wire = wire; + this.charset = charset != null ? charset : Consts.ASCII.name(); + } + + public LoggingSessionInputBuffer(final SessionInputBuffer in, final Wire wire) { + this(in, wire, null); + } + + public boolean isDataAvailable(final int timeout) throws IOException { + return this.in.isDataAvailable(timeout); + } + + public int read(final byte[] b, final int off, final int len) throws IOException { + final int l = this.in.read(b, off, len); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, off, l); + } + return l; + } + + public int read() throws IOException { + final int l = this.in.read(); + if (this.wire.enabled() && l != -1) { + this.wire.input(l); + } + return l; + } + + public int read(final byte[] b) throws IOException { + final int l = this.in.read(b); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, 0, l); + } + return l; + } + + public String readLine() throws IOException { + final String s = this.in.readLine(); + if (this.wire.enabled() && s != null) { + final String tmp = s + "\r\n"; + this.wire.input(tmp.getBytes(this.charset)); + } + return s; + } + + public int readLine(final CharArrayBuffer buffer) throws IOException { + final int l = this.in.readLine(buffer); + if (this.wire.enabled() && l >= 0) { + final int pos = buffer.length() - l; + final String s = new String(buffer.buffer(), pos, l); + final String tmp = s + "\r\n"; + this.wire.input(tmp.getBytes(this.charset)); + } + return l; + } + + public HttpTransportMetrics getMetrics() { + return this.in.getMetrics(); + } + + public boolean isEof() { + if (this.eofSensor != null) { + return this.eofSensor.isEof(); + } else { + return false; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingSessionOutputBuffer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingSessionOutputBuffer.java new file mode 100644 index 000000000..bf1100b4e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/LoggingSessionOutputBuffer.java @@ -0,0 +1,118 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Logs all data written to the wire LOG. + * @since 4.0 + * @deprecated (4.3) no longer used. + */ +@Immutable +@Deprecated +public class LoggingSessionOutputBuffer implements SessionOutputBuffer { + + /** Original data transmitter. */ + private final SessionOutputBuffer out; + + /** The wire log to use. */ + private final Wire wire; + + private final String charset; + + /** + * Create an instance that wraps the specified session output buffer. + * @param out The session output buffer. + * @param wire The Wire log to use. + * @param charset protocol charset, <code>ASCII</code> if <code>null</code> + */ + public LoggingSessionOutputBuffer( + final SessionOutputBuffer out, final Wire wire, final String charset) { + super(); + this.out = out; + this.wire = wire; + this.charset = charset != null ? charset : Consts.ASCII.name(); + } + + public LoggingSessionOutputBuffer(final SessionOutputBuffer out, final Wire wire) { + this(out, wire, null); + } + + public void write(final byte[] b, final int off, final int len) throws IOException { + this.out.write(b, off, len); + if (this.wire.enabled()) { + this.wire.output(b, off, len); + } + } + + public void write(final int b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void write(final byte[] b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void writeLine(final CharArrayBuffer buffer) throws IOException { + this.out.writeLine(buffer); + if (this.wire.enabled()) { + final String s = new String(buffer.buffer(), 0, buffer.length()); + final String tmp = s + "\r\n"; + this.wire.output(tmp.getBytes(this.charset)); + } + } + + public void writeLine(final String s) throws IOException { + this.out.writeLine(s); + if (this.wire.enabled()) { + final String tmp = s + "\r\n"; + this.wire.output(tmp.getBytes(this.charset)); + } + } + + public HttpTransportMetrics getMetrics() { + return this.out.getMetrics(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ManagedClientConnectionImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ManagedClientConnectionImpl.java new file mode 100644 index 000000000..d1db2d8eb --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ManagedClientConnectionImpl.java @@ -0,0 +1,461 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import ch.boye.httpclientandroidlib.HttpConnectionMetrics; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * @since 4.2 + * + * @deprecated (4.3) use {@link ManagedHttpClientConnectionFactory}. + */ +@Deprecated +@NotThreadSafe +class ManagedClientConnectionImpl implements ManagedClientConnection { + + private final ClientConnectionManager manager; + private final ClientConnectionOperator operator; + private volatile HttpPoolEntry poolEntry; + private volatile boolean reusable; + private volatile long duration; + + ManagedClientConnectionImpl( + final ClientConnectionManager manager, + final ClientConnectionOperator operator, + final HttpPoolEntry entry) { + super(); + Args.notNull(manager, "Connection manager"); + Args.notNull(operator, "Connection operator"); + Args.notNull(entry, "HTTP pool entry"); + this.manager = manager; + this.operator = operator; + this.poolEntry = entry; + this.reusable = false; + this.duration = Long.MAX_VALUE; + } + + public String getId() { + return null; + } + + HttpPoolEntry getPoolEntry() { + return this.poolEntry; + } + + HttpPoolEntry detach() { + final HttpPoolEntry local = this.poolEntry; + this.poolEntry = null; + return local; + } + + public ClientConnectionManager getManager() { + return this.manager; + } + + private OperatedClientConnection getConnection() { + final HttpPoolEntry local = this.poolEntry; + if (local == null) { + return null; + } + return local.getConnection(); + } + + private OperatedClientConnection ensureConnection() { + final HttpPoolEntry local = this.poolEntry; + if (local == null) { + throw new ConnectionShutdownException(); + } + return local.getConnection(); + } + + private HttpPoolEntry ensurePoolEntry() { + final HttpPoolEntry local = this.poolEntry; + if (local == null) { + throw new ConnectionShutdownException(); + } + return local; + } + + public void close() throws IOException { + final HttpPoolEntry local = this.poolEntry; + if (local != null) { + final OperatedClientConnection conn = local.getConnection(); + local.getTracker().reset(); + conn.close(); + } + } + + public void shutdown() throws IOException { + final HttpPoolEntry local = this.poolEntry; + if (local != null) { + final OperatedClientConnection conn = local.getConnection(); + local.getTracker().reset(); + conn.shutdown(); + } + } + + public boolean isOpen() { + final OperatedClientConnection conn = getConnection(); + if (conn != null) { + return conn.isOpen(); + } else { + return false; + } + } + + public boolean isStale() { + final OperatedClientConnection conn = getConnection(); + if (conn != null) { + return conn.isStale(); + } else { + return true; + } + } + + public void setSocketTimeout(final int timeout) { + final OperatedClientConnection conn = ensureConnection(); + conn.setSocketTimeout(timeout); + } + + public int getSocketTimeout() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getSocketTimeout(); + } + + public HttpConnectionMetrics getMetrics() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getMetrics(); + } + + public void flush() throws IOException { + final OperatedClientConnection conn = ensureConnection(); + conn.flush(); + } + + public boolean isResponseAvailable(final int timeout) throws IOException { + final OperatedClientConnection conn = ensureConnection(); + return conn.isResponseAvailable(timeout); + } + + public void receiveResponseEntity( + final HttpResponse response) throws HttpException, IOException { + final OperatedClientConnection conn = ensureConnection(); + conn.receiveResponseEntity(response); + } + + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + final OperatedClientConnection conn = ensureConnection(); + return conn.receiveResponseHeader(); + } + + public void sendRequestEntity( + final HttpEntityEnclosingRequest request) throws HttpException, IOException { + final OperatedClientConnection conn = ensureConnection(); + conn.sendRequestEntity(request); + } + + public void sendRequestHeader( + final HttpRequest request) throws HttpException, IOException { + final OperatedClientConnection conn = ensureConnection(); + conn.sendRequestHeader(request); + } + + public InetAddress getLocalAddress() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getLocalAddress(); + } + + public int getLocalPort() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getLocalPort(); + } + + public InetAddress getRemoteAddress() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getRemoteAddress(); + } + + public int getRemotePort() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getRemotePort(); + } + + public boolean isSecure() { + final OperatedClientConnection conn = ensureConnection(); + return conn.isSecure(); + } + + public void bind(final Socket socket) throws IOException { + throw new UnsupportedOperationException(); + } + + public Socket getSocket() { + final OperatedClientConnection conn = ensureConnection(); + return conn.getSocket(); + } + + public SSLSession getSSLSession() { + final OperatedClientConnection conn = ensureConnection(); + SSLSession result = null; + final Socket sock = conn.getSocket(); + if (sock instanceof SSLSocket) { + result = ((SSLSocket)sock).getSession(); + } + return result; + } + + public Object getAttribute(final String id) { + final OperatedClientConnection conn = ensureConnection(); + if (conn instanceof HttpContext) { + return ((HttpContext) conn).getAttribute(id); + } else { + return null; + } + } + + public Object removeAttribute(final String id) { + final OperatedClientConnection conn = ensureConnection(); + if (conn instanceof HttpContext) { + return ((HttpContext) conn).removeAttribute(id); + } else { + return null; + } + } + + public void setAttribute(final String id, final Object obj) { + final OperatedClientConnection conn = ensureConnection(); + if (conn instanceof HttpContext) { + ((HttpContext) conn).setAttribute(id, obj); + } + } + + public HttpRoute getRoute() { + final HttpPoolEntry local = ensurePoolEntry(); + return local.getEffectiveRoute(); + } + + public void open( + final HttpRoute route, + final HttpContext context, + final HttpParams params) throws IOException { + Args.notNull(route, "Route"); + Args.notNull(params, "HTTP parameters"); + final OperatedClientConnection conn; + synchronized (this) { + if (this.poolEntry == null) { + throw new ConnectionShutdownException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + Asserts.notNull(tracker, "Route tracker"); + Asserts.check(!tracker.isConnected(), "Connection already open"); + conn = this.poolEntry.getConnection(); + } + + final HttpHost proxy = route.getProxyHost(); + this.operator.openConnection( + conn, + (proxy != null) ? proxy : route.getTargetHost(), + route.getLocalAddress(), + context, params); + + synchronized (this) { + if (this.poolEntry == null) { + throw new InterruptedIOException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + if (proxy == null) { + tracker.connectTarget(conn.isSecure()); + } else { + tracker.connectProxy(proxy, conn.isSecure()); + } + } + } + + public void tunnelTarget( + final boolean secure, final HttpParams params) throws IOException { + Args.notNull(params, "HTTP parameters"); + final HttpHost target; + final OperatedClientConnection conn; + synchronized (this) { + if (this.poolEntry == null) { + throw new ConnectionShutdownException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + Asserts.notNull(tracker, "Route tracker"); + Asserts.check(tracker.isConnected(), "Connection not open"); + Asserts.check(!tracker.isTunnelled(), "Connection is already tunnelled"); + target = tracker.getTargetHost(); + conn = this.poolEntry.getConnection(); + } + + conn.update(null, target, secure, params); + + synchronized (this) { + if (this.poolEntry == null) { + throw new InterruptedIOException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + tracker.tunnelTarget(secure); + } + } + + public void tunnelProxy( + final HttpHost next, final boolean secure, final HttpParams params) throws IOException { + Args.notNull(next, "Next proxy"); + Args.notNull(params, "HTTP parameters"); + final OperatedClientConnection conn; + synchronized (this) { + if (this.poolEntry == null) { + throw new ConnectionShutdownException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + Asserts.notNull(tracker, "Route tracker"); + Asserts.check(tracker.isConnected(), "Connection not open"); + conn = this.poolEntry.getConnection(); + } + + conn.update(null, next, secure, params); + + synchronized (this) { + if (this.poolEntry == null) { + throw new InterruptedIOException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + tracker.tunnelProxy(next, secure); + } + } + + public void layerProtocol( + final HttpContext context, final HttpParams params) throws IOException { + Args.notNull(params, "HTTP parameters"); + final HttpHost target; + final OperatedClientConnection conn; + synchronized (this) { + if (this.poolEntry == null) { + throw new ConnectionShutdownException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + Asserts.notNull(tracker, "Route tracker"); + Asserts.check(tracker.isConnected(), "Connection not open"); + Asserts.check(tracker.isTunnelled(), "Protocol layering without a tunnel not supported"); + Asserts.check(!tracker.isLayered(), "Multiple protocol layering not supported"); + target = tracker.getTargetHost(); + conn = this.poolEntry.getConnection(); + } + this.operator.updateSecureConnection(conn, target, context, params); + + synchronized (this) { + if (this.poolEntry == null) { + throw new InterruptedIOException(); + } + final RouteTracker tracker = this.poolEntry.getTracker(); + tracker.layerProtocol(conn.isSecure()); + } + } + + public Object getState() { + final HttpPoolEntry local = ensurePoolEntry(); + return local.getState(); + } + + public void setState(final Object state) { + final HttpPoolEntry local = ensurePoolEntry(); + local.setState(state); + } + + public void markReusable() { + this.reusable = true; + } + + public void unmarkReusable() { + this.reusable = false; + } + + public boolean isMarkedReusable() { + return this.reusable; + } + + public void setIdleDuration(final long duration, final TimeUnit unit) { + if(duration > 0) { + this.duration = unit.toMillis(duration); + } else { + this.duration = -1; + } + } + + public void releaseConnection() { + synchronized (this) { + if (this.poolEntry == null) { + return; + } + this.manager.releaseConnection(this, this.duration, TimeUnit.MILLISECONDS); + this.poolEntry = null; + } + } + + public void abortConnection() { + synchronized (this) { + if (this.poolEntry == null) { + return; + } + this.reusable = false; + final OperatedClientConnection conn = this.poolEntry.getConnection(); + try { + conn.shutdown(); + } catch (final IOException ignore) { + } + this.manager.releaseConnection(this, this.duration, TimeUnit.MILLISECONDS); + this.poolEntry = null; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ManagedHttpClientConnectionFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ManagedHttpClientConnectionFactory.java new file mode 100644 index 000000000..51b3844f9 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ManagedHttpClientConnectionFactory.java @@ -0,0 +1,121 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.conn.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.io.DefaultHttpRequestWriterFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; + +/** + * Factory for {@link ManagedHttpClientConnection} instances. + * @since 4.3 + */ +@Immutable +public class ManagedHttpClientConnectionFactory + implements HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> { + + private static final AtomicLong COUNTER = new AtomicLong(); + + public static final ManagedHttpClientConnectionFactory INSTANCE = new ManagedHttpClientConnectionFactory(); + + public HttpClientAndroidLog log = new HttpClientAndroidLog(DefaultManagedHttpClientConnection.class); + public HttpClientAndroidLog headerlog = new HttpClientAndroidLog("ch.boye.httpclientandroidlib.headers"); + public HttpClientAndroidLog wirelog = new HttpClientAndroidLog("ch.boye.httpclientandroidlib.wire"); + + private final HttpMessageWriterFactory<HttpRequest> requestWriterFactory; + private final HttpMessageParserFactory<HttpResponse> responseParserFactory; + + public ManagedHttpClientConnectionFactory( + final HttpMessageWriterFactory<HttpRequest> requestWriterFactory, + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + super(); + this.requestWriterFactory = requestWriterFactory != null ? requestWriterFactory : + DefaultHttpRequestWriterFactory.INSTANCE; + this.responseParserFactory = responseParserFactory != null ? responseParserFactory : + DefaultHttpResponseParserFactory.INSTANCE; + } + + public ManagedHttpClientConnectionFactory( + final HttpMessageParserFactory<HttpResponse> responseParserFactory) { + this(null, responseParserFactory); + } + + public ManagedHttpClientConnectionFactory() { + this(null, null); + } + + public ManagedHttpClientConnection create(final HttpRoute route, final ConnectionConfig config) { + final ConnectionConfig cconfig = config != null ? config : ConnectionConfig.DEFAULT; + CharsetDecoder chardecoder = null; + CharsetEncoder charencoder = null; + final Charset charset = cconfig.getCharset(); + final CodingErrorAction malformedInputAction = cconfig.getMalformedInputAction() != null ? + cconfig.getMalformedInputAction() : CodingErrorAction.REPORT; + final CodingErrorAction unmappableInputAction = cconfig.getUnmappableInputAction() != null ? + cconfig.getUnmappableInputAction() : CodingErrorAction.REPORT; + if (charset != null) { + chardecoder = charset.newDecoder(); + chardecoder.onMalformedInput(malformedInputAction); + chardecoder.onUnmappableCharacter(unmappableInputAction); + charencoder = charset.newEncoder(); + charencoder.onMalformedInput(malformedInputAction); + charencoder.onUnmappableCharacter(unmappableInputAction); + } + final String id = "http-outgoing-" + Long.toString(COUNTER.getAndIncrement()); + return new LoggingManagedHttpClientConnection( + id, + log, + headerlog, + wirelog, + cconfig.getBufferSize(), + cconfig.getFragmentSizeHint(), + chardecoder, + charencoder, + cconfig.getMessageConstraints(), + null, + null, + requestWriterFactory, + responseParserFactory); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/PoolingClientConnectionManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/PoolingClientConnectionManager.java new file mode 100644 index 000000000..f9685b81e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/PoolingClientConnectionManager.java @@ -0,0 +1,328 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; +import ch.boye.httpclientandroidlib.conn.DnsResolver; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.pool.ConnPoolControl; +import ch.boye.httpclientandroidlib.pool.PoolStats; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Manages a pool of {@link ch.boye.httpclientandroidlib.conn.OperatedClientConnection} + * and is able to service connection requests from multiple execution threads. + * Connections are pooled on a per route basis. A request for a route which + * already the manager has persistent connections for available in the pool + * will be services by leasing a connection from the pool rather than + * creating a brand new connection. + * <p> + * PoolingConnectionManager maintains a maximum limit of connection on + * a per route basis and in total. Per default this implementation will + * create no more than than 2 concurrent connections per given route + * and no more 20 connections in total. For many real-world applications + * these limits may prove too constraining, especially if they use HTTP + * as a transport protocol for their services. Connection limits, however, + * can be adjusted using HTTP parameters. + * + * @since 4.2 + * + * @deprecated (4.3) use {@link PoolingHttpClientConnectionManager}. + */ +@Deprecated +@ThreadSafe +public class PoolingClientConnectionManager implements ClientConnectionManager, ConnPoolControl<HttpRoute> { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final SchemeRegistry schemeRegistry; + + private final HttpConnPool pool; + + private final ClientConnectionOperator operator; + + /** the custom-configured DNS lookup mechanism. */ + private final DnsResolver dnsResolver; + + public PoolingClientConnectionManager(final SchemeRegistry schreg) { + this(schreg, -1, TimeUnit.MILLISECONDS); + } + + public PoolingClientConnectionManager(final SchemeRegistry schreg,final DnsResolver dnsResolver) { + this(schreg, -1, TimeUnit.MILLISECONDS,dnsResolver); + } + + public PoolingClientConnectionManager() { + this(SchemeRegistryFactory.createDefault()); + } + + public PoolingClientConnectionManager( + final SchemeRegistry schemeRegistry, + final long timeToLive, final TimeUnit tunit) { + this(schemeRegistry, timeToLive, tunit, new SystemDefaultDnsResolver()); + } + + public PoolingClientConnectionManager(final SchemeRegistry schemeRegistry, + final long timeToLive, final TimeUnit tunit, + final DnsResolver dnsResolver) { + super(); + Args.notNull(schemeRegistry, "Scheme registry"); + Args.notNull(dnsResolver, "DNS resolver"); + this.schemeRegistry = schemeRegistry; + this.dnsResolver = dnsResolver; + this.operator = createConnectionOperator(schemeRegistry); + this.pool = new HttpConnPool(this.log, this.operator, 2, 20, timeToLive, tunit); + } + + @Override + protected void finalize() throws Throwable { + try { + shutdown(); + } finally { + super.finalize(); + } + } + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry. + * + * @return the connection operator to use + */ + protected ClientConnectionOperator createConnectionOperator(final SchemeRegistry schreg) { + return new DefaultClientConnectionOperator(schreg, this.dnsResolver); + } + + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + private String format(final HttpRoute route, final Object state) { + final StringBuilder buf = new StringBuilder(); + buf.append("[route: ").append(route).append("]"); + if (state != null) { + buf.append("[state: ").append(state).append("]"); + } + return buf.toString(); + } + + private String formatStats(final HttpRoute route) { + final StringBuilder buf = new StringBuilder(); + final PoolStats totals = this.pool.getTotalStats(); + final PoolStats stats = this.pool.getStats(route); + buf.append("[total kept alive: ").append(totals.getAvailable()).append("; "); + buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable()); + buf.append(" of ").append(stats.getMax()).append("; "); + buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable()); + buf.append(" of ").append(totals.getMax()).append("]"); + return buf.toString(); + } + + private String format(final HttpPoolEntry entry) { + final StringBuilder buf = new StringBuilder(); + buf.append("[id: ").append(entry.getId()).append("]"); + buf.append("[route: ").append(entry.getRoute()).append("]"); + final Object state = entry.getState(); + if (state != null) { + buf.append("[state: ").append(state).append("]"); + } + return buf.toString(); + } + + public ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + Args.notNull(route, "HTTP route"); + if (this.log.isDebugEnabled()) { + this.log.debug("Connection request: " + format(route, state) + formatStats(route)); + } + final Future<HttpPoolEntry> future = this.pool.lease(route, state); + + return new ClientConnectionRequest() { + + public void abortRequest() { + future.cancel(true); + } + + public ManagedClientConnection getConnection( + final long timeout, + final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException { + return leaseConnection(future, timeout, tunit); + } + + }; + + } + + ManagedClientConnection leaseConnection( + final Future<HttpPoolEntry> future, + final long timeout, + final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException { + final HttpPoolEntry entry; + try { + entry = future.get(timeout, tunit); + if (entry == null || future.isCancelled()) { + throw new InterruptedException(); + } + Asserts.check(entry.getConnection() != null, "Pool entry with no connection"); + if (this.log.isDebugEnabled()) { + this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute())); + } + return new ManagedClientConnectionImpl(this, this.operator, entry); + } catch (final ExecutionException ex) { + Throwable cause = ex.getCause(); + if (cause == null) { + cause = ex; + } + this.log.error("Unexpected exception leasing connection from pool", cause); + // Should never happen + throw new InterruptedException(); + } catch (final TimeoutException ex) { + throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool"); + } + } + + public void releaseConnection( + final ManagedClientConnection conn, final long keepalive, final TimeUnit tunit) { + + Args.check(conn instanceof ManagedClientConnectionImpl, "Connection class mismatch, " + + "connection not obtained from this manager"); + final ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn; + Asserts.check(managedConn.getManager() == this, "Connection not obtained from this manager"); + synchronized (managedConn) { + final HttpPoolEntry entry = managedConn.detach(); + if (entry == null) { + return; + } + try { + if (managedConn.isOpen() && !managedConn.isMarkedReusable()) { + try { + managedConn.shutdown(); + } catch (final IOException iox) { + if (this.log.isDebugEnabled()) { + this.log.debug("I/O exception shutting down released connection", iox); + } + } + } + // Only reusable connections can be kept alive + if (managedConn.isMarkedReusable()) { + entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS); + if (this.log.isDebugEnabled()) { + final String s; + if (keepalive > 0) { + s = "for " + keepalive + " " + tunit; + } else { + s = "indefinitely"; + } + this.log.debug("Connection " + format(entry) + " can be kept alive " + s); + } + } + } finally { + this.pool.release(entry, managedConn.isMarkedReusable()); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute())); + } + } + } + + public void shutdown() { + this.log.debug("Connection manager is shutting down"); + try { + this.pool.shutdown(); + } catch (final IOException ex) { + this.log.debug("I/O exception shutting down connection manager", ex); + } + this.log.debug("Connection manager shut down"); + } + + public void closeIdleConnections(final long idleTimeout, final TimeUnit tunit) { + if (this.log.isDebugEnabled()) { + this.log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit); + } + this.pool.closeIdle(idleTimeout, tunit); + } + + public void closeExpiredConnections() { + this.log.debug("Closing expired connections"); + this.pool.closeExpired(); + } + + public int getMaxTotal() { + return this.pool.getMaxTotal(); + } + + public void setMaxTotal(final int max) { + this.pool.setMaxTotal(max); + } + + public int getDefaultMaxPerRoute() { + return this.pool.getDefaultMaxPerRoute(); + } + + public void setDefaultMaxPerRoute(final int max) { + this.pool.setDefaultMaxPerRoute(max); + } + + public int getMaxPerRoute(final HttpRoute route) { + return this.pool.getMaxPerRoute(route); + } + + public void setMaxPerRoute(final HttpRoute route, final int max) { + this.pool.setMaxPerRoute(route, max); + } + + public PoolStats getTotalStats() { + return this.pool.getTotalStats(); + } + + public PoolStats getStats(final HttpRoute route) { + return this.pool.getStats(route); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/PoolingHttpClientConnectionManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/PoolingHttpClientConnectionManager.java new file mode 100644 index 000000000..a2606b4e6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/PoolingHttpClientConnectionManager.java @@ -0,0 +1,516 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.Closeable; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.config.Lookup; +import ch.boye.httpclientandroidlib.config.Registry; +import ch.boye.httpclientandroidlib.config.RegistryBuilder; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; +import ch.boye.httpclientandroidlib.conn.ConnectionRequest; +import ch.boye.httpclientandroidlib.conn.DnsResolver; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory; +import ch.boye.httpclientandroidlib.conn.ssl.SSLConnectionSocketFactory; +import ch.boye.httpclientandroidlib.pool.ConnFactory; +import ch.boye.httpclientandroidlib.pool.ConnPoolControl; +import ch.boye.httpclientandroidlib.pool.PoolStats; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * <tt>ClientConnectionPoolManager</tt> maintains a pool of + * {@link HttpClientConnection}s and is able to service connection requests + * from multiple execution threads. Connections are pooled on a per route + * basis. A request for a route which already the manager has persistent + * connections for available in the pool will be services by leasing + * a connection from the pool rather than creating a brand new connection. + * <p/> + * <tt>ClientConnectionPoolManager</tt> maintains a maximum limit of connection + * on a per route basis and in total. Per default this implementation will + * create no more than than 2 concurrent connections per given route + * and no more 20 connections in total. For many real-world applications + * these limits may prove too constraining, especially if they use HTTP + * as a transport protocol for their services. Connection limits, however, + * can be adjusted using {@link ConnPoolControl} methods. + * + * @since 4.3 + */ +@ThreadSafe +public class PoolingHttpClientConnectionManager + implements HttpClientConnectionManager, ConnPoolControl<HttpRoute>, Closeable { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ConfigData configData; + private final CPool pool; + private final HttpClientConnectionOperator connectionOperator; + private final AtomicBoolean isShutDown; + + private static Registry<ConnectionSocketFactory> getDefaultRegistry() { + return RegistryBuilder.<ConnectionSocketFactory>create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", SSLConnectionSocketFactory.getSocketFactory()) + .build(); + } + + public PoolingHttpClientConnectionManager() { + this(getDefaultRegistry()); + } + + public PoolingHttpClientConnectionManager(final long timeToLive, final TimeUnit tunit) { + this(getDefaultRegistry(), null, null ,null, timeToLive, tunit); + } + + public PoolingHttpClientConnectionManager( + final Registry<ConnectionSocketFactory> socketFactoryRegistry) { + this(socketFactoryRegistry, null, null); + } + + public PoolingHttpClientConnectionManager( + final Registry<ConnectionSocketFactory> socketFactoryRegistry, + final DnsResolver dnsResolver) { + this(socketFactoryRegistry, null, dnsResolver); + } + + public PoolingHttpClientConnectionManager( + final Registry<ConnectionSocketFactory> socketFactoryRegistry, + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { + this(socketFactoryRegistry, connFactory, null); + } + + public PoolingHttpClientConnectionManager( + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { + this(getDefaultRegistry(), connFactory, null); + } + + public PoolingHttpClientConnectionManager( + final Registry<ConnectionSocketFactory> socketFactoryRegistry, + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, + final DnsResolver dnsResolver) { + this(socketFactoryRegistry, connFactory, null, dnsResolver, -1, TimeUnit.MILLISECONDS); + } + + public PoolingHttpClientConnectionManager( + final Registry<ConnectionSocketFactory> socketFactoryRegistry, + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, + final SchemePortResolver schemePortResolver, + final DnsResolver dnsResolver, + final long timeToLive, final TimeUnit tunit) { + super(); + this.configData = new ConfigData(); + this.pool = new CPool( + new InternalConnectionFactory(this.configData, connFactory), 2, 20, timeToLive, tunit); + this.connectionOperator = new HttpClientConnectionOperator( + socketFactoryRegistry, schemePortResolver, dnsResolver); + this.isShutDown = new AtomicBoolean(false); + } + + PoolingHttpClientConnectionManager( + final CPool pool, + final Lookup<ConnectionSocketFactory> socketFactoryRegistry, + final SchemePortResolver schemePortResolver, + final DnsResolver dnsResolver) { + super(); + this.configData = new ConfigData(); + this.pool = pool; + this.connectionOperator = new HttpClientConnectionOperator( + socketFactoryRegistry, schemePortResolver, dnsResolver); + this.isShutDown = new AtomicBoolean(false); + } + + @Override + protected void finalize() throws Throwable { + try { + shutdown(); + } finally { + super.finalize(); + } + } + + public void close() { + shutdown(); + } + + private String format(final HttpRoute route, final Object state) { + final StringBuilder buf = new StringBuilder(); + buf.append("[route: ").append(route).append("]"); + if (state != null) { + buf.append("[state: ").append(state).append("]"); + } + return buf.toString(); + } + + private String formatStats(final HttpRoute route) { + final StringBuilder buf = new StringBuilder(); + final PoolStats totals = this.pool.getTotalStats(); + final PoolStats stats = this.pool.getStats(route); + buf.append("[total kept alive: ").append(totals.getAvailable()).append("; "); + buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable()); + buf.append(" of ").append(stats.getMax()).append("; "); + buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable()); + buf.append(" of ").append(totals.getMax()).append("]"); + return buf.toString(); + } + + private String format(final CPoolEntry entry) { + final StringBuilder buf = new StringBuilder(); + buf.append("[id: ").append(entry.getId()).append("]"); + buf.append("[route: ").append(entry.getRoute()).append("]"); + final Object state = entry.getState(); + if (state != null) { + buf.append("[state: ").append(state).append("]"); + } + return buf.toString(); + } + + public ConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + Args.notNull(route, "HTTP route"); + if (this.log.isDebugEnabled()) { + this.log.debug("Connection request: " + format(route, state) + formatStats(route)); + } + final Future<CPoolEntry> future = this.pool.lease(route, state, null); + return new ConnectionRequest() { + + public boolean cancel() { + return future.cancel(true); + } + + public HttpClientConnection get( + final long timeout, + final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException { + return leaseConnection(future, timeout, tunit); + } + + }; + + } + + protected HttpClientConnection leaseConnection( + final Future<CPoolEntry> future, + final long timeout, + final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException { + final CPoolEntry entry; + try { + entry = future.get(timeout, tunit); + if (entry == null || future.isCancelled()) { + throw new InterruptedException(); + } + Asserts.check(entry.getConnection() != null, "Pool entry with no connection"); + if (this.log.isDebugEnabled()) { + this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute())); + } + return CPoolProxy.newProxy(entry); + } catch (final TimeoutException ex) { + throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool"); + } + } + + public void releaseConnection( + final HttpClientConnection managedConn, + final Object state, + final long keepalive, final TimeUnit tunit) { + Args.notNull(managedConn, "Managed connection"); + synchronized (managedConn) { + final CPoolEntry entry = CPoolProxy.detach(managedConn); + if (entry == null) { + return; + } + final ManagedHttpClientConnection conn = entry.getConnection(); + try { + if (conn.isOpen()) { + entry.setState(state); + entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS); + if (this.log.isDebugEnabled()) { + final String s; + if (keepalive > 0) { + s = "for " + (double) keepalive / 1000 + " seconds"; + } else { + s = "indefinitely"; + } + this.log.debug("Connection " + format(entry) + " can be kept alive " + s); + } + } + } finally { + this.pool.release(entry, conn.isOpen() && entry.isRouteComplete()); + if (this.log.isDebugEnabled()) { + this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute())); + } + } + } + } + + public void connect( + final HttpClientConnection managedConn, + final HttpRoute route, + final int connectTimeout, + final HttpContext context) throws IOException { + Args.notNull(managedConn, "Managed Connection"); + Args.notNull(route, "HTTP route"); + final ManagedHttpClientConnection conn; + synchronized (managedConn) { + final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn); + conn = entry.getConnection(); + } + final HttpHost host; + if (route.getProxyHost() != null) { + host = route.getProxyHost(); + } else { + host = route.getTargetHost(); + } + final InetSocketAddress localAddress = route.getLocalSocketAddress(); + SocketConfig socketConfig = this.configData.getSocketConfig(host); + if (socketConfig == null) { + socketConfig = this.configData.getDefaultSocketConfig(); + } + if (socketConfig == null) { + socketConfig = SocketConfig.DEFAULT; + } + this.connectionOperator.connect( + conn, host, localAddress, connectTimeout, socketConfig, context); + } + + public void upgrade( + final HttpClientConnection managedConn, + final HttpRoute route, + final HttpContext context) throws IOException { + Args.notNull(managedConn, "Managed Connection"); + Args.notNull(route, "HTTP route"); + final ManagedHttpClientConnection conn; + synchronized (managedConn) { + final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn); + conn = entry.getConnection(); + } + this.connectionOperator.upgrade(conn, route.getTargetHost(), context); + } + + public void routeComplete( + final HttpClientConnection managedConn, + final HttpRoute route, + final HttpContext context) throws IOException { + Args.notNull(managedConn, "Managed Connection"); + Args.notNull(route, "HTTP route"); + synchronized (managedConn) { + final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn); + entry.markRouteComplete(); + } + } + + public void shutdown() { + if (this.isShutDown.compareAndSet(false, true)) { + this.log.debug("Connection manager is shutting down"); + try { + this.pool.shutdown(); + } catch (final IOException ex) { + this.log.debug("I/O exception shutting down connection manager", ex); + } + this.log.debug("Connection manager shut down"); + } + } + + public void closeIdleConnections(final long idleTimeout, final TimeUnit tunit) { + if (this.log.isDebugEnabled()) { + this.log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit); + } + this.pool.closeIdle(idleTimeout, tunit); + } + + public void closeExpiredConnections() { + this.log.debug("Closing expired connections"); + this.pool.closeExpired(); + } + + public int getMaxTotal() { + return this.pool.getMaxTotal(); + } + + public void setMaxTotal(final int max) { + this.pool.setMaxTotal(max); + } + + public int getDefaultMaxPerRoute() { + return this.pool.getDefaultMaxPerRoute(); + } + + public void setDefaultMaxPerRoute(final int max) { + this.pool.setDefaultMaxPerRoute(max); + } + + public int getMaxPerRoute(final HttpRoute route) { + return this.pool.getMaxPerRoute(route); + } + + public void setMaxPerRoute(final HttpRoute route, final int max) { + this.pool.setMaxPerRoute(route, max); + } + + public PoolStats getTotalStats() { + return this.pool.getTotalStats(); + } + + public PoolStats getStats(final HttpRoute route) { + return this.pool.getStats(route); + } + + public SocketConfig getDefaultSocketConfig() { + return this.configData.getDefaultSocketConfig(); + } + + public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) { + this.configData.setDefaultSocketConfig(defaultSocketConfig); + } + + public ConnectionConfig getDefaultConnectionConfig() { + return this.configData.getDefaultConnectionConfig(); + } + + public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig) { + this.configData.setDefaultConnectionConfig(defaultConnectionConfig); + } + + public SocketConfig getSocketConfig(final HttpHost host) { + return this.configData.getSocketConfig(host); + } + + public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig) { + this.configData.setSocketConfig(host, socketConfig); + } + + public ConnectionConfig getConnectionConfig(final HttpHost host) { + return this.configData.getConnectionConfig(host); + } + + public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig) { + this.configData.setConnectionConfig(host, connectionConfig); + } + + static class ConfigData { + + private final Map<HttpHost, SocketConfig> socketConfigMap; + private final Map<HttpHost, ConnectionConfig> connectionConfigMap; + private volatile SocketConfig defaultSocketConfig; + private volatile ConnectionConfig defaultConnectionConfig; + + ConfigData() { + super(); + this.socketConfigMap = new ConcurrentHashMap<HttpHost, SocketConfig>(); + this.connectionConfigMap = new ConcurrentHashMap<HttpHost, ConnectionConfig>(); + } + + public SocketConfig getDefaultSocketConfig() { + return this.defaultSocketConfig; + } + + public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) { + this.defaultSocketConfig = defaultSocketConfig; + } + + public ConnectionConfig getDefaultConnectionConfig() { + return this.defaultConnectionConfig; + } + + public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig) { + this.defaultConnectionConfig = defaultConnectionConfig; + } + + public SocketConfig getSocketConfig(final HttpHost host) { + return this.socketConfigMap.get(host); + } + + public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig) { + this.socketConfigMap.put(host, socketConfig); + } + + public ConnectionConfig getConnectionConfig(final HttpHost host) { + return this.connectionConfigMap.get(host); + } + + public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig) { + this.connectionConfigMap.put(host, connectionConfig); + } + + } + + static class InternalConnectionFactory implements ConnFactory<HttpRoute, ManagedHttpClientConnection> { + + private final ConfigData configData; + private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; + + InternalConnectionFactory( + final ConfigData configData, + final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { + super(); + this.configData = configData != null ? configData : new ConfigData(); + this.connFactory = connFactory != null ? connFactory : + ManagedHttpClientConnectionFactory.INSTANCE; + } + + public ManagedHttpClientConnection create(final HttpRoute route) throws IOException { + ConnectionConfig config = null; + if (route.getProxyHost() != null) { + config = this.configData.getConnectionConfig(route.getProxyHost()); + } + if (config == null) { + config = this.configData.getConnectionConfig(route.getTargetHost()); + } + if (config == null) { + config = this.configData.getDefaultConnectionConfig(); + } + if (config == null) { + config = ConnectionConfig.DEFAULT; + } + return this.connFactory.create(route, config); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ProxySelectorRoutePlanner.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ProxySelectorRoutePlanner.java new file mode 100644 index 000000000..8c68e5e18 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/ProxySelectorRoutePlanner.java @@ -0,0 +1,279 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.params.ConnRouteParams; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.protocol.HttpContext; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on {@link java.net.ProxySelector}. + * By default, it will pick up the proxy settings of the JVM, either + * from system properties or from the browser running the application. + * Additionally, it interprets some + * {@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames parameters}, + * though not the {@link + * ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li> + * <li>{@link ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames#FORCED_ROUTE}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.3) use {@link SystemDefaultRoutePlanner} + */ +@NotThreadSafe // e.g [gs]etProxySelector() +@Deprecated +public class ProxySelectorRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected final SchemeRegistry schemeRegistry; // @ThreadSafe + + /** The proxy selector to use, or <code>null</code> for system default. */ + protected ProxySelector proxySelector; + + /** + * Creates a new proxy selector route planner. + * + * @param schreg the scheme registry + * @param prosel the proxy selector, or + * <code>null</code> for the system default + */ + public ProxySelectorRoutePlanner(final SchemeRegistry schreg, + final ProxySelector prosel) { + Args.notNull(schreg, "SchemeRegistry"); + schemeRegistry = schreg; + proxySelector = prosel; + } + + /** + * Obtains the proxy selector to use. + * + * @return the proxy selector, or <code>null</code> for the system default + */ + public ProxySelector getProxySelector() { + return this.proxySelector; + } + + /** + * Sets the proxy selector to use. + * + * @param prosel the proxy selector, or + * <code>null</code> to use the system default + */ + public void setProxySelector(final ProxySelector prosel) { + this.proxySelector = prosel; + } + + public HttpRoute determineRoute(final HttpHost target, + final HttpRequest request, + final HttpContext context) + throws HttpException { + + Args.notNull(request, "HTTP request"); + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) { + return route; + } + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + Asserts.notNull(target, "Target host"); + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = determineProxy(target, request, context); + + final Scheme schm = + this.schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + /** + * Determines a proxy for the given target. + * + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return the proxy to use, or <code>null</code> for a direct route + * + * @throws HttpException + * in case of system proxy settings that cannot be handled + */ + protected HttpHost determineProxy(final HttpHost target, + final HttpRequest request, + final HttpContext context) + throws HttpException { + + // the proxy selector can be 'unset', so we better deal with null here + ProxySelector psel = this.proxySelector; + if (psel == null) { + psel = ProxySelector.getDefault(); + } + if (psel == null) { + return null; + } + + URI targetURI = null; + try { + targetURI = new URI(target.toURI()); + } catch (final URISyntaxException usx) { + throw new HttpException + ("Cannot convert host to URI: " + target, usx); + } + final List<Proxy> proxies = psel.select(targetURI); + + final Proxy p = chooseProxy(proxies, target, request, context); + + HttpHost result = null; + if (p.type() == Proxy.Type.HTTP) { + // convert the socket address to an HttpHost + if (!(p.address() instanceof InetSocketAddress)) { + throw new HttpException + ("Unable to handle non-Inet proxy address: "+p.address()); + } + final InetSocketAddress isa = (InetSocketAddress) p.address(); + // assume default scheme (http) + result = new HttpHost(getHost(isa), isa.getPort()); + } + + return result; + } + + /** + * Obtains a host from an {@link InetSocketAddress}. + * + * @param isa the socket address + * + * @return a host string, either as a symbolic name or + * as a literal IP address string + * <br/> + * (TODO: determine format for IPv6 addresses, with or without [brackets]) + */ + protected String getHost(final InetSocketAddress isa) { + + //@@@ Will this work with literal IPv6 addresses, or do we + //@@@ need to wrap these in [] for the string representation? + //@@@ Having it in this method at least allows for easy workarounds. + return isa.isUnresolved() ? + isa.getHostName() : isa.getAddress().getHostAddress(); + + } + + /** + * Chooses a proxy from a list of available proxies. + * The default implementation just picks the first non-SOCKS proxy + * from the list. If there are only SOCKS proxies, + * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned. + * Derived classes may implement more advanced strategies, + * such as proxy rotation if there are multiple options. + * + * @param proxies the list of proxies to choose from, + * never <code>null</code> or empty + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return a proxy type + */ + protected Proxy chooseProxy(final List<Proxy> proxies, + final HttpHost target, + final HttpRequest request, + final HttpContext context) { + Args.notEmpty(proxies, "List of proxies"); + + Proxy result = null; + + // check the list for one we can use + for (int i=0; (result == null) && (i < proxies.size()); i++) { + + final Proxy p = proxies.get(i); + switch (p.type()) { + + case DIRECT: + case HTTP: + result = p; + break; + + case SOCKS: + // SOCKS hosts are not handled on the route level. + // The socket may make use of the SOCKS host though. + break; + } + } + + if (result == null) { + //@@@ log as warning or info that only a socks proxy is available? + // result can only be null if all proxies are socks proxies + // socks proxies are not handled on the route planning level + result = Proxy.NO_PROXY; + } + + return result; + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SchemeRegistryFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SchemeRegistryFactory.java new file mode 100644 index 000000000..0a17993d1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SchemeRegistryFactory.java @@ -0,0 +1,90 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory; +import ch.boye.httpclientandroidlib.conn.scheme.Scheme; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory; + +/** + * @since 4.1 + * + * @deprecated (4.3) use {@link ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder}. + */ +@ThreadSafe +@Deprecated +public final class SchemeRegistryFactory { + + /** + * Initializes default scheme registry based on JSSE defaults. System properties will + * not be taken into consideration. + */ + public static SchemeRegistry createDefault() { + final SchemeRegistry registry = new SchemeRegistry(); + registry.register( + new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); + registry.register( + new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); + return registry; + } + + /** + * Initializes default scheme registry using system properties as described in + * <a href="http://download.oracle.com/javase/1,5.0/docs/guide/security/jsse/JSSERefGuide.html"> + * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform + * Standard Edition 5</a> + * <p> + * The following system properties are taken into account by this method: + * <ul> + * <li>ssl.TrustManagerFactory.algorithm</li> + * <li>javax.net.ssl.trustStoreType</li> + * <li>javax.net.ssl.trustStore</li> + * <li>javax.net.ssl.trustStoreProvider</li> + * <li>javax.net.ssl.trustStorePassword</li> + * <li>java.home</li> + * <li>ssl.KeyManagerFactory.algorithm</li> + * <li>javax.net.ssl.keyStoreType</li> + * <li>javax.net.ssl.keyStore</li> + * <li>javax.net.ssl.keyStoreProvider</li> + * <li>javax.net.ssl.keyStorePassword</li> + * </ul> + * <p> + * + * @since 4.2 + */ + public static SchemeRegistry createSystemDefault() { + final SchemeRegistry registry = new SchemeRegistry(); + registry.register( + new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); + registry.register( + new Scheme("https", 443, SSLSocketFactory.getSystemSocketFactory())); + return registry; + } +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SingleClientConnManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SingleClientConnManager.java new file mode 100644 index 000000000..c2b6c8799 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SingleClientConnManager.java @@ -0,0 +1,427 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * A connection manager for a single connection. This connection manager + * maintains only one active connection at a time. Even though this class + * is thread-safe it ought to be used by one execution thread only. + * <p> + * SingleClientConnManager will make an effort to reuse the connection + * for subsequent requests with the same {@link HttpRoute route}. + * It will, however, close the existing connection and open it + * for the given route, if the route of the persistent connection does + * not match that of the connection request. If the connection has been + * already been allocated {@link IllegalStateException} is thrown. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link BasicClientConnectionManager} + */ +@ThreadSafe +@Deprecated +public class SingleClientConnManager implements ClientConnectionManager { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** The message to be logged on multiple allocation. */ + public final static String MISUSE_MESSAGE = + "Invalid use of SingleClientConnManager: connection still allocated.\n" + + "Make sure to release the connection before allocating another one."; + + /** The schemes supported by this connection manager. */ + protected final SchemeRegistry schemeRegistry; + + /** The operator for opening and updating connections. */ + protected final ClientConnectionOperator connOperator; + + /** Whether the connection should be shut down on release. */ + protected final boolean alwaysShutDown; + + /** The one and only entry in this pool. */ + @GuardedBy("this") + protected volatile PoolEntry uniquePoolEntry; + + /** The currently issued managed connection, if any. */ + @GuardedBy("this") + protected volatile ConnAdapter managedConn; + + /** The time of the last connection release, or -1. */ + @GuardedBy("this") + protected volatile long lastReleaseTime; + + /** The time the last released connection expires and shouldn't be reused. */ + @GuardedBy("this") + protected volatile long connectionExpiresTime; + + /** Indicates whether this connection manager is shut down. */ + protected volatile boolean isShutDown; + + /** + * Creates a new simple connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry + * + * @deprecated (4.1) use {@link SingleClientConnManager#SingleClientConnManager(SchemeRegistry)} + */ + @Deprecated + public SingleClientConnManager(final HttpParams params, + final SchemeRegistry schreg) { + this(schreg); + } + /** + * Creates a new simple connection manager. + * + * @param schreg the scheme registry + */ + public SingleClientConnManager(final SchemeRegistry schreg) { + Args.notNull(schreg, "Scheme registry"); + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.uniquePoolEntry = new PoolEntry(); + this.managedConn = null; + this.lastReleaseTime = -1L; + this.alwaysShutDown = false; //@@@ from params? as argument? + this.isShutDown = false; + } + + /** + * @since 4.1 + */ + public SingleClientConnManager() { + this(SchemeRegistryFactory.createDefault()); + } + + @Override + protected void finalize() throws Throwable { + try { + shutdown(); + } finally { // Make sure we call overridden method even if shutdown barfs + super.finalize(); + } + } + + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(final SchemeRegistry schreg) { + return new DefaultClientConnectionOperator(schreg); + } + + /** + * Asserts that this manager is not shut down. + * + * @throws IllegalStateException if this manager is shut down + */ + protected final void assertStillUp() throws IllegalStateException { + Asserts.check(!this.isShutDown, "Manager is shut down"); + } + + public final ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + return new ClientConnectionRequest() { + + public void abortRequest() { + // Nothing to abort, since requests are immediate. + } + + public ManagedClientConnection getConnection( + final long timeout, final TimeUnit tunit) { + return SingleClientConnManager.this.getConnection( + route, state); + } + + }; + } + + /** + * Obtains a connection. + * + * @param route where the connection should point to + * + * @return a connection that can be used to communicate + * along the given route + */ + public ManagedClientConnection getConnection(final HttpRoute route, final Object state) { + Args.notNull(route, "Route"); + assertStillUp(); + + if (log.isDebugEnabled()) { + log.debug("Get connection for route " + route); + } + + synchronized (this) { + + Asserts.check(managedConn == null, MISUSE_MESSAGE); + + // check re-usability of the connection + boolean recreate = false; + boolean shutdown = false; + + // Kill the connection if it expired. + closeExpiredConnections(); + + if (uniquePoolEntry.connection.isOpen()) { + final RouteTracker tracker = uniquePoolEntry.tracker; + shutdown = (tracker == null || // can happen if method is aborted + !tracker.toRoute().equals(route)); + } else { + // If the connection is not open, create a new PoolEntry, + // as the connection may have been marked not reusable, + // due to aborts -- and the PoolEntry should not be reused + // either. There's no harm in recreating an entry if + // the connection is closed. + recreate = true; + } + + if (shutdown) { + recreate = true; + try { + uniquePoolEntry.shutdown(); + } catch (final IOException iox) { + log.debug("Problem shutting down connection.", iox); + } + } + + if (recreate) { + uniquePoolEntry = new PoolEntry(); + } + + managedConn = new ConnAdapter(uniquePoolEntry, route); + + return managedConn; + } + } + + public void releaseConnection( + final ManagedClientConnection conn, + final long validDuration, final TimeUnit timeUnit) { + Args.check(conn instanceof ConnAdapter, "Connection class mismatch, " + + "connection not obtained from this manager"); + assertStillUp(); + + if (log.isDebugEnabled()) { + log.debug("Releasing connection " + conn); + } + + final ConnAdapter sca = (ConnAdapter) conn; + synchronized (sca) { + if (sca.poolEntry == null) + { + return; // already released + } + final ClientConnectionManager manager = sca.getManager(); + Asserts.check(manager == this, "Connection not obtained from this manager"); + try { + // make sure that the response has been read completely + if (sca.isOpen() && (this.alwaysShutDown || + !sca.isMarkedReusable()) + ) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not reusable."); + } + + // make sure this connection will not be re-used + // we might have gotten here because of a shutdown trigger + // shutdown of the adapter also clears the tracked route + sca.shutdown(); + } + } catch (final IOException iox) { + if (log.isDebugEnabled()) { + log.debug("Exception shutting down released connection.", + iox); + } + } finally { + sca.detach(); + synchronized (this) { + managedConn = null; + lastReleaseTime = System.currentTimeMillis(); + if(validDuration > 0) { + connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; + } else { + connectionExpiresTime = Long.MAX_VALUE; + } + } + } + } + } + + public void closeExpiredConnections() { + final long time = connectionExpiresTime; + if (System.currentTimeMillis() >= time) { + closeIdleConnections(0, TimeUnit.MILLISECONDS); + } + } + + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + assertStillUp(); + + // idletime can be 0 or negative, no problem there + Args.notNull(tunit, "Time unit"); + + synchronized (this) { + if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { + final long cutoff = + System.currentTimeMillis() - tunit.toMillis(idletime); + if (lastReleaseTime <= cutoff) { + try { + uniquePoolEntry.close(); + } catch (final IOException iox) { + // ignore + log.debug("Problem closing idle connection.", iox); + } + } + } + } + } + + public void shutdown() { + this.isShutDown = true; + synchronized (this) { + try { + if (uniquePoolEntry != null) { + uniquePoolEntry.shutdown(); + } + } catch (final IOException iox) { + // ignore + log.debug("Problem while shutting down manager.", iox); + } finally { + uniquePoolEntry = null; + managedConn = null; + } + } + } + + protected void revokeConnection() { + final ConnAdapter conn = managedConn; + if (conn == null) { + return; + } + conn.detach(); + + synchronized (this) { + try { + uniquePoolEntry.shutdown(); + } catch (final IOException iox) { + // ignore + log.debug("Problem while shutting down connection.", iox); + } + } + } + + /** + * The pool entry for this connection manager. + */ + protected class PoolEntry extends AbstractPoolEntry { + + /** + * Creates a new pool entry. + * + */ + protected PoolEntry() { + super(SingleClientConnManager.this.connOperator, null); + } + + /** + * Closes the connection in this pool entry. + */ + protected void close() throws IOException { + shutdownEntry(); + if (connection.isOpen()) { + connection.close(); + } + } + + /** + * Shuts down the connection in this pool entry. + */ + protected void shutdown() throws IOException { + shutdownEntry(); + if (connection.isOpen()) { + connection.shutdown(); + } + } + + } + + /** + * The connection adapter used by this manager. + */ + protected class ConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new connection adapter. + * + * @param entry the pool entry for the connection being wrapped + * @param route the planned route for this connection + */ + protected ConnAdapter(final PoolEntry entry, final HttpRoute route) { + super(SingleClientConnManager.this, entry); + markReusable(); + entry.route = route; + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SystemDefaultDnsResolver.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SystemDefaultDnsResolver.java new file mode 100644 index 000000000..bb6b7c60d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SystemDefaultDnsResolver.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import ch.boye.httpclientandroidlib.conn.DnsResolver; + +/** + * DNS resolver that uses the default OS implementation for resolving host names. + * + * @since 4.2 + */ +public class SystemDefaultDnsResolver implements DnsResolver { + + public static final SystemDefaultDnsResolver INSTANCE = new SystemDefaultDnsResolver(); + + public InetAddress[] resolve(final String host) throws UnknownHostException { + return InetAddress.getAllByName(host); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SystemDefaultRoutePlanner.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SystemDefaultRoutePlanner.java new file mode 100644 index 000000000..27ea2a3d0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/SystemDefaultRoutePlanner.java @@ -0,0 +1,132 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.conn; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.conn.SchemePortResolver; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner} implementation + * based on {@link ProxySelector}. By default, this class will pick up + * the proxy settings of the JVM, either from system properties + * or from the browser running the application. + * + * @since 4.3 + */ +@Immutable +public class SystemDefaultRoutePlanner extends DefaultRoutePlanner { + + private final ProxySelector proxySelector; + + public SystemDefaultRoutePlanner( + final SchemePortResolver schemePortResolver, + final ProxySelector proxySelector) { + super(schemePortResolver); + this.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault(); + } + + public SystemDefaultRoutePlanner(final ProxySelector proxySelector) { + this(null, proxySelector); + } + + @Override + protected HttpHost determineProxy( + final HttpHost target, + final HttpRequest request, + final HttpContext context) throws HttpException { + final URI targetURI; + try { + targetURI = new URI(target.toURI()); + } catch (final URISyntaxException ex) { + throw new HttpException("Cannot convert host to URI: " + target, ex); + } + final List<Proxy> proxies = this.proxySelector.select(targetURI); + final Proxy p = chooseProxy(proxies); + HttpHost result = null; + if (p.type() == Proxy.Type.HTTP) { + // convert the socket address to an HttpHost + if (!(p.address() instanceof InetSocketAddress)) { + throw new HttpException("Unable to handle non-Inet proxy address: " + p.address()); + } + final InetSocketAddress isa = (InetSocketAddress) p.address(); + // assume default scheme (http) + result = new HttpHost(getHost(isa), isa.getPort()); + } + + return result; + } + + private String getHost(final InetSocketAddress isa) { + + //@@@ Will this work with literal IPv6 addresses, or do we + //@@@ need to wrap these in [] for the string representation? + //@@@ Having it in this method at least allows for easy workarounds. + return isa.isUnresolved() ? + isa.getHostName() : isa.getAddress().getHostAddress(); + + } + + private Proxy chooseProxy(final List<Proxy> proxies) { + Proxy result = null; + // check the list for one we can use + for (int i=0; (result == null) && (i < proxies.size()); i++) { + final Proxy p = proxies.get(i); + switch (p.type()) { + + case DIRECT: + case HTTP: + result = p; + break; + + case SOCKS: + // SOCKS hosts are not handled on the route level. + // The socket may make use of the SOCKS host though. + break; + } + } + if (result == null) { + //@@@ log as warning or info that only a socks proxy is available? + // result can only be null if all proxies are socks proxies + // socks proxies are not handled on the route planning level + result = Proxy.NO_PROXY; + } + return result; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/Wire.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/Wire.java new file mode 100644 index 000000000..1a678f05d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/Wire.java @@ -0,0 +1,152 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Logs data to the wire LOG. + * TODO: make package private. Should not be part of the public API. + * + * @since 4.0 + */ +@Immutable +public class Wire { + + public HttpClientAndroidLog log; + private final String id; + + /** + * @since 4.3 + */ + public Wire(final HttpClientAndroidLog log, final String id) { + this.log = log; + this.id = id; + } + + public Wire(final HttpClientAndroidLog log) { + this(log, ""); + } + + private void wire(final String header, final InputStream instream) + throws IOException { + final StringBuilder buffer = new StringBuilder(); + int ch; + while ((ch = instream.read()) != -1) { + if (ch == 13) { + buffer.append("[\\r]"); + } else if (ch == 10) { + buffer.append("[\\n]\""); + buffer.insert(0, "\""); + buffer.insert(0, header); + log.debug(id + " " + buffer.toString()); + buffer.setLength(0); + } else if ((ch < 32) || (ch > 127)) { + buffer.append("[0x"); + buffer.append(Integer.toHexString(ch)); + buffer.append("]"); + } else { + buffer.append((char) ch); + } + } + if (buffer.length() > 0) { + buffer.append('\"'); + buffer.insert(0, '\"'); + buffer.insert(0, header); + log.debug(id + " " + buffer.toString()); + } + } + + + public boolean enabled() { + return log.isDebugEnabled(); + } + + public void output(final InputStream outstream) + throws IOException { + Args.notNull(outstream, "Output"); + wire(">> ", outstream); + } + + public void input(final InputStream instream) + throws IOException { + Args.notNull(instream, "Input"); + wire("<< ", instream); + } + + public void output(final byte[] b, final int off, final int len) + throws IOException { + Args.notNull(b, "Output"); + wire(">> ", new ByteArrayInputStream(b, off, len)); + } + + public void input(final byte[] b, final int off, final int len) + throws IOException { + Args.notNull(b, "Input"); + wire("<< ", new ByteArrayInputStream(b, off, len)); + } + + public void output(final byte[] b) + throws IOException { + Args.notNull(b, "Output"); + wire(">> ", new ByteArrayInputStream(b)); + } + + public void input(final byte[] b) + throws IOException { + Args.notNull(b, "Input"); + wire("<< ", new ByteArrayInputStream(b)); + } + + public void output(final int b) + throws IOException { + output(new byte[] {(byte) b}); + } + + public void input(final int b) + throws IOException { + input(new byte[] {(byte) b}); + } + + public void output(final String s) + throws IOException { + Args.notNull(s, "Output"); + output(s.getBytes()); + } + + public void input(final String s) + throws IOException { + Args.notNull(s, "Input"); + input(s.getBytes()); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/package-info.java new file mode 100644 index 000000000..49305e4b7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/package-info.java @@ -0,0 +1,32 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of client connection management + * functions. + */ +package ch.boye.httpclientandroidlib.impl.conn; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/AbstractConnPool.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/AbstractConnPool.java new file mode 100644 index 000000000..f85539c2d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/AbstractConnPool.java @@ -0,0 +1,234 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.annotation.GuardedBy; +import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.conn.IdleConnectionHandler; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * An abstract connection pool. + * It is used by the {@link ThreadSafeClientConnManager}. + * The abstract pool includes a {@link #poolLock}, which is used to + * synchronize access to the internal pool datastructures. + * Don't use <code>synchronized</code> for that purpose! + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.pool.AbstractConnPool} + */ +@Deprecated +public abstract class AbstractConnPool { + + public HttpClientAndroidLog log; + + /** + * The global lock for this pool. + */ + protected final Lock poolLock; + + /** References to issued connections */ + @GuardedBy("poolLock") + protected Set<BasicPoolEntry> leasedConnections; + + /** The current total number of connections. */ + @GuardedBy("poolLock") + protected int numConnections; + + /** Indicates whether this pool is shut down. */ + protected volatile boolean isShutDown; + + protected Set<BasicPoolEntryRef> issuedConnections; + + protected ReferenceQueue<Object> refQueue; + + protected IdleConnectionHandler idleConnHandler; + + /** + * Creates a new connection pool. + */ + protected AbstractConnPool() { + super(); + this.log = new HttpClientAndroidLog(getClass()); + this.leasedConnections = new HashSet<BasicPoolEntry>(); + this.idleConnHandler = new IdleConnectionHandler(); + this.poolLock = new ReentrantLock(); + } + + public void enableConnectionGC() + throws IllegalStateException { + } + + /** + * Obtains a pool entry with a connection within the given timeout. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + public final + BasicPoolEntry getEntry( + final HttpRoute route, + final Object state, + final long timeout, + final TimeUnit tunit) + throws ConnectionPoolTimeoutException, InterruptedException { + return requestPoolEntry(route, state).getPoolEntry(timeout, tunit); + } + + /** + * Returns a new {@link PoolEntryRequest}, from which a {@link BasicPoolEntry} + * can be obtained, or the request can be aborted. + */ + public abstract PoolEntryRequest requestPoolEntry(HttpRoute route, Object state); + + + /** + * Returns an entry into the pool. + * The connection of the entry is expected to be in a suitable state, + * either open and re-usable, or closed. The pool will not make any + * attempt to determine whether it can be re-used or not. + * + * @param entry the entry for the connection to release + * @param reusable <code>true</code> if the entry is deemed + * reusable, <code>false</code> otherwise. + * @param validDuration The duration that the entry should remain free and reusable. + * @param timeUnit The unit of time the duration is measured in. + */ + public abstract void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) + ; + + public void handleReference(final Reference<?> ref) { + } + + protected abstract void handleLostEntry(HttpRoute route); + + /** + * Closes idle connections. + * + * @param idletime the time the connections should have been idle + * in order to be closed now + * @param tunit the unit for the <code>idletime</code> + */ + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + + // idletime can be 0 or negative, no problem there + Args.notNull(tunit, "Time unit"); + + poolLock.lock(); + try { + idleConnHandler.closeIdleConnections(tunit.toMillis(idletime)); + } finally { + poolLock.unlock(); + } + } + + public void closeExpiredConnections() { + poolLock.lock(); + try { + idleConnHandler.closeExpiredConnections(); + } finally { + poolLock.unlock(); + } + } + + + /** + * Deletes all entries for closed connections. + */ + public abstract void deleteClosedConnections(); + + /** + * Shuts down this pool and all associated resources. + * Overriding methods MUST call the implementation here! + */ + public void shutdown() { + + poolLock.lock(); + try { + + if (isShutDown) { + return; + } + + // close all connections that are issued to an application + final Iterator<BasicPoolEntry> iter = leasedConnections.iterator(); + while (iter.hasNext()) { + final BasicPoolEntry entry = iter.next(); + iter.remove(); + closeConnection(entry.getConnection()); + } + idleConnHandler.removeAll(); + + isShutDown = true; + + } finally { + poolLock.unlock(); + } + } + + + /** + * Closes a connection from this pool. + * + * @param conn the connection to close, or <code>null</code> + */ + protected void closeConnection(final OperatedClientConnection conn) { + if (conn != null) { + try { + conn.close(); + } catch (final IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + +} // class AbstractConnPool + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntry.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntry.java new file mode 100644 index 000000000..2c5bf05ce --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntry.java @@ -0,0 +1,163 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.conn.AbstractPoolEntry; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Basic implementation of a connection pool entry. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.pool.PoolEntry} + */ +@Deprecated +public class BasicPoolEntry extends AbstractPoolEntry { + + private final long created; + + private long updated; + private final long validUntil; + private long expiry; + + public BasicPoolEntry(final ClientConnectionOperator op, + final HttpRoute route, + final ReferenceQueue<Object> queue) { + super(op, route); + Args.notNull(route, "HTTP route"); + this.created = System.currentTimeMillis(); + this.validUntil = Long.MAX_VALUE; + this.expiry = this.validUntil; + } + + /** + * Creates a new pool entry. + * + * @param op the connection operator + * @param route the planned route for the connection + */ + public BasicPoolEntry(final ClientConnectionOperator op, + final HttpRoute route) { + this(op, route, -1, TimeUnit.MILLISECONDS); + } + + /** + * Creates a new pool entry with a specified maximum lifetime. + * + * @param op the connection operator + * @param route the planned route for the connection + * @param connTTL maximum lifetime of this entry, <=0 implies "infinity" + * @param timeunit TimeUnit of connTTL + * + * @since 4.1 + */ + public BasicPoolEntry(final ClientConnectionOperator op, + final HttpRoute route, final long connTTL, final TimeUnit timeunit) { + super(op, route); + Args.notNull(route, "HTTP route"); + this.created = System.currentTimeMillis(); + if (connTTL > 0) { + this.validUntil = this.created + timeunit.toMillis(connTTL); + } else { + this.validUntil = Long.MAX_VALUE; + } + this.expiry = this.validUntil; + } + + protected final OperatedClientConnection getConnection() { + return super.connection; + } + + protected final HttpRoute getPlannedRoute() { + return super.route; + } + + protected final BasicPoolEntryRef getWeakRef() { + return null; + } + + @Override + protected void shutdownEntry() { + super.shutdownEntry(); + } + + /** + * @since 4.1 + */ + public long getCreated() { + return this.created; + } + + /** + * @since 4.1 + */ + public long getUpdated() { + return this.updated; + } + + /** + * @since 4.1 + */ + public long getExpiry() { + return this.expiry; + } + + public long getValidUntil() { + return this.validUntil; + } + + /** + * @since 4.1 + */ + public void updateExpiry(final long time, final TimeUnit timeunit) { + this.updated = System.currentTimeMillis(); + final long newExpiry; + if (time > 0) { + newExpiry = this.updated + timeunit.toMillis(time); + } else { + newExpiry = Long.MAX_VALUE; + } + this.expiry = Math.min(validUntil, newExpiry); + } + + /** + * @since 4.1 + */ + public boolean isExpired(final long now) { + return now >= this.expiry; + } + +} + + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntryRef.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntryRef.java new file mode 100644 index 000000000..202e6b102 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntryRef.java @@ -0,0 +1,76 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * A weak reference to a {@link BasicPoolEntry BasicPoolEntry}. + * This reference explicitly keeps the planned route, so the connection + * can be reclaimed if it is lost to garbage collection. + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +public class BasicPoolEntryRef extends WeakReference<BasicPoolEntry> { + + /** The planned route of the entry. */ + private final HttpRoute route; // HttpRoute is @Immutable + + + /** + * Creates a new reference to a pool entry. + * + * @param entry the pool entry, must not be <code>null</code> + * @param queue the reference queue, or <code>null</code> + */ + public BasicPoolEntryRef(final BasicPoolEntry entry, + final ReferenceQueue<Object> queue) { + super(entry, queue); + Args.notNull(entry, "Pool entry"); + route = entry.getPlannedRoute(); + } + + + /** + * Obtain the planned route for the referenced entry. + * The planned route is still available, even if the entry is gone. + * + * @return the planned route + */ + public final HttpRoute getRoute() { + return this.route; + } + +} // class BasicPoolEntryRef + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPooledConnAdapter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPooledConnAdapter.java new file mode 100644 index 000000000..3d6433a2d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPooledConnAdapter.java @@ -0,0 +1,75 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.impl.conn.AbstractPoolEntry; +import ch.boye.httpclientandroidlib.impl.conn.AbstractPooledConnAdapter; + +/** + * A connection wrapper and callback handler. + * All connections given out by the manager are wrappers which + * can be {@link #detach detach}ed to prevent further use on release. + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +public class BasicPooledConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new adapter. + * + * @param tsccm the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected BasicPooledConnAdapter(final ThreadSafeClientConnManager tsccm, + final AbstractPoolEntry entry) { + super(tsccm, entry); + markReusable(); + } + + @Override + protected ClientConnectionManager getManager() { + // override needed only to make method visible in this package + return super.getManager(); + } + + @Override + protected AbstractPoolEntry getPoolEntry() { + // override needed only to make method visible in this package + return super.getPoolEntry(); + } + + @Override + protected void detach() { + // override needed only to make method visible in this package + super.detach(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/ConnPoolByRoute.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/ConnPoolByRoute.java new file mode 100644 index 000000000..22f6a089d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/ConnPoolByRoute.java @@ -0,0 +1,829 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.params.ConnManagerParams; +import ch.boye.httpclientandroidlib.conn.params.ConnPerRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * A connection pool that maintains connections by route. + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3.x, see there for original authors. It implements the same + * algorithm for connection re-use and connection-per-host enforcement: + * <ul> + * <li>connections are re-used only for the exact same route</li> + * <li>connection limits are enforced per route rather than per host</li> + * </ul> + * Note that access to the pool data structures is synchronized via the + * {@link AbstractConnPool#poolLock poolLock} in the base class, + * not via <code>synchronized</code> methods. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.pool.AbstractConnPool} + */ +@Deprecated +public class ConnPoolByRoute extends AbstractConnPool { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final Lock poolLock; + + /** Connection operator for this pool */ + protected final ClientConnectionOperator operator; + + /** Connections per route lookup */ + protected final ConnPerRoute connPerRoute; + + /** References to issued connections */ + protected final Set<BasicPoolEntry> leasedConnections; + + /** The list of free connections */ + protected final Queue<BasicPoolEntry> freeConnections; + + /** The list of WaitingThreads waiting for a connection */ + protected final Queue<WaitingThread> waitingThreads; + + /** Map of route-specific pools */ + protected final Map<HttpRoute, RouteSpecificPool> routeToPool; + + private final long connTTL; + + private final TimeUnit connTTLTimeUnit; + + protected volatile boolean shutdown; + + protected volatile int maxTotalConnections; + + protected volatile int numConnections; + + /** + * Creates a new connection pool, managed by route. + * + * @since 4.1 + */ + public ConnPoolByRoute( + final ClientConnectionOperator operator, + final ConnPerRoute connPerRoute, + final int maxTotalConnections) { + this(operator, connPerRoute, maxTotalConnections, -1, TimeUnit.MILLISECONDS); + } + + /** + * @since 4.1 + */ + public ConnPoolByRoute( + final ClientConnectionOperator operator, + final ConnPerRoute connPerRoute, + final int maxTotalConnections, + final long connTTL, + final TimeUnit connTTLTimeUnit) { + super(); + Args.notNull(operator, "Connection operator"); + Args.notNull(connPerRoute, "Connections per route"); + this.poolLock = super.poolLock; + this.leasedConnections = super.leasedConnections; + this.operator = operator; + this.connPerRoute = connPerRoute; + this.maxTotalConnections = maxTotalConnections; + this.freeConnections = createFreeConnQueue(); + this.waitingThreads = createWaitingThreadQueue(); + this.routeToPool = createRouteToPoolMap(); + this.connTTL = connTTL; + this.connTTLTimeUnit = connTTLTimeUnit; + } + + protected Lock getLock() { + return this.poolLock; + } + + /** + * Creates a new connection pool, managed by route. + * + * @deprecated (4.1) use {@link ConnPoolByRoute#ConnPoolByRoute(ClientConnectionOperator, ConnPerRoute, int)} + */ + @Deprecated + public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) { + this(operator, + ConnManagerParams.getMaxConnectionsPerRoute(params), + ConnManagerParams.getMaxTotalConnections(params)); + } + + /** + * Creates the queue for {@link #freeConnections}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<BasicPoolEntry> createFreeConnQueue() { + return new LinkedList<BasicPoolEntry>(); + } + + /** + * Creates the queue for {@link #waitingThreads}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<WaitingThread> createWaitingThreadQueue() { + return new LinkedList<WaitingThread>(); + } + + /** + * Creates the map for {@link #routeToPool}. + * Called once by the constructor. + * + * @return a map + */ + protected Map<HttpRoute, RouteSpecificPool> createRouteToPoolMap() { + return new HashMap<HttpRoute, RouteSpecificPool>(); + } + + + /** + * Creates a new route-specific pool. + * Called by {@link #getRoutePool} when necessary. + * + * @param route the route + * + * @return the new pool + */ + protected RouteSpecificPool newRouteSpecificPool(final HttpRoute route) { + return new RouteSpecificPool(route, this.connPerRoute); + } + + + /** + * Creates a new waiting thread. + * Called by {@link #getRoutePool} when necessary. + * + * @param cond the condition to wait for + * @param rospl the route specific pool, or <code>null</code> + * + * @return a waiting thread representation + */ + protected WaitingThread newWaitingThread(final Condition cond, + final RouteSpecificPool rospl) { + return new WaitingThread(cond, rospl); + } + + private void closeConnection(final BasicPoolEntry entry) { + final OperatedClientConnection conn = entry.getConnection(); + if (conn != null) { + try { + conn.close(); + } catch (final IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + + /** + * Get a route-specific pool of available connections. + * + * @param route the route + * @param create whether to create the pool if it doesn't exist + * + * @return the pool for the argument route, + * never <code>null</code> if <code>create</code> is <code>true</code> + */ + protected RouteSpecificPool getRoutePool(final HttpRoute route, + final boolean create) { + RouteSpecificPool rospl = null; + poolLock.lock(); + try { + + rospl = routeToPool.get(route); + if ((rospl == null) && create) { + // no pool for this route yet (or anymore) + rospl = newRouteSpecificPool(route); + routeToPool.put(route, rospl); + } + + } finally { + poolLock.unlock(); + } + + return rospl; + } + + public int getConnectionsInPool(final HttpRoute route) { + poolLock.lock(); + try { + // don't allow a pool to be created here! + final RouteSpecificPool rospl = getRoutePool(route, false); + return (rospl != null) ? rospl.getEntryCount() : 0; + + } finally { + poolLock.unlock(); + } + } + + public int getConnectionsInPool() { + poolLock.lock(); + try { + return numConnections; + } finally { + poolLock.unlock(); + } + } + + @Override + public PoolEntryRequest requestPoolEntry( + final HttpRoute route, + final Object state) { + + final WaitingThreadAborter aborter = new WaitingThreadAborter(); + + return new PoolEntryRequest() { + + public void abortRequest() { + poolLock.lock(); + try { + aborter.abort(); + } finally { + poolLock.unlock(); + } + } + + public BasicPoolEntry getPoolEntry( + final long timeout, + final TimeUnit tunit) + throws InterruptedException, ConnectionPoolTimeoutException { + return getEntryBlocking(route, state, timeout, tunit, aborter); + } + + }; + } + + /** + * Obtains a pool entry with a connection within the given timeout. + * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)} + * must be called before blocking, to allow the thread to be interrupted. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * @param aborter an object which can abort a {@link WaitingThread}. + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + protected BasicPoolEntry getEntryBlocking( + final HttpRoute route, final Object state, + final long timeout, final TimeUnit tunit, + final WaitingThreadAborter aborter) + throws ConnectionPoolTimeoutException, InterruptedException { + + Date deadline = null; + if (timeout > 0) { + deadline = new Date + (System.currentTimeMillis() + tunit.toMillis(timeout)); + } + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + WaitingThread waitingThread = null; + + while (entry == null) { + Asserts.check(!shutdown, "Connection pool shut down"); + + if (log.isDebugEnabled()) { + log.debug("[" + route + "] total kept alive: " + freeConnections.size() + + ", total issued: " + leasedConnections.size() + + ", total allocated: " + numConnections + " out of " + maxTotalConnections); + } + + // the cases to check for: + // - have a free connection for that route + // - allowed to create a free connection for that route + // - can delete and replace a free connection for another route + // - need to wait for one of the things above to come true + + entry = getFreeEntry(rospl, state); + if (entry != null) { + break; + } + + final boolean hasCapacity = rospl.getCapacity() > 0; + + if (log.isDebugEnabled()) { + log.debug("Available capacity: " + rospl.getCapacity() + + " out of " + rospl.getMaxEntries() + + " [" + route + "][" + state + "]"); + } + + if (hasCapacity && numConnections < maxTotalConnections) { + + entry = createEntry(rospl, operator); + + } else if (hasCapacity && !freeConnections.isEmpty()) { + + deleteLeastUsedEntry(); + // if least used entry's route was the same as rospl, + // rospl is now out of date : we preemptively refresh + rospl = getRoutePool(route, true); + entry = createEntry(rospl, operator); + + } else { + + if (log.isDebugEnabled()) { + log.debug("Need to wait for connection" + + " [" + route + "][" + state + "]"); + } + + if (waitingThread == null) { + waitingThread = + newWaitingThread(poolLock.newCondition(), rospl); + aborter.setWaitingThread(waitingThread); + } + + boolean success = false; + try { + rospl.queueThread(waitingThread); + waitingThreads.add(waitingThread); + success = waitingThread.await(deadline); + + } finally { + // In case of 'success', we were woken up by the + // connection pool and should now have a connection + // waiting for us, or else we're shutting down. + // Just continue in the loop, both cases are checked. + rospl.removeThread(waitingThread); + waitingThreads.remove(waitingThread); + } + + // check for spurious wakeup vs. timeout + if (!success && (deadline != null) && + (deadline.getTime() <= System.currentTimeMillis())) { + throw new ConnectionPoolTimeoutException + ("Timeout waiting for connection from pool"); + } + } + } // while no entry + + } finally { + poolLock.unlock(); + } + return entry; + } + + @Override + public void freeEntry(final BasicPoolEntry entry, final boolean reusable, final long validDuration, final TimeUnit timeUnit) { + + final HttpRoute route = entry.getPlannedRoute(); + if (log.isDebugEnabled()) { + log.debug("Releasing connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + if (shutdown) { + // the pool is shut down, release the + // connection's resources and get out of here + closeConnection(entry); + return; + } + + // no longer issued, we keep a hard reference now + leasedConnections.remove(entry); + + final RouteSpecificPool rospl = getRoutePool(route, true); + + if (reusable && rospl.getCapacity() >= 0) { + if (log.isDebugEnabled()) { + final String s; + if (validDuration > 0) { + s = "for " + validDuration + " " + timeUnit; + } else { + s = "indefinitely"; + } + log.debug("Pooling connection" + + " [" + route + "][" + entry.getState() + "]; keep alive " + s); + } + rospl.freeEntry(entry); + entry.updateExpiry(validDuration, timeUnit); + freeConnections.add(entry); + } else { + closeConnection(entry); + rospl.dropEntry(); + numConnections--; + } + + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + } + + /** + * If available, get a free pool entry for a route. + * + * @param rospl the route-specific pool from which to get an entry + * + * @return an available pool entry for the given route, or + * <code>null</code> if none is available + */ + protected BasicPoolEntry getFreeEntry(final RouteSpecificPool rospl, final Object state) { + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + boolean done = false; + while(!done) { + + entry = rospl.allocEntry(state); + + if (entry != null) { + if (log.isDebugEnabled()) { + log.debug("Getting free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + + } + freeConnections.remove(entry); + if (entry.isExpired(System.currentTimeMillis())) { + // If the free entry isn't valid anymore, get rid of it + // and loop to find another one that might be valid. + if (log.isDebugEnabled()) { + log.debug("Closing expired free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + } + closeConnection(entry); + // We use dropEntry instead of deleteEntry because the entry + // is no longer "free" (we just allocated it), and deleteEntry + // can only be used to delete free entries. + rospl.dropEntry(); + numConnections--; + } else { + leasedConnections.add(entry); + done = true; + } + + } else { + done = true; + if (log.isDebugEnabled()) { + log.debug("No free connections" + + " [" + rospl.getRoute() + "][" + state + "]"); + } + } + } + } finally { + poolLock.unlock(); + } + return entry; + } + + + /** + * Creates a new pool entry. + * This method assumes that the new connection will be handed + * out immediately. + * + * @param rospl the route-specific pool for which to create the entry + * @param op the operator for creating a connection + * + * @return the new pool entry for a new connection + */ + protected BasicPoolEntry createEntry(final RouteSpecificPool rospl, + final ClientConnectionOperator op) { + + if (log.isDebugEnabled()) { + log.debug("Creating new connection [" + rospl.getRoute() + "]"); + } + + // the entry will create the connection when needed + final BasicPoolEntry entry = new BasicPoolEntry(op, rospl.getRoute(), connTTL, connTTLTimeUnit); + + poolLock.lock(); + try { + rospl.createdEntry(entry); + numConnections++; + leasedConnections.add(entry); + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Deletes a given pool entry. + * This closes the pooled connection and removes all references, + * so that it can be GCed. + * + * <p><b>Note:</b> Does not remove the entry from the freeConnections list. + * It is assumed that the caller has already handled this step.</p> + * <!-- @@@ is that a good idea? or rather fix it? --> + * + * @param entry the pool entry for the connection to delete + */ + protected void deleteEntry(final BasicPoolEntry entry) { + + final HttpRoute route = entry.getPlannedRoute(); + + if (log.isDebugEnabled()) { + log.debug("Deleting connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + + closeConnection(entry); + + final RouteSpecificPool rospl = getRoutePool(route, true); + rospl.deleteEntry(entry); + numConnections--; + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + } finally { + poolLock.unlock(); + } + } + + + /** + * Delete an old, free pool entry to make room for a new one. + * Used to replace pool entries with ones for a different route. + */ + protected void deleteLeastUsedEntry() { + poolLock.lock(); + try { + + final BasicPoolEntry entry = freeConnections.remove(); + + if (entry != null) { + deleteEntry(entry); + } else if (log.isDebugEnabled()) { + log.debug("No free connection to delete"); + } + + } finally { + poolLock.unlock(); + } + } + + @Override + protected void handleLostEntry(final HttpRoute route) { + + poolLock.lock(); + try { + + final RouteSpecificPool rospl = getRoutePool(route, true); + rospl.dropEntry(); + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + numConnections--; + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + } + + /** + * Notifies a waiting thread that a connection is available. + * This will wake a thread waiting in the specific route pool, + * if there is one. + * Otherwise, a thread in the connection pool will be notified. + * + * @param rospl the pool in which to notify, or <code>null</code> + */ + protected void notifyWaitingThread(final RouteSpecificPool rospl) { + + //@@@ while this strategy provides for best connection re-use, + //@@@ is it fair? only do this if the connection is open? + // Find the thread we are going to notify. We want to ensure that + // each waiting thread is only interrupted once, so we will remove + // it from all wait queues before interrupting. + WaitingThread waitingThread = null; + + poolLock.lock(); + try { + + if ((rospl != null) && rospl.hasThread()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on pool" + + " [" + rospl.getRoute() + "]"); + } + waitingThread = rospl.nextThread(); + } else if (!waitingThreads.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on any pool"); + } + waitingThread = waitingThreads.remove(); + } else if (log.isDebugEnabled()) { + log.debug("Notifying no-one, there are no waiting threads"); + } + + if (waitingThread != null) { + waitingThread.wakeup(); + } + + } finally { + poolLock.unlock(); + } + } + + + @Override + public void deleteClosedConnections() { + poolLock.lock(); + try { + final Iterator<BasicPoolEntry> iter = freeConnections.iterator(); + while (iter.hasNext()) { + final BasicPoolEntry entry = iter.next(); + if (!entry.getConnection().isOpen()) { + iter.remove(); + deleteEntry(entry); + } + } + } finally { + poolLock.unlock(); + } + } + + /** + * Closes idle connections. + * + * @param idletime the time the connections should have been idle + * in order to be closed now + * @param tunit the unit for the <code>idletime</code> + */ + @Override + public void closeIdleConnections(final long idletime, final TimeUnit tunit) { + Args.notNull(tunit, "Time unit"); + final long t = idletime > 0 ? idletime : 0; + if (log.isDebugEnabled()) { + log.debug("Closing connections idle longer than " + t + " " + tunit); + } + // the latest time for which connections will be closed + final long deadline = System.currentTimeMillis() - tunit.toMillis(t); + poolLock.lock(); + try { + final Iterator<BasicPoolEntry> iter = freeConnections.iterator(); + while (iter.hasNext()) { + final BasicPoolEntry entry = iter.next(); + if (entry.getUpdated() <= deadline) { + if (log.isDebugEnabled()) { + log.debug("Closing connection last used @ " + new Date(entry.getUpdated())); + } + iter.remove(); + deleteEntry(entry); + } + } + } finally { + poolLock.unlock(); + } + } + + @Override + public void closeExpiredConnections() { + log.debug("Closing expired connections"); + final long now = System.currentTimeMillis(); + + poolLock.lock(); + try { + final Iterator<BasicPoolEntry> iter = freeConnections.iterator(); + while (iter.hasNext()) { + final BasicPoolEntry entry = iter.next(); + if (entry.isExpired(now)) { + if (log.isDebugEnabled()) { + log.debug("Closing connection expired @ " + new Date(entry.getExpiry())); + } + iter.remove(); + deleteEntry(entry); + } + } + } finally { + poolLock.unlock(); + } + } + + @Override + public void shutdown() { + poolLock.lock(); + try { + if (shutdown) { + return; + } + shutdown = true; + + // close all connections that are issued to an application + final Iterator<BasicPoolEntry> iter1 = leasedConnections.iterator(); + while (iter1.hasNext()) { + final BasicPoolEntry entry = iter1.next(); + iter1.remove(); + closeConnection(entry); + } + + // close all free connections + final Iterator<BasicPoolEntry> iter2 = freeConnections.iterator(); + while (iter2.hasNext()) { + final BasicPoolEntry entry = iter2.next(); + iter2.remove(); + + if (log.isDebugEnabled()) { + log.debug("Closing connection" + + " [" + entry.getPlannedRoute() + "][" + entry.getState() + "]"); + } + closeConnection(entry); + } + + // wake up all waiting threads + final Iterator<WaitingThread> iwth = waitingThreads.iterator(); + while (iwth.hasNext()) { + final WaitingThread waiter = iwth.next(); + iwth.remove(); + waiter.wakeup(); + } + + routeToPool.clear(); + + } finally { + poolLock.unlock(); + } + } + + /** + * since 4.1 + */ + public void setMaxTotalConnections(final int max) { + poolLock.lock(); + try { + maxTotalConnections = max; + } finally { + poolLock.unlock(); + } + } + + + /** + * since 4.1 + */ + public int getMaxTotalConnections() { + return maxTotalConnections; + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/PoolEntryRequest.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/PoolEntryRequest.java new file mode 100644 index 000000000..bf7eb147f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/PoolEntryRequest.java @@ -0,0 +1,69 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; + +/** + * Encapsulates a request for a {@link BasicPoolEntry}. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link java.util.concurrent.Future} + */ +@Deprecated +public interface PoolEntryRequest { + + /** + * Obtains a pool entry with a connection within the given timeout. + * If {@link #abortRequest()} is called before this completes + * an {@link InterruptedException} is thrown. + * + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted or the request was aborted + */ + BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException; + + /** + * Aborts the active or next call to + * {@link #getPoolEntry(long, TimeUnit)}. + */ + void abortRequest(); + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/RouteSpecificPool.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/RouteSpecificPool.java new file mode 100644 index 000000000..73964c8d1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/RouteSpecificPool.java @@ -0,0 +1,313 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.Queue; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; +import ch.boye.httpclientandroidlib.conn.params.ConnPerRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.LangUtils; + + +/** + * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}. + * The methods in this class are unsynchronized. It is expected that the + * containing pool takes care of synchronization. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.pool.AbstractConnPool} + */ +@Deprecated +public class RouteSpecificPool { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + /** The route this pool is for. */ + protected final HttpRoute route; //Immutable + + protected final int maxEntries; + + /** Connections per route */ + protected final ConnPerRoute connPerRoute; + + /** + * The list of free entries. + * This list is managed LIFO, to increase idle times and + * allow for closing connections that are not really needed. + */ + protected final LinkedList<BasicPoolEntry> freeEntries; + + /** The list of threads waiting for this pool. */ + protected final Queue<WaitingThread> waitingThreads; + + /** The number of created entries. */ + protected int numEntries; + + /** + * @deprecated (4.1) use {@link RouteSpecificPool#RouteSpecificPool(HttpRoute, ConnPerRoute)} + */ + @Deprecated + public RouteSpecificPool(final HttpRoute route, final int maxEntries) { + this.route = route; + this.maxEntries = maxEntries; + this.connPerRoute = new ConnPerRoute() { + public int getMaxForRoute(final HttpRoute route) { + return RouteSpecificPool.this.maxEntries; + } + }; + this.freeEntries = new LinkedList<BasicPoolEntry>(); + this.waitingThreads = new LinkedList<WaitingThread>(); + this.numEntries = 0; + } + + + /** + * Creates a new route-specific pool. + * + * @param route the route for which to pool + * @param connPerRoute the connections per route configuration + */ + public RouteSpecificPool(final HttpRoute route, final ConnPerRoute connPerRoute) { + this.route = route; + this.connPerRoute = connPerRoute; + this.maxEntries = connPerRoute.getMaxForRoute(route); + this.freeEntries = new LinkedList<BasicPoolEntry>(); + this.waitingThreads = new LinkedList<WaitingThread>(); + this.numEntries = 0; + } + + + /** + * Obtains the route for which this pool is specific. + * + * @return the route + */ + public final HttpRoute getRoute() { + return route; + } + + + /** + * Obtains the maximum number of entries allowed for this pool. + * + * @return the max entry number + */ + public final int getMaxEntries() { + return maxEntries; + } + + + /** + * Indicates whether this pool is unused. + * A pool is unused if there is neither an entry nor a waiting thread. + * All entries count, not only the free but also the allocated ones. + * + * @return <code>true</code> if this pool is unused, + * <code>false</code> otherwise + */ + public boolean isUnused() { + return (numEntries < 1) && waitingThreads.isEmpty(); + } + + + /** + * Return remaining capacity of this pool + * + * @return capacity + */ + public int getCapacity() { + return connPerRoute.getMaxForRoute(route) - numEntries; + } + + + /** + * Obtains the number of entries. + * This includes not only the free entries, but also those that + * have been created and are currently issued to an application. + * + * @return the number of entries for the route of this pool + */ + public final int getEntryCount() { + return numEntries; + } + + + /** + * Obtains a free entry from this pool, if one is available. + * + * @return an available pool entry, or <code>null</code> if there is none + */ + public BasicPoolEntry allocEntry(final Object state) { + if (!freeEntries.isEmpty()) { + final ListIterator<BasicPoolEntry> it = freeEntries.listIterator(freeEntries.size()); + while (it.hasPrevious()) { + final BasicPoolEntry entry = it.previous(); + if (entry.getState() == null || LangUtils.equals(state, entry.getState())) { + it.remove(); + return entry; + } + } + } + if (getCapacity() == 0 && !freeEntries.isEmpty()) { + final BasicPoolEntry entry = freeEntries.remove(); + entry.shutdownEntry(); + final OperatedClientConnection conn = entry.getConnection(); + try { + conn.close(); + } catch (final IOException ex) { + log.debug("I/O error closing connection", ex); + } + return entry; + } + return null; + } + + + /** + * Returns an allocated entry to this pool. + * + * @param entry the entry obtained from {@link #allocEntry allocEntry} + * or presented to {@link #createdEntry createdEntry} + */ + public void freeEntry(final BasicPoolEntry entry) { + if (numEntries < 1) { + throw new IllegalStateException + ("No entry created for this pool. " + route); + } + if (numEntries <= freeEntries.size()) { + throw new IllegalStateException + ("No entry allocated from this pool. " + route); + } + freeEntries.add(entry); + } + + + /** + * Indicates creation of an entry for this pool. + * The entry will <i>not</i> be added to the list of free entries, + * it is only recognized as belonging to this pool now. It can then + * be passed to {@link #freeEntry freeEntry}. + * + * @param entry the entry that was created for this pool + */ + public void createdEntry(final BasicPoolEntry entry) { + Args.check(route.equals(entry.getPlannedRoute()), "Entry not planned for this pool"); + numEntries++; + } + + + /** + * Deletes an entry from this pool. + * Only entries that are currently free in this pool can be deleted. + * Allocated entries can not be deleted. + * + * @param entry the entry to delete from this pool + * + * @return <code>true</code> if the entry was found and deleted, or + * <code>false</code> if the entry was not found + */ + public boolean deleteEntry(final BasicPoolEntry entry) { + + final boolean found = freeEntries.remove(entry); + if (found) { + numEntries--; + } + return found; + } + + + /** + * Forgets about an entry from this pool. + * This method is used to indicate that an entry + * {@link #allocEntry allocated} + * from this pool has been lost and will not be returned. + */ + public void dropEntry() { + Asserts.check(numEntries > 0, "There is no entry that could be dropped"); + numEntries--; + } + + + /** + * Adds a waiting thread. + * This pool makes no attempt to match waiting threads with pool entries. + * It is the caller's responsibility to check that there is no entry + * before adding a waiting thread. + * + * @param wt the waiting thread + */ + public void queueThread(final WaitingThread wt) { + Args.notNull(wt, "Waiting thread"); + this.waitingThreads.add(wt); + } + + + /** + * Checks whether there is a waiting thread in this pool. + * + * @return <code>true</code> if there is a waiting thread, + * <code>false</code> otherwise + */ + public boolean hasThread() { + return !this.waitingThreads.isEmpty(); + } + + + /** + * Returns the next thread in the queue. + * + * @return a waiting thread, or <code>null</code> if there is none + */ + public WaitingThread nextThread() { + return this.waitingThreads.peek(); + } + + + /** + * Removes a waiting thread, if it is queued. + * + * @param wt the waiting thread + */ + public void removeThread(final WaitingThread wt) { + if (wt == null) { + return; + } + + this.waitingThreads.remove(wt); + } + + +} // class RouteSpecificPool diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/ThreadSafeClientConnManager.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/ThreadSafeClientConnManager.java new file mode 100644 index 000000000..1a1ff5c37 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/ThreadSafeClientConnManager.java @@ -0,0 +1,377 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; +import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; +import ch.boye.httpclientandroidlib.conn.ConnectionPoolTimeoutException; +import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; +import ch.boye.httpclientandroidlib.conn.params.ConnPerRouteBean; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; +import ch.boye.httpclientandroidlib.impl.conn.DefaultClientConnectionOperator; +import ch.boye.httpclientandroidlib.impl.conn.SchemeRegistryFactory; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; + +/** + * Manages a pool of {@link ch.boye.httpclientandroidlib.conn.OperatedClientConnection } + * and is able to service connection requests from multiple execution threads. + * Connections are pooled on a per route basis. A request for a route which + * already the manager has persistent connections for available in the pool + * will be services by leasing a connection from the pool rather than + * creating a brand new connection. + * <p> + * ThreadSafeClientConnManager maintains a maximum limit of connection on + * a per route basis and in total. Per default this implementation will + * create no more than than 2 concurrent connections per given route + * and no more 20 connections in total. For many real-world applications + * these limits may prove too constraining, especially if they use HTTP + * as a transport protocol for their services. Connection limits, however, + * can be adjusted using HTTP parameters. + * + * @since 4.0 + * + * @deprecated (4.2) use {@link ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager} + */ +@ThreadSafe +@Deprecated +public class ThreadSafeClientConnManager implements ClientConnectionManager { + + public HttpClientAndroidLog log; + + /** The schemes supported by this connection manager. */ + protected final SchemeRegistry schemeRegistry; // @ThreadSafe + + protected final AbstractConnPool connectionPool; + + /** The pool of connections being managed. */ + protected final ConnPoolByRoute pool; + + /** The operator for opening and updating connections. */ + protected final ClientConnectionOperator connOperator; // DefaultClientConnectionOperator is @ThreadSafe + + protected final ConnPerRouteBean connPerRoute; + + /** + * Creates a new thread safe connection manager. + * + * @param schreg the scheme registry. + */ + public ThreadSafeClientConnManager(final SchemeRegistry schreg) { + this(schreg, -1, TimeUnit.MILLISECONDS); + } + + /** + * @since 4.1 + */ + public ThreadSafeClientConnManager() { + this(SchemeRegistryFactory.createDefault()); + } + + /** + * Creates a new thread safe connection manager. + * + * @param schreg the scheme registry. + * @param connTTL max connection lifetime, <=0 implies "infinity" + * @param connTTLTimeUnit TimeUnit of connTTL + * + * @since 4.1 + */ + public ThreadSafeClientConnManager(final SchemeRegistry schreg, + final long connTTL, final TimeUnit connTTLTimeUnit) { + this(schreg, connTTL, connTTLTimeUnit, new ConnPerRouteBean()); + } + + /** + * Creates a new thread safe connection manager. + * + * @param schreg the scheme registry. + * @param connTTL max connection lifetime, <=0 implies "infinity" + * @param connTTLTimeUnit TimeUnit of connTTL + * @param connPerRoute mapping of maximum connections per route, + * provided as a dependency so it can be managed externally, e.g. + * for dynamic connection pool size management. + * + * @since 4.2 + */ + public ThreadSafeClientConnManager(final SchemeRegistry schreg, + final long connTTL, final TimeUnit connTTLTimeUnit, final ConnPerRouteBean connPerRoute) { + super(); + Args.notNull(schreg, "Scheme registry"); + this.log = new HttpClientAndroidLog(getClass()); + this.schemeRegistry = schreg; + this.connPerRoute = connPerRoute; + this.connOperator = createConnectionOperator(schreg); + this.pool = createConnectionPool(connTTL, connTTLTimeUnit) ; + this.connectionPool = this.pool; + } + + /** + * Creates a new thread safe connection manager. + * + * @param params the parameters for this manager. + * @param schreg the scheme registry. + * + * @deprecated (4.1) use {@link ThreadSafeClientConnManager#ThreadSafeClientConnManager(SchemeRegistry)} + */ + @Deprecated + public ThreadSafeClientConnManager(final HttpParams params, + final SchemeRegistry schreg) { + Args.notNull(schreg, "Scheme registry"); + this.log = new HttpClientAndroidLog(getClass()); + this.schemeRegistry = schreg; + this.connPerRoute = new ConnPerRouteBean(); + this.connOperator = createConnectionOperator(schreg); + this.pool = (ConnPoolByRoute) createConnectionPool(params) ; + this.connectionPool = this.pool; + } + + @Override + protected void finalize() throws Throwable { + try { + shutdown(); + } finally { + super.finalize(); + } + } + + /** + * Hook for creating the connection pool. + * + * @return the connection pool to use + * + * @deprecated (4.1) use #createConnectionPool(long, TimeUnit)) + */ + @Deprecated + protected AbstractConnPool createConnectionPool(final HttpParams params) { + return new ConnPoolByRoute(connOperator, params); + } + + /** + * Hook for creating the connection pool. + * + * @return the connection pool to use + * + * @since 4.1 + */ + protected ConnPoolByRoute createConnectionPool(final long connTTL, final TimeUnit connTTLTimeUnit) { + return new ConnPoolByRoute(connOperator, connPerRoute, 20, connTTL, connTTLTimeUnit); + } + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry. + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(final SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg);// @ThreadSafe + } + + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + public ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + final PoolEntryRequest poolRequest = pool.requestPoolEntry( + route, state); + + return new ClientConnectionRequest() { + + public void abortRequest() { + poolRequest.abortRequest(); + } + + public ManagedClientConnection getConnection( + final long timeout, final TimeUnit tunit) throws InterruptedException, + ConnectionPoolTimeoutException { + Args.notNull(route, "Route"); + + if (log.isDebugEnabled()) { + log.debug("Get connection: " + route + ", timeout = " + timeout); + } + + final BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit); + return new BasicPooledConnAdapter(ThreadSafeClientConnManager.this, entry); + } + + }; + + } + + public void releaseConnection(final ManagedClientConnection conn, final long validDuration, final TimeUnit timeUnit) { + Args.check(conn instanceof BasicPooledConnAdapter, "Connection class mismatch, " + + "connection not obtained from this manager"); + final BasicPooledConnAdapter hca = (BasicPooledConnAdapter) conn; + if (hca.getPoolEntry() != null) { + Asserts.check(hca.getManager() == this, "Connection not obtained from this manager"); + } + synchronized (hca) { + final BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry(); + if (entry == null) { + return; + } + try { + // make sure that the response has been read completely + if (hca.isOpen() && !hca.isMarkedReusable()) { + // In MTHCM, there would be a call to + // SimpleHttpConnectionManager.finishLastResponse(conn); + // Consuming the response is handled outside in 4.0. + + // make sure this connection will not be re-used + // Shut down rather than close, we might have gotten here + // because of a shutdown trigger. + // Shutdown of the adapter also clears the tracked route. + hca.shutdown(); + } + } catch (final IOException iox) { + if (log.isDebugEnabled()) { + log.debug("Exception shutting down released connection.", + iox); + } + } finally { + final boolean reusable = hca.isMarkedReusable(); + if (log.isDebugEnabled()) { + if (reusable) { + log.debug("Released connection is reusable."); + } else { + log.debug("Released connection is not reusable."); + } + } + hca.detach(); + pool.freeEntry(entry, reusable, validDuration, timeUnit); + } + } + } + + public void shutdown() { + log.debug("Shutting down"); + pool.shutdown(); + } + + /** + * Gets the total number of pooled connections for the given route. + * This is the total number of connections that have been created and + * are still in use by this connection manager for the route. + * This value will not exceed the maximum number of connections per host. + * + * @param route the route in question + * + * @return the total number of pooled connections for that route + */ + public int getConnectionsInPool(final HttpRoute route) { + return pool.getConnectionsInPool(route); + } + + /** + * Gets the total number of pooled connections. This is the total number of + * connections that have been created and are still in use by this connection + * manager. This value will not exceed the maximum number of connections + * in total. + * + * @return the total number of pooled connections + */ + public int getConnectionsInPool() { + return pool.getConnectionsInPool(); + } + + public void closeIdleConnections(final long idleTimeout, final TimeUnit tunit) { + if (log.isDebugEnabled()) { + log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit); + } + pool.closeIdleConnections(idleTimeout, tunit); + } + + public void closeExpiredConnections() { + log.debug("Closing expired connections"); + pool.closeExpiredConnections(); + } + + /** + * since 4.1 + */ + public int getMaxTotal() { + return pool.getMaxTotalConnections(); + } + + /** + * since 4.1 + */ + public void setMaxTotal(final int max) { + pool.setMaxTotalConnections(max); + } + + /** + * @since 4.1 + */ + public int getDefaultMaxPerRoute() { + return connPerRoute.getDefaultMaxPerRoute(); + } + + /** + * @since 4.1 + */ + public void setDefaultMaxPerRoute(final int max) { + connPerRoute.setDefaultMaxPerRoute(max); + } + + /** + * @since 4.1 + */ + public int getMaxForRoute(final HttpRoute route) { + return connPerRoute.getMaxForRoute(route); + } + + /** + * @since 4.1 + */ + public void setMaxForRoute(final HttpRoute route, final int max) { + connPerRoute.setMaxForRoute(route, max); + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThread.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThread.java new file mode 100644 index 000000000..c3f46cacf --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThread.java @@ -0,0 +1,198 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + + +import java.util.Date; +import java.util.concurrent.locks.Condition; + +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Represents a thread waiting for a connection. + * This class implements throwaway objects. It is instantiated whenever + * a thread needs to wait. Instances are not re-used, except if the + * waiting thread experiences a spurious wakeup and continues to wait. + * <br/> + * All methods assume external synchronization on the condition + * passed to the constructor. + * Instances of this class do <i>not</i> synchronize access! + * + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +public class WaitingThread { + + /** The condition on which the thread is waiting. */ + private final Condition cond; + + /** The route specific pool on which the thread is waiting. */ + //@@@ replace with generic pool interface + private final RouteSpecificPool pool; + + /** The thread that is waiting for an entry. */ + private Thread waiter; + + /** True if this was interrupted. */ + private boolean aborted; + + + /** + * Creates a new entry for a waiting thread. + * + * @param cond the condition for which to wait + * @param pool the pool on which the thread will be waiting, + * or <code>null</code> + */ + public WaitingThread(final Condition cond, final RouteSpecificPool pool) { + + Args.notNull(cond, "Condition"); + + this.cond = cond; + this.pool = pool; + } + + + /** + * Obtains the condition. + * + * @return the condition on which to wait, never <code>null</code> + */ + public final Condition getCondition() { + // not synchronized + return this.cond; + } + + + /** + * Obtains the pool, if there is one. + * + * @return the pool on which a thread is or was waiting, + * or <code>null</code> + */ + public final RouteSpecificPool getPool() { + // not synchronized + return this.pool; + } + + + /** + * Obtains the thread, if there is one. + * + * @return the thread which is waiting, or <code>null</code> + */ + public final Thread getThread() { + // not synchronized + return this.waiter; + } + + + /** + * Blocks the calling thread. + * This method returns when the thread is notified or interrupted, + * if a timeout occurrs, or if there is a spurious wakeup. + * <br/> + * This method assumes external synchronization. + * + * @param deadline when to time out, or <code>null</code> for no timeout + * + * @return <code>true</code> if the condition was satisfied, + * <code>false</code> in case of a timeout. + * Typically, a call to {@link #wakeup} is used to indicate + * that the condition was satisfied. Since the condition is + * accessible outside, this cannot be guaranteed though. + * + * @throws InterruptedException if the waiting thread was interrupted + * + * @see #wakeup + */ + public boolean await(final Date deadline) + throws InterruptedException { + + // This is only a sanity check. We cannot synchronize here, + // the lock would not be released on calling cond.await() below. + if (this.waiter != null) { + throw new IllegalStateException + ("A thread is already waiting on this object." + + "\ncaller: " + Thread.currentThread() + + "\nwaiter: " + this.waiter); + } + + if (aborted) { + throw new InterruptedException("Operation interrupted"); + } + + this.waiter = Thread.currentThread(); + + boolean success = false; + try { + if (deadline != null) { + success = this.cond.awaitUntil(deadline); + } else { + this.cond.await(); + success = true; + } + if (aborted) { + throw new InterruptedException("Operation interrupted"); + } + } finally { + this.waiter = null; + } + return success; + + } // await + + + /** + * Wakes up the waiting thread. + * <br/> + * This method assumes external synchronization. + */ + public void wakeup() { + + // If external synchronization and pooling works properly, + // this cannot happen. Just a sanity check. + if (this.waiter == null) { + throw new IllegalStateException + ("Nobody waiting on this object."); + } + + // One condition might be shared by several WaitingThread instances. + // It probably isn't, but just in case: wake all, not just one. + this.cond.signalAll(); + } + + public void interrupt() { + aborted = true; + this.cond.signalAll(); + } + + +} // class WaitingThread diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThreadAborter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThreadAborter.java new file mode 100644 index 000000000..7ad999996 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThreadAborter.java @@ -0,0 +1,69 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; + +/** + * A simple class that can interrupt a {@link WaitingThread}. + * + * Must be called with the pool lock held. + * + * @since 4.0 + * + * @deprecated (4.2) do not use + */ +@Deprecated +public class WaitingThreadAborter { + + private WaitingThread waitingThread; + private boolean aborted; + + /** + * If a waiting thread has been set, interrupts it. + */ + public void abort() { + aborted = true; + + if (waitingThread != null) { + waitingThread.interrupt(); + } + + } + + /** + * Sets the waiting thread. If this has already been aborted, + * the waiting thread is immediately interrupted. + * + * @param waitingThread The thread to interrupt when aborting. + */ + public void setWaitingThread(final WaitingThread waitingThread) { + this.waitingThread = waitingThread; + if (aborted) { + waitingThread.interrupt(); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/package-info.java new file mode 100644 index 000000000..5115d461f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/conn/tsccm/package-info.java @@ -0,0 +1,33 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Deprecated. + * + * @deprecated (4.3) + */ +package ch.boye.httpclientandroidlib.impl.conn.tsccm; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieAttributeHandler.java new file mode 100644 index 000000000..e9406bab2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieAttributeHandler.java @@ -0,0 +1,52 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; + +/** + * + * @since 4.0 + */ +@Immutable +public abstract class AbstractCookieAttributeHandler implements CookieAttributeHandler { + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + // Do nothing + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + // Always match + return true; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieSpec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieSpec.java new file mode 100644 index 000000000..7dc320ef4 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieSpec.java @@ -0,0 +1,104 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Abstract cookie specification which can delegate the job of parsing, + * validation or matching cookie attributes to a number of arbitrary + * {@link CookieAttributeHandler}s. + * + * + * @since 4.0 + */ +@NotThreadSafe // HashMap is not thread-safe +public abstract class AbstractCookieSpec implements CookieSpec { + + /** + * Stores attribute name -> attribute handler mappings + */ + private final Map<String, CookieAttributeHandler> attribHandlerMap; + + /** + * Default constructor + * */ + public AbstractCookieSpec() { + super(); + this.attribHandlerMap = new HashMap<String, CookieAttributeHandler>(10); + } + + public void registerAttribHandler( + final String name, final CookieAttributeHandler handler) { + Args.notNull(name, "Attribute name"); + Args.notNull(handler, "Attribute handler"); + this.attribHandlerMap.put(name, handler); + } + + /** + * Finds an attribute handler {@link CookieAttributeHandler} for the + * given attribute. Returns <tt>null</tt> if no attribute handler is + * found for the specified attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @return an attribute handler or <tt>null</tt> + */ + protected CookieAttributeHandler findAttribHandler(final String name) { + return this.attribHandlerMap.get(name); + } + + /** + * Gets attribute handler {@link CookieAttributeHandler} for the + * given attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @throws IllegalStateException if handler not found for the + * specified attribute. + */ + protected CookieAttributeHandler getAttribHandler(final String name) { + final CookieAttributeHandler handler = findAttribHandler(name); + if (handler == null) { + throw new IllegalStateException("Handler not registered for " + + name + " attribute."); + } else { + return handler; + } + } + + protected Collection<CookieAttributeHandler> getAttribHandlers() { + return this.attribHandlerMap.values(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie.java new file mode 100644 index 000000000..c826daada --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie.java @@ -0,0 +1,361 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Default implementation of {@link SetCookie}. + * + * @since 4.0 + */ +@NotThreadSafe +public class BasicClientCookie implements SetCookie, ClientCookie, Cloneable, Serializable { + + private static final long serialVersionUID = -3869795591041535538L; + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicClientCookie(final String name, final String value) { + super(); + Args.notNull(name, "Name"); + this.name = name; + this.attribs = new HashMap<String, String>(); + this.value = value; + } + + /** + * Returns the name. + * + * @return String name The name + */ + public String getName() { + return this.name; + } + + /** + * Returns the value. + * + * @return String value The current value. + */ + public String getValue() { + return this.value; + } + + /** + * Sets the value + * + * @param value + */ + public void setValue(final String value) { + this.value = value; + } + + /** + * Returns the comment describing the purpose of this cookie, or + * <tt>null</tt> if no such comment has been defined. + * + * @return comment + * + * @see #setComment(String) + */ + public String getComment() { + return cookieComment; + } + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described using this comment. + * + * @param comment + * + * @see #getComment() + */ + public void setComment(final String comment) { + cookieComment = comment; + } + + + /** + * Returns null. Cookies prior to RFC2965 do not set this attribute + */ + public String getCommentURL() { + return null; + } + + + /** + * Returns the expiration {@link Date} of the cookie, or <tt>null</tt> + * if none exists. + * <p><strong>Note:</strong> the object returned by this method is + * considered immutable. Changing it (e.g. using setTime()) could result + * in undefined behaviour. Do so at your peril. </p> + * @return Expiration {@link Date}, or <tt>null</tt>. + * + * @see #setExpiryDate(java.util.Date) + * + */ + public Date getExpiryDate() { + return cookieExpiryDate; + } + + /** + * Sets expiration date. + * <p><strong>Note:</strong> the object returned by this method is considered + * immutable. Changing it (e.g. using setTime()) could result in undefined + * behaviour. Do so at your peril.</p> + * + * @param expiryDate the {@link Date} after which this cookie is no longer valid. + * + * @see #getExpiryDate + * + */ + public void setExpiryDate (final Date expiryDate) { + cookieExpiryDate = expiryDate; + } + + + /** + * Returns <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise. + * + * @return <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise + */ + public boolean isPersistent() { + return (null != cookieExpiryDate); + } + + + /** + * Returns domain attribute of the cookie. + * + * @return the value of the domain attribute + * + * @see #setDomain(java.lang.String) + */ + public String getDomain() { + return cookieDomain; + } + + /** + * Sets the domain attribute. + * + * @param domain The value of the domain attribute + * + * @see #getDomain + */ + public void setDomain(final String domain) { + if (domain != null) { + cookieDomain = domain.toLowerCase(Locale.ENGLISH); + } else { + cookieDomain = null; + } + } + + + /** + * Returns the path attribute of the cookie + * + * @return The value of the path attribute. + * + * @see #setPath(java.lang.String) + */ + public String getPath() { + return cookiePath; + } + + /** + * Sets the path attribute. + * + * @param path The value of the path attribute + * + * @see #getPath + * + */ + public void setPath(final String path) { + cookiePath = path; + } + + /** + * @return <code>true</code> if this cookie should only be sent over secure connections. + * @see #setSecure(boolean) + */ + public boolean isSecure() { + return isSecure; + } + + /** + * Sets the secure attribute of the cookie. + * <p> + * When <tt>true</tt> the cookie should only be sent + * using a secure protocol (https). This should only be set when + * the cookie's originating server used a secure protocol to set the + * cookie's value. + * + * @param secure The value of the secure attribute + * + * @see #isSecure() + */ + public void setSecure (final boolean secure) { + isSecure = secure; + } + + + /** + * Returns null. Cookies prior to RFC2965 do not set this attribute + */ + public int[] getPorts() { + return null; + } + + + /** + * Returns the version of the cookie specification to which this + * cookie conforms. + * + * @return the version of the cookie. + * + * @see #setVersion(int) + * + */ + public int getVersion() { + return cookieVersion; + } + + /** + * Sets the version of the cookie specification to which this + * cookie conforms. + * + * @param version the version of the cookie. + * + * @see #getVersion + */ + public void setVersion(final int version) { + cookieVersion = version; + } + + /** + * Returns true if this cookie has expired. + * @param date Current time + * + * @return <tt>true</tt> if the cookie has expired. + */ + public boolean isExpired(final Date date) { + Args.notNull(date, "Date"); + return (cookieExpiryDate != null + && cookieExpiryDate.getTime() <= date.getTime()); + } + + public void setAttribute(final String name, final String value) { + this.attribs.put(name, value); + } + + public String getAttribute(final String name) { + return this.attribs.get(name); + } + + public boolean containsAttribute(final String name) { + return this.attribs.get(name) != null; + } + + @Override + public Object clone() throws CloneNotSupportedException { + final BasicClientCookie clone = (BasicClientCookie) super.clone(); + clone.attribs = new HashMap<String, String>(this.attribs); + return clone; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + buffer.append("[version: "); + buffer.append(Integer.toString(this.cookieVersion)); + buffer.append("]"); + buffer.append("[name: "); + buffer.append(this.name); + buffer.append("]"); + buffer.append("[value: "); + buffer.append(this.value); + buffer.append("]"); + buffer.append("[domain: "); + buffer.append(this.cookieDomain); + buffer.append("]"); + buffer.append("[path: "); + buffer.append(this.cookiePath); + buffer.append("]"); + buffer.append("[expiry: "); + buffer.append(this.cookieExpiryDate); + buffer.append("]"); + return buffer.toString(); + } + + // ----------------------------------------------------- Instance Variables + + /** Cookie name */ + private final String name; + + /** Cookie attributes as specified by the origin server */ + private Map<String, String> attribs; + + /** Cookie value */ + private String value; + + /** Comment attribute. */ + private String cookieComment; + + /** Domain attribute. */ + private String cookieDomain; + + /** Expiration {@link Date}. */ + private Date cookieExpiryDate; + + /** Path attribute. */ + private String cookiePath; + + /** My secure flag. */ + private boolean isSecure; + + /** The version of the cookie specification I was created from. */ + private int cookieVersion; + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie2.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie2.java new file mode 100644 index 000000000..7ed0f82b6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie2.java @@ -0,0 +1,101 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.SetCookie2; + +/** + * Default implementation of {@link SetCookie2}. + * + * @since 4.0 + */ +@NotThreadSafe +public class BasicClientCookie2 extends BasicClientCookie implements SetCookie2 { + + private static final long serialVersionUID = -7744598295706617057L; + + private String commentURL; + private int[] ports; + private boolean discard; + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicClientCookie2(final String name, final String value) { + super(name, value); + } + + @Override + public int[] getPorts() { + return this.ports; + } + + public void setPorts(final int[] ports) { + this.ports = ports; + } + + @Override + public String getCommentURL() { + return this.commentURL; + } + + public void setCommentURL(final String commentURL) { + this.commentURL = commentURL; + } + + public void setDiscard(final boolean discard) { + this.discard = discard; + } + + @Override + public boolean isPersistent() { + return !this.discard && super.isPersistent(); + } + + @Override + public boolean isExpired(final Date date) { + return this.discard || super.isExpired(date); + } + + @Override + public Object clone() throws CloneNotSupportedException { + final BasicClientCookie2 clone = (BasicClientCookie2) super.clone(); + if (this.ports != null) { + clone.ports = this.ports.clone(); + } + return clone; + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicCommentHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicCommentHandler.java new file mode 100644 index 000000000..69d65b961 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicCommentHandler.java @@ -0,0 +1,51 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class BasicCommentHandler extends AbstractCookieAttributeHandler { + + public BasicCommentHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + cookie.setComment(value); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicDomainHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicDomainHandler.java new file mode 100644 index 000000000..bad00ec48 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicDomainHandler.java @@ -0,0 +1,116 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class BasicDomainHandler implements CookieAttributeHandler { + + public BasicDomainHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException("Missing value for domain attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for domain attribute"); + } + cookie.setDomain(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + // Validate the cookies domain attribute. NOTE: Domains without + // any dots are allowed to support hosts on private LANs that don't + // have DNS names. Since they have no dots, to domain-match the + // request-host and domain must be identical for the cookie to sent + // back to the origin-server. + final String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + throw new CookieRestrictionViolationException("Cookie domain may not be null"); + } + if (host.contains(".")) { + // Not required to have at least two dots. RFC 2965. + // A Set-Cookie2 with Domain=ajax.com will be accepted. + + // domain must match host + if (!host.endsWith(domain)) { + if (domain.startsWith(".")) { + domain = domain.substring(1, domain.length()); + } + if (!host.equals(domain)) { + throw new CookieRestrictionViolationException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + } + } else { + if (!host.equals(domain)) { + throw new CookieRestrictionViolationException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + if (host.equals(domain)) { + return true; + } + if (!domain.startsWith(".")) { + domain = '.' + domain; + } + return host.endsWith(domain) || host.equals(domain.substring(1)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicExpiresHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicExpiresHandler.java new file mode 100644 index 000000000..9b9dee5b2 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicExpiresHandler.java @@ -0,0 +1,66 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class BasicExpiresHandler extends AbstractCookieAttributeHandler { + + /** Valid date patterns */ + private final String[] datepatterns; + + public BasicExpiresHandler(final String[] datepatterns) { + Args.notNull(datepatterns, "Array of date patterns"); + this.datepatterns = datepatterns; + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException("Missing value for expires attribute"); + } + final Date expiry = DateUtils.parseDate(value, this.datepatterns); + if (expiry == null) { + throw new MalformedCookieException("Unable to parse expires attribute: " + + value); + } + cookie.setExpiryDate(expiry); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicMaxAgeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicMaxAgeHandler.java new file mode 100644 index 000000000..750361b22 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicMaxAgeHandler.java @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Date; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class BasicMaxAgeHandler extends AbstractCookieAttributeHandler { + + public BasicMaxAgeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException("Missing value for max-age attribute"); + } + final int age; + try { + age = Integer.parseInt(value); + } catch (final NumberFormatException e) { + throw new MalformedCookieException ("Invalid max-age attribute: " + + value); + } + if (age < 0) { + throw new MalformedCookieException ("Negative max-age attribute: " + + value); + } + cookie.setExpiryDate(new Date(System.currentTimeMillis() + age * 1000L)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicPathHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicPathHandler.java new file mode 100644 index 000000000..e73657148 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicPathHandler.java @@ -0,0 +1,87 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.TextUtils; + +/** + * + * @since 4.0 + */ +@Immutable +public class BasicPathHandler implements CookieAttributeHandler { + + public BasicPathHandler() { + super(); + } + + public void parse( + final SetCookie cookie, final String value) throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + cookie.setPath(!TextUtils.isBlank(value) ? value : "/"); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (!match(cookie, origin)) { + throw new CookieRestrictionViolationException( + "Illegal path attribute \"" + cookie.getPath() + + "\". Path of origin: \"" + origin.getPath() + "\""); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final String targetpath = origin.getPath(); + String topmostPath = cookie.getPath(); + if (topmostPath == null) { + topmostPath = "/"; + } + if (topmostPath.length() > 1 && topmostPath.endsWith("/")) { + topmostPath = topmostPath.substring(0, topmostPath.length() - 1); + } + boolean match = targetpath.startsWith (topmostPath); + // if there is a match and these values are not exactly the same we have + // to make sure we're not matcing "/foobar" and "/foo" + if (match && targetpath.length() != topmostPath.length()) { + if (!topmostPath.endsWith("/")) { + match = (targetpath.charAt(topmostPath.length()) == '/'); + } + } + return match; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicSecureHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicSecureHandler.java new file mode 100644 index 000000000..939741fc3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BasicSecureHandler.java @@ -0,0 +1,60 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class BasicSecureHandler extends AbstractCookieAttributeHandler { + + public BasicSecureHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + cookie.setSecure(true); + } + + @Override + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + return !cookie.isSecure() || origin.isSecure(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpec.java new file mode 100644 index 000000000..bdb06090d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpec.java @@ -0,0 +1,207 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.List; + +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SM; +import ch.boye.httpclientandroidlib.cookie.SetCookie2; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * 'Meta' cookie specification that picks up a cookie policy based on + * the format of cookies sent with the HTTP response. + * + * @since 4.0 + */ +@NotThreadSafe // CookieSpec fields are @NotThreadSafe +public class BestMatchSpec implements CookieSpec { + + private final String[] datepatterns; + private final boolean oneHeader; + + // Cached values of CookieSpec instances + private RFC2965Spec strict; // @NotThreadSafe + private RFC2109Spec obsoleteStrict; // @NotThreadSafe + private BrowserCompatSpec compat; // @NotThreadSafe + + public BestMatchSpec(final String[] datepatterns, final boolean oneHeader) { + super(); + this.datepatterns = datepatterns == null ? null : datepatterns.clone(); + this.oneHeader = oneHeader; + } + + public BestMatchSpec() { + this(null, false); + } + + private RFC2965Spec getStrict() { + if (this.strict == null) { + this.strict = new RFC2965Spec(this.datepatterns, this.oneHeader); + } + return strict; + } + + private RFC2109Spec getObsoleteStrict() { + if (this.obsoleteStrict == null) { + this.obsoleteStrict = new RFC2109Spec(this.datepatterns, this.oneHeader); + } + return obsoleteStrict; + } + + private BrowserCompatSpec getCompat() { + if (this.compat == null) { + this.compat = new BrowserCompatSpec(this.datepatterns); + } + return compat; + } + + public List<Cookie> parse( + final Header header, + final CookieOrigin origin) throws MalformedCookieException { + Args.notNull(header, "Header"); + Args.notNull(origin, "Cookie origin"); + HeaderElement[] helems = header.getElements(); + boolean versioned = false; + boolean netscape = false; + for (final HeaderElement helem: helems) { + if (helem.getParameterByName("version") != null) { + versioned = true; + } + if (helem.getParameterByName("expires") != null) { + netscape = true; + } + } + if (netscape || !versioned) { + // Need to parse the header again, because Netscape style cookies do not correctly + // support multiple header elements (comma cannot be treated as an element separator) + final NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + final CharArrayBuffer buffer; + final ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + helems = new HeaderElement[] { parser.parseHeader(buffer, cursor) }; + return getCompat().parse(helems, origin); + } else { + if (SM.SET_COOKIE2.equals(header.getName())) { + return getStrict().parse(helems, origin); + } else { + return getObsoleteStrict().parse(helems, origin); + } + } + } + + public void validate( + final Cookie cookie, + final CookieOrigin origin) throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + if (cookie.getVersion() > 0) { + if (cookie instanceof SetCookie2) { + getStrict().validate(cookie, origin); + } else { + getObsoleteStrict().validate(cookie, origin); + } + } else { + getCompat().validate(cookie, origin); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + if (cookie.getVersion() > 0) { + if (cookie instanceof SetCookie2) { + return getStrict().match(cookie, origin); + } else { + return getObsoleteStrict().match(cookie, origin); + } + } else { + return getCompat().match(cookie, origin); + } + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + Args.notNull(cookies, "List of cookies"); + int version = Integer.MAX_VALUE; + boolean isSetCookie2 = true; + for (final Cookie cookie: cookies) { + if (!(cookie instanceof SetCookie2)) { + isSetCookie2 = false; + } + if (cookie.getVersion() < version) { + version = cookie.getVersion(); + } + } + if (version > 0) { + if (isSetCookie2) { + return getStrict().formatCookies(cookies); + } else { + return getObsoleteStrict().formatCookies(cookies); + } + } else { + return getCompat().formatCookies(cookies); + } + } + + public int getVersion() { + return getStrict().getVersion(); + } + + public Header getVersionHeader() { + return getStrict().getVersionHeader(); + } + + @Override + public String toString() { + return "best-match"; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpecFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpecFactory.java new file mode 100644 index 000000000..c20e4bcd1 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpecFactory.java @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.CookieSpecFactory; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link CookieSpecProvider} implementation that creates and initializes + * {@link BestMatchSpec} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class BestMatchSpecFactory implements CookieSpecFactory, CookieSpecProvider { + + private final String[] datepatterns; + private final boolean oneHeader; + + public BestMatchSpecFactory(final String[] datepatterns, final boolean oneHeader) { + super(); + this.datepatterns = datepatterns; + this.oneHeader = oneHeader; + } + + public BestMatchSpecFactory() { + this(null, false); + } + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + + String[] patterns = null; + final Collection<?> param = (Collection<?>) params.getParameter( + CookieSpecPNames.DATE_PATTERNS); + if (param != null) { + patterns = new String[param.size()]; + patterns = param.toArray(patterns); + } + final boolean singleHeader = params.getBooleanParameter( + CookieSpecPNames.SINGLE_COOKIE_HEADER, false); + + return new BestMatchSpec(patterns, singleHeader); + } else { + return new BestMatchSpec(); + } + } + + public CookieSpec create(final HttpContext context) { + return new BestMatchSpec(this.datepatterns, this.oneHeader); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpec.java new file mode 100644 index 000000000..6caa71987 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpec.java @@ -0,0 +1,219 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SM; +import ch.boye.httpclientandroidlib.message.BasicHeaderElement; +import ch.boye.httpclientandroidlib.message.BasicHeaderValueFormatter; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + + +/** + * Cookie specification that strives to closely mimic (mis)behavior of + * common web browser applications such as Microsoft Internet Explorer + * and Mozilla FireFox. + * + * + * @since 4.0 + */ +@NotThreadSafe // superclass is @NotThreadSafe +public class BrowserCompatSpec extends CookieSpecBase { + + + private static final String[] DEFAULT_DATE_PATTERNS = new String[] { + DateUtils.PATTERN_RFC1123, + DateUtils.PATTERN_RFC1036, + DateUtils.PATTERN_ASCTIME, + "EEE, dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MMM-yyyy HH-mm-ss z", + "EEE, dd MMM yy HH:mm:ss z", + "EEE dd-MMM-yyyy HH:mm:ss z", + "EEE dd MMM yyyy HH:mm:ss z", + "EEE dd-MMM-yyyy HH-mm-ss z", + "EEE dd-MMM-yy HH:mm:ss z", + "EEE dd MMM yy HH:mm:ss z", + "EEE,dd-MMM-yy HH:mm:ss z", + "EEE,dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MM-yyyy HH:mm:ss z", + }; + + private final String[] datepatterns; + + /** Default constructor */ + public BrowserCompatSpec(final String[] datepatterns, final BrowserCompatSpecFactory.SecurityLevel securityLevel) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = DEFAULT_DATE_PATTERNS; + } + switch (securityLevel) { + case SECURITYLEVEL_DEFAULT: + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + break; + case SECURITYLEVEL_IE_MEDIUM: + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler() { + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException { + // No validation + } + } + ); + break; + default: + throw new RuntimeException("Unknown security level"); + } + + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new BasicDomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + registerAttribHandler(ClientCookie.VERSION_ATTR, new BrowserCompatVersionAttributeHandler()); + } + + /** Default constructor */ + public BrowserCompatSpec(final String[] datepatterns) { + this(datepatterns, BrowserCompatSpecFactory.SecurityLevel.SECURITYLEVEL_DEFAULT); + } + + /** Default constructor */ + public BrowserCompatSpec() { + this(null, BrowserCompatSpecFactory.SecurityLevel.SECURITYLEVEL_DEFAULT); + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(header, "Header"); + Args.notNull(origin, "Cookie origin"); + final String headername = header.getName(); + if (!headername.equalsIgnoreCase(SM.SET_COOKIE)) { + throw new MalformedCookieException("Unrecognized cookie header '" + + header.toString() + "'"); + } + HeaderElement[] helems = header.getElements(); + boolean versioned = false; + boolean netscape = false; + for (final HeaderElement helem: helems) { + if (helem.getParameterByName("version") != null) { + versioned = true; + } + if (helem.getParameterByName("expires") != null) { + netscape = true; + } + } + if (netscape || !versioned) { + // Need to parse the header again, because Netscape style cookies do not correctly + // support multiple header elements (comma cannot be treated as an element separator) + final NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + final CharArrayBuffer buffer; + final ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + helems = new HeaderElement[] { parser.parseHeader(buffer, cursor) }; + } + return parse(helems, origin); + } + + private static boolean isQuoteEnclosed(final String s) { + return s != null && s.startsWith("\"") && s.endsWith("\""); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + Args.notEmpty(cookies, "List of cookies"); + final CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + for (int i = 0; i < cookies.size(); i++) { + final Cookie cookie = cookies.get(i); + if (i > 0) { + buffer.append("; "); + } + final String cookieName = cookie.getName(); + final String cookieValue = cookie.getValue(); + if (cookie.getVersion() > 0 && !isQuoteEnclosed(cookieValue)) { + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement( + buffer, + new BasicHeaderElement(cookieName, cookieValue), + false); + } else { + // Netscape style cookies do not support quoted values + buffer.append(cookieName); + buffer.append("="); + if (cookieValue != null) { + buffer.append(cookieValue); + } + } + } + final List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + public int getVersion() { + return 0; + } + + public Header getVersionHeader() { + return null; + } + + @Override + public String toString() { + return "compatibility"; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpecFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpecFactory.java new file mode 100644 index 000000000..f6239b4f3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpecFactory.java @@ -0,0 +1,92 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.CookieSpecFactory; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link CookieSpecProvider} implementation that creates and initializes + * {@link BrowserCompatSpec} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class BrowserCompatSpecFactory implements CookieSpecFactory, CookieSpecProvider { + + public enum SecurityLevel { + SECURITYLEVEL_DEFAULT, + SECURITYLEVEL_IE_MEDIUM + } + + private final String[] datepatterns; + private final SecurityLevel securityLevel; + + public BrowserCompatSpecFactory(final String[] datepatterns, final SecurityLevel securityLevel) { + super(); + this.datepatterns = datepatterns; + this.securityLevel = securityLevel; + } + + public BrowserCompatSpecFactory(final String[] datepatterns) { + this(null, SecurityLevel.SECURITYLEVEL_DEFAULT); + } + + public BrowserCompatSpecFactory() { + this(null, SecurityLevel.SECURITYLEVEL_DEFAULT); + } + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + + String[] patterns = null; + final Collection<?> param = (Collection<?>) params.getParameter( + CookieSpecPNames.DATE_PATTERNS); + if (param != null) { + patterns = new String[param.size()]; + patterns = param.toArray(patterns); + } + return new BrowserCompatSpec(patterns, securityLevel); + } else { + return new BrowserCompatSpec(null, securityLevel); + } + } + + public CookieSpec create(final HttpContext context) { + return new BrowserCompatSpec(this.datepatterns); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatVersionAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatVersionAttributeHandler.java new file mode 100644 index 000000000..210fac962 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatVersionAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <tt>"Version"</tt> cookie attribute handler for BrowserCompat cookie spec. + * + * @since 4.3 + */ +@Immutable +public class BrowserCompatVersionAttributeHandler extends + AbstractCookieAttributeHandler { + + public BrowserCompatVersionAttributeHandler() { + super(); + } + + /** + * Parse cookie version attribute. + */ + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException("Missing value for version attribute"); + } + int version = 0; + try { + version = Integer.parseInt(value); + } catch (final NumberFormatException e) { + // Just ignore invalid versions + } + cookie.setVersion(version); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/CookieSpecBase.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/CookieSpecBase.java new file mode 100644 index 000000000..e59c3686e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/CookieSpecBase.java @@ -0,0 +1,121 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.NameValuePair; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Cookie management functions shared by all specification. + * + * + * @since 4.0 + */ +@NotThreadSafe // AbstractCookieSpec is not thread-safe +public abstract class CookieSpecBase extends AbstractCookieSpec { + + protected static String getDefaultPath(final CookieOrigin origin) { + String defaultPath = origin.getPath(); + int lastSlashIndex = defaultPath.lastIndexOf('/'); + if (lastSlashIndex >= 0) { + if (lastSlashIndex == 0) { + //Do not remove the very first slash + lastSlashIndex = 1; + } + defaultPath = defaultPath.substring(0, lastSlashIndex); + } + return defaultPath; + } + + protected static String getDefaultDomain(final CookieOrigin origin) { + return origin.getHost(); + } + + protected List<Cookie> parse(final HeaderElement[] elems, final CookieOrigin origin) + throws MalformedCookieException { + final List<Cookie> cookies = new ArrayList<Cookie>(elems.length); + for (final HeaderElement headerelement : elems) { + final String name = headerelement.getName(); + final String value = headerelement.getValue(); + if (name == null || name.length() == 0) { + throw new MalformedCookieException("Cookie name may not be empty"); + } + + final BasicClientCookie cookie = new BasicClientCookie(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + + // cycle through the parameters + final NameValuePair[] attribs = headerelement.getParameters(); + for (int j = attribs.length - 1; j >= 0; j--) { + final NameValuePair attrib = attribs[j]; + final String s = attrib.getName().toLowerCase(Locale.ENGLISH); + + cookie.setAttribute(s, attrib.getValue()); + + final CookieAttributeHandler handler = findAttribHandler(s); + if (handler != null) { + handler.parse(cookie, attrib.getValue()); + } + } + cookies.add(cookie); + } + return cookies; + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + for (final CookieAttributeHandler handler: getAttribHandlers()) { + handler.validate(cookie, origin); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + for (final CookieAttributeHandler handler: getAttribHandlers()) { + if (!handler.match(cookie, origin)) { + return false; + } + } + return true; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/DateParseException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/DateParseException.java new file mode 100644 index 000000000..08b387272 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/DateParseException.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * An exception to indicate an error parsing a date string. + * + * @see DateUtils + * + * + * @since 4.0 + * + * @deprecated (4.3) no longer used. + */ +@Deprecated +@Immutable +public class DateParseException extends Exception { + + private static final long serialVersionUID = 4417696455000643370L; + + /** + * + */ + public DateParseException() { + super(); + } + + /** + * @param message the exception message + */ + public DateParseException(final String message) { + super(message); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/DateUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/DateUtils.java new file mode 100644 index 000000000..897d6fb00 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/DateUtils.java @@ -0,0 +1,156 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Date; +import java.util.TimeZone; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * A utility class for parsing and formatting HTTP dates as used in cookies and + * other headers. This class handles dates as defined by RFC 2616 section + * 3.3.1 as well as some other common non-standard formats. + * + * + * @since 4.0 + * + * @deprecated (4.3) Use {@link ch.boye.httpclientandroidlib.client.utils.DateUtils}. + */ +@Deprecated +@Immutable +public final class DateUtils { + + /** + * Date format pattern used to parse HTTP date headers in RFC 1123 format. + */ + public static final String PATTERN_RFC1123 = ch.boye.httpclientandroidlib.client.utils.DateUtils.PATTERN_RFC1123; + + /** + * Date format pattern used to parse HTTP date headers in RFC 1036 format. + */ + public static final String PATTERN_RFC1036 = ch.boye.httpclientandroidlib.client.utils.DateUtils.PATTERN_RFC1036; + + /** + * Date format pattern used to parse HTTP date headers in ANSI C + * <code>asctime()</code> format. + */ + public static final String PATTERN_ASCTIME = ch.boye.httpclientandroidlib.client.utils.DateUtils.PATTERN_ASCTIME; + + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + /** + * Parses a date value. The formats used for parsing the date value are retrieved from + * the default http params. + * + * @param dateValue the date value to parse + * + * @return the parsed date + * + * @throws DateParseException if the value could not be parsed using any of the + * supported date formats + */ + public static Date parseDate(final String dateValue) throws DateParseException { + return parseDate(dateValue, null, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * + * @return the parsed date + * + * @throws DateParseException if none of the dataFormats could parse the dateValue + */ + public static Date parseDate(final String dateValue, final String[] dateFormats) + throws DateParseException { + return parseDate(dateValue, dateFormats, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * @param startDate During parsing, two digit years will be placed in the range + * <code>startDate</code> to <code>startDate + 100 years</code>. This value may + * be <code>null</code>. When <code>null</code> is given as a parameter, year + * <code>2000</code> will be used. + * + * @return the parsed date + * + * @throws DateParseException if none of the dataFormats could parse the dateValue + */ + public static Date parseDate( + final String dateValue, + final String[] dateFormats, + final Date startDate + ) throws DateParseException { + final Date d = ch.boye.httpclientandroidlib.client.utils.DateUtils.parseDate(dateValue, dateFormats, startDate); + if (d == null) { + throw new DateParseException("Unable to parse the date " + dateValue); + } + return d; + } + + /** + * Formats the given date according to the RFC 1123 pattern. + * + * @param date The date to format. + * @return An RFC 1123 formatted date string. + * + * @see #PATTERN_RFC1123 + */ + public static String formatDate(final Date date) { + return ch.boye.httpclientandroidlib.client.utils.DateUtils.formatDate(date); + } + + /** + * Formats the given date according to the specified pattern. The pattern + * must conform to that used by the {@link java.text.SimpleDateFormat simple + * date format} class. + * + * @param date The date to format. + * @param pattern The pattern to use for formatting the date. + * @return A formatted date string. + * + * @throws IllegalArgumentException If the given date pattern is invalid. + * + * @see java.text.SimpleDateFormat + */ + public static String formatDate(final Date date, final String pattern) { + return ch.boye.httpclientandroidlib.client.utils.DateUtils.formatDate(date, pattern); + } + + /** This class should not be instantiated. */ + private DateUtils() { + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpec.java new file mode 100644 index 000000000..3aa350a40 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpec.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collections; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; + +/** + * CookieSpec that ignores all cookies + * + * @since 4.1 + */ +@NotThreadSafe // superclass is @NotThreadSafe +public class IgnoreSpec extends CookieSpecBase { + + public int getVersion() { + return 0; + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + return Collections.emptyList(); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + return Collections.emptyList(); + } + + public Header getVersionHeader() { + return null; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpecFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpecFactory.java new file mode 100644 index 000000000..426e25cad --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpecFactory.java @@ -0,0 +1,58 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.CookieSpecFactory; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link CookieSpecProvider} implementation that ignores all cookies. + * + * @since 4.1 + */ +@Immutable +@SuppressWarnings("deprecation") +public class IgnoreSpecFactory implements CookieSpecFactory, CookieSpecProvider { + + public IgnoreSpecFactory() { + super(); + } + + public CookieSpec newInstance(final HttpParams params) { + return new IgnoreSpec(); + } + + public CookieSpec create(final HttpContext context) { + return new IgnoreSpec(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDomainHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDomainHandler.java new file mode 100644 index 000000000..adbd40e91 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDomainHandler.java @@ -0,0 +1,106 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Locale; +import java.util.StringTokenizer; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class NetscapeDomainHandler extends BasicDomainHandler { + + public NetscapeDomainHandler() { + super(); + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + super.validate(cookie, origin); + // Perform Netscape Cookie draft specific validation + final String host = origin.getHost(); + final String domain = cookie.getDomain(); + if (host.contains(".")) { + final int domainParts = new StringTokenizer(domain, ".").countTokens(); + + if (isSpecialDomain(domain)) { + if (domainParts < 2) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + domain + + "\" violates the Netscape cookie specification for " + + "special domains"); + } + } else { + if (domainParts < 3) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + domain + + "\" violates the Netscape cookie specification"); + } + } + } + } + + /** + * Checks if the given domain is in one of the seven special + * top level domains defined by the Netscape cookie specification. + * @param domain The domain. + * @return True if the specified domain is "special" + */ + private static boolean isSpecialDomain(final String domain) { + final String ucDomain = domain.toUpperCase(Locale.ENGLISH); + return ucDomain.endsWith(".COM") + || ucDomain.endsWith(".EDU") + || ucDomain.endsWith(".NET") + || ucDomain.endsWith(".GOV") + || ucDomain.endsWith(".MIL") + || ucDomain.endsWith(".ORG") + || ucDomain.endsWith(".INT"); + } + + @Override + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final String host = origin.getHost(); + final String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + return host.endsWith(domain); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftHeaderParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftHeaderParser.java new file mode 100644 index 000000000..89a59688f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftHeaderParser.java @@ -0,0 +1,138 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.NameValuePair; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.message.BasicHeaderElement; +import ch.boye.httpclientandroidlib.message.BasicNameValuePair; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * + * @since 4.0 + */ +@Immutable +public class NetscapeDraftHeaderParser { + + public final static NetscapeDraftHeaderParser DEFAULT = new NetscapeDraftHeaderParser(); + + public NetscapeDraftHeaderParser() { + super(); + } + + public HeaderElement parseHeader( + final CharArrayBuffer buffer, + final ParserCursor cursor) throws ParseException { + Args.notNull(buffer, "Char array buffer"); + Args.notNull(cursor, "Parser cursor"); + final NameValuePair nvp = parseNameValuePair(buffer, cursor); + final List<NameValuePair> params = new ArrayList<NameValuePair>(); + while (!cursor.atEnd()) { + final NameValuePair param = parseNameValuePair(buffer, cursor); + params.add(param); + } + return new BasicHeaderElement( + nvp.getName(), + nvp.getValue(), params.toArray(new NameValuePair[params.size()])); + } + + private NameValuePair parseNameValuePair( + final CharArrayBuffer buffer, final ParserCursor cursor) { + boolean terminated = false; + + int pos = cursor.getPos(); + final int indexFrom = cursor.getPos(); + final int indexTo = cursor.getUpperBound(); + + // Find name + String name = null; + while (pos < indexTo) { + final char ch = buffer.charAt(pos); + if (ch == '=') { + break; + } + if (ch == ';') { + terminated = true; + break; + } + pos++; + } + + if (pos == indexTo) { + terminated = true; + name = buffer.substringTrimmed(indexFrom, indexTo); + } else { + name = buffer.substringTrimmed(indexFrom, pos); + pos++; + } + + if (terminated) { + cursor.updatePos(pos); + return new BasicNameValuePair(name, null); + } + + // Find value + String value = null; + int i1 = pos; + + while (pos < indexTo) { + final char ch = buffer.charAt(pos); + if (ch == ';') { + terminated = true; + break; + } + pos++; + } + + int i2 = pos; + // Trim leading white spaces + while (i1 < i2 && (HTTP.isWhitespace(buffer.charAt(i1)))) { + i1++; + } + // Trim trailing white spaces + while ((i2 > i1) && (HTTP.isWhitespace(buffer.charAt(i2 - 1)))) { + i2--; + } + value = buffer.substring(i1, i2); + if (terminated) { + pos++; + } + cursor.updatePos(pos); + return new BasicNameValuePair(name, value); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpec.java new file mode 100644 index 000000000..139e5985c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpec.java @@ -0,0 +1,171 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import ch.boye.httpclientandroidlib.FormattedHeader; +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SM; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * This {@link ch.boye.httpclientandroidlib.cookie.CookieSpec} implementation conforms to + * the original draft specification published by Netscape Communications. + * It should be avoided unless absolutely necessary for compatibility with + * legacy applications. + * + * @since 4.0 + */ +@NotThreadSafe // superclass is @NotThreadSafe +public class NetscapeDraftSpec extends CookieSpecBase { + + protected static final String EXPIRES_PATTERN = "EEE, dd-MMM-yy HH:mm:ss z"; + + private final String[] datepatterns; + + /** Default constructor */ + public NetscapeDraftSpec(final String[] datepatterns) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = new String[] { EXPIRES_PATTERN }; + } + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new NetscapeDomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public NetscapeDraftSpec() { + this(null); + } + + /** + * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s. + * + * <p>Syntax of the Set-Cookie HTTP Response Header:</p> + * + * <p>This is the format a CGI script would use to add to + * the HTTP headers a new piece of data which is to be stored by + * the client for later retrieval.</p> + * + * <PRE> + * Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure + * </PRE> + * + * <p>Please note that the Netscape draft specification does not fully conform to the HTTP + * header format. Comma character if present in <code>Set-Cookie</code> will not be treated + * as a header element separator</p> + * + * @see <a href="http://web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec.html"> + * The Cookie Spec.</a> + * + * @param header the <tt>Set-Cookie</tt> received from the server + * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value + * @throws MalformedCookieException if an exception occurs during parsing + */ + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(header, "Header"); + Args.notNull(origin, "Cookie origin"); + if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) { + throw new MalformedCookieException("Unrecognized cookie header '" + + header.toString() + "'"); + } + final NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + final CharArrayBuffer buffer; + final ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + final String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + return parse(new HeaderElement[] { parser.parseHeader(buffer, cursor) }, origin); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + Args.notEmpty(cookies, "List of cookies"); + final CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + for (int i = 0; i < cookies.size(); i++) { + final Cookie cookie = cookies.get(i); + if (i > 0) { + buffer.append("; "); + } + buffer.append(cookie.getName()); + final String s = cookie.getValue(); + if (s != null) { + buffer.append("="); + buffer.append(s); + } + } + final List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + public int getVersion() { + return 0; + } + + public Header getVersionHeader() { + return null; + } + + @Override + public String toString() { + return "netscape"; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpecFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpecFactory.java new file mode 100644 index 000000000..d1187a150 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpecFactory.java @@ -0,0 +1,81 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.CookieSpecFactory; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link CookieSpecProvider} implementation that creates and initializes + * {@link NetscapeDraftSpec} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class NetscapeDraftSpecFactory implements CookieSpecFactory, CookieSpecProvider { + + private final String[] datepatterns; + + public NetscapeDraftSpecFactory(final String[] datepatterns) { + super(); + this.datepatterns = datepatterns; + } + + public NetscapeDraftSpecFactory() { + this(null); + } + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + + String[] patterns = null; + final Collection<?> param = (Collection<?>) params.getParameter( + CookieSpecPNames.DATE_PATTERNS); + if (param != null) { + patterns = new String[param.size()]; + patterns = param.toArray(patterns); + } + return new NetscapeDraftSpec(patterns); + } else { + return new NetscapeDraftSpec(); + } + } + + public CookieSpec create(final HttpContext context) { + return new NetscapeDraftSpec(this.datepatterns); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixFilter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixFilter.java new file mode 100644 index 000000000..4777918ba --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixFilter.java @@ -0,0 +1,132 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import ch.boye.httpclientandroidlib.client.utils.Punycode; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; + +/** + * Wraps a CookieAttributeHandler and leverages its match method + * to never match a suffix from a black list. May be used to provide + * additional security for cross-site attack types by preventing + * cookies from apparent domains that are not publicly available. + * An uptodate list of suffixes can be obtained from + * <a href="http://publicsuffix.org/">publicsuffix.org</a> + * + * @since 4.0 + */ +public class PublicSuffixFilter implements CookieAttributeHandler { + private final CookieAttributeHandler wrapped; + private Set<String> exceptions; + private Set<String> suffixes; + + public PublicSuffixFilter(final CookieAttributeHandler wrapped) { + this.wrapped = wrapped; + } + + /** + * Sets the suffix blacklist patterns. + * A pattern can be "com", "*.jp" + * TODO add support for patterns like "lib.*.us" + * @param suffixes + */ + public void setPublicSuffixes(final Collection<String> suffixes) { + this.suffixes = new HashSet<String>(suffixes); + } + + /** + * Sets the exceptions from the blacklist. Exceptions can not be patterns. + * TODO add support for patterns + * @param exceptions + */ + public void setExceptions(final Collection<String> exceptions) { + this.exceptions = new HashSet<String>(exceptions); + } + + /** + * Never matches if the cookie's domain is from the blacklist. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (isForPublicSuffix(cookie)) { + return false; + } + return wrapped.match(cookie, origin); + } + + public void parse(final SetCookie cookie, final String value) throws MalformedCookieException { + wrapped.parse(cookie, value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException { + wrapped.validate(cookie, origin); + } + + private boolean isForPublicSuffix(final Cookie cookie) { + String domain = cookie.getDomain(); + if (domain.startsWith(".")) { + domain = domain.substring(1); + } + domain = Punycode.toUnicode(domain); + + // An exception rule takes priority over any other matching rule. + if (this.exceptions != null) { + if (this.exceptions.contains(domain)) { + return false; + } + } + + + if (this.suffixes == null) { + return false; + } + + do { + if (this.suffixes.contains(domain)) { + return true; + } + // patterns + if (domain.startsWith("*.")) { + domain = domain.substring(2); + } + final int nextdot = domain.indexOf('.'); + if (nextdot == -1) { + break; + } + domain = "*" + domain.substring(nextdot); + } while (domain.length() > 0); + + return false; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixListParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixListParser.java new file mode 100644 index 000000000..4f0374f99 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixListParser.java @@ -0,0 +1,127 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Parses the list from <a href="http://publicsuffix.org/">publicsuffix.org</a> + * and configures a PublicSuffixFilter. + * + * @since 4.0 + */ +@Immutable +public class PublicSuffixListParser { + private static final int MAX_LINE_LEN = 256; + private final PublicSuffixFilter filter; + + PublicSuffixListParser(final PublicSuffixFilter filter) { + this.filter = filter; + } + + /** + * Parses the public suffix list format. + * When creating the reader from the file, make sure to + * use the correct encoding (the original list is in UTF-8). + * + * @param list the suffix list. The caller is responsible for closing the reader. + * @throws IOException on error while reading from list + */ + public void parse(final Reader list) throws IOException { + final Collection<String> rules = new ArrayList<String>(); + final Collection<String> exceptions = new ArrayList<String>(); + final BufferedReader r = new BufferedReader(list); + final StringBuilder sb = new StringBuilder(256); + boolean more = true; + while (more) { + more = readLine(r, sb); + String line = sb.toString(); + if (line.length() == 0) { + continue; + } + if (line.startsWith("//")) + { + continue; //entire lines can also be commented using // + } + if (line.startsWith(".")) + { + line = line.substring(1); // A leading dot is optional + } + // An exclamation mark (!) at the start of a rule marks an exception to a previous wildcard rule + final boolean isException = line.startsWith("!"); + if (isException) { + line = line.substring(1); + } + + if (isException) { + exceptions.add(line); + } else { + rules.add(line); + } + } + + filter.setPublicSuffixes(rules); + filter.setExceptions(exceptions); + } + + /** + * + * @param r + * @param sb + * @return false when the end of the stream is reached + * @throws IOException + */ + private boolean readLine(final Reader r, final StringBuilder sb) throws IOException { + sb.setLength(0); + int b; + boolean hitWhitespace = false; + while ((b = r.read()) != -1) { + final char c = (char) b; + if (c == '\n') { + break; + } + // Each line is only read up to the first whitespace + if (Character.isWhitespace(c)) { + hitWhitespace = true; + } + if (!hitWhitespace) { + sb.append(c); + } + if (sb.length() > MAX_LINE_LEN) + { + throw new IOException("Line too long"); // prevent excess memory usage + } + } + return (b != -1); + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109DomainHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109DomainHandler.java new file mode 100644 index 000000000..d669dcb43 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109DomainHandler.java @@ -0,0 +1,120 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class RFC2109DomainHandler implements CookieAttributeHandler { + + public RFC2109DomainHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException("Missing value for domain attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for domain attribute"); + } + cookie.setDomain(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + String host = origin.getHost(); + final String domain = cookie.getDomain(); + if (domain == null) { + throw new CookieRestrictionViolationException("Cookie domain may not be null"); + } + if (!domain.equals(host)) { + int dotIndex = domain.indexOf('.'); + if (dotIndex == -1) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + domain + + "\" does not match the host \"" + + host + "\""); + } + // domain must start with dot + if (!domain.startsWith(".")) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + domain + + "\" violates RFC 2109: domain must start with a dot"); + } + // domain must have at least one embedded dot + dotIndex = domain.indexOf('.', 1); + if (dotIndex < 0 || dotIndex == domain.length() - 1) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + domain + + "\" violates RFC 2109: domain must contain an embedded dot"); + } + host = host.toLowerCase(Locale.ENGLISH); + if (!host.endsWith(domain)) { + throw new CookieRestrictionViolationException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + // host minus domain may not contain any dots + final String hostWithoutDomain = host.substring(0, host.length() - domain.length()); + if (hostWithoutDomain.indexOf('.') != -1) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + domain + + "\" violates RFC 2109: host minus domain may not contain any dots"); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final String host = origin.getHost(); + final String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + return host.equals(domain) || (domain.startsWith(".") && host.endsWith(domain)); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109Spec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109Spec.java new file mode 100644 index 000000000..1292788a0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109Spec.java @@ -0,0 +1,240 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.utils.DateUtils; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookiePathComparator; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SM; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * RFC 2109 compliant {@link ch.boye.httpclientandroidlib.cookie.CookieSpec} implementation. + * This is an older version of the official HTTP state management specification + * superseded by RFC 2965. + * + * @see RFC2965Spec + * + * @since 4.0 + */ +@NotThreadSafe // superclass is @NotThreadSafe +public class RFC2109Spec extends CookieSpecBase { + + private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator(); + + private final static String[] DATE_PATTERNS = { + DateUtils.PATTERN_RFC1123, + DateUtils.PATTERN_RFC1036, + DateUtils.PATTERN_ASCTIME + }; + + private final String[] datepatterns; + private final boolean oneHeader; + + /** Default constructor */ + public RFC2109Spec(final String[] datepatterns, final boolean oneHeader) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = DATE_PATTERNS; + } + this.oneHeader = oneHeader; + registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2109VersionHandler()); + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2109DomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public RFC2109Spec() { + this(null, false); + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(header, "Header"); + Args.notNull(origin, "Cookie origin"); + if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE)) { + throw new MalformedCookieException("Unrecognized cookie header '" + + header.toString() + "'"); + } + final HeaderElement[] elems = header.getElements(); + return parse(elems, origin); + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + final String name = cookie.getName(); + if (name.indexOf(' ') != -1) { + throw new CookieRestrictionViolationException("Cookie name may not contain blanks"); + } + if (name.startsWith("$")) { + throw new CookieRestrictionViolationException("Cookie name may not start with $"); + } + super.validate(cookie, origin); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + Args.notEmpty(cookies, "List of cookies"); + List<Cookie> cookieList; + if (cookies.size() > 1) { + // Create a mutable copy and sort the copy. + cookieList = new ArrayList<Cookie>(cookies); + Collections.sort(cookieList, PATH_COMPARATOR); + } else { + cookieList = cookies; + } + if (this.oneHeader) { + return doFormatOneHeader(cookieList); + } else { + return doFormatManyHeaders(cookieList); + } + } + + private List<Header> doFormatOneHeader(final List<Cookie> cookies) { + int version = Integer.MAX_VALUE; + // Pick the lowest common denominator + for (final Cookie cookie : cookies) { + if (cookie.getVersion() < version) { + version = cookie.getVersion(); + } + } + final CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + buffer.append("$Version="); + buffer.append(Integer.toString(version)); + for (final Cookie cooky : cookies) { + buffer.append("; "); + final Cookie cookie = cooky; + formatCookieAsVer(buffer, cookie, version); + } + final List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + private List<Header> doFormatManyHeaders(final List<Cookie> cookies) { + final List<Header> headers = new ArrayList<Header>(cookies.size()); + for (final Cookie cookie : cookies) { + final int version = cookie.getVersion(); + final CharArrayBuffer buffer = new CharArrayBuffer(40); + buffer.append("Cookie: "); + buffer.append("$Version="); + buffer.append(Integer.toString(version)); + buffer.append("; "); + formatCookieAsVer(buffer, cookie, version); + headers.add(new BufferedHeader(buffer)); + } + return headers; + } + + /** + * Return a name/value string suitable for sending in a <tt>"Cookie"</tt> + * header as defined in RFC 2109 for backward compatibility with cookie + * version 0 + * @param buffer The char array buffer to use for output + * @param name The cookie name + * @param value The cookie value + * @param version The cookie version + */ + protected void formatParamAsVer(final CharArrayBuffer buffer, + final String name, final String value, final int version) { + buffer.append(name); + buffer.append("="); + if (value != null) { + if (version > 0) { + buffer.append('\"'); + buffer.append(value); + buffer.append('\"'); + } else { + buffer.append(value); + } + } + } + + /** + * Return a string suitable for sending in a <tt>"Cookie"</tt> header + * as defined in RFC 2109 for backward compatibility with cookie version 0 + * @param buffer The char array buffer to use for output + * @param cookie The {@link Cookie} to be formatted as string + * @param version The version to use. + */ + protected void formatCookieAsVer(final CharArrayBuffer buffer, + final Cookie cookie, final int version) { + formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version); + if (cookie.getPath() != null) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) { + buffer.append("; "); + formatParamAsVer(buffer, "$Path", cookie.getPath(), version); + } + } + if (cookie.getDomain() != null) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + buffer.append("; "); + formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version); + } + } + } + + public int getVersion() { + return 1; + } + + public Header getVersionHeader() { + return null; + } + + @Override + public String toString() { + return "rfc2109"; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109SpecFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109SpecFactory.java new file mode 100644 index 000000000..24802c351 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109SpecFactory.java @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.CookieSpecFactory; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link CookieSpecProvider} implementation that creates and initializes + * {@link RFC2109Spec} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class RFC2109SpecFactory implements CookieSpecFactory, CookieSpecProvider { + + private final String[] datepatterns; + private final boolean oneHeader; + + public RFC2109SpecFactory(final String[] datepatterns, final boolean oneHeader) { + super(); + this.datepatterns = datepatterns; + this.oneHeader = oneHeader; + } + + public RFC2109SpecFactory() { + this(null, false); + } + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + + String[] patterns = null; + final Collection<?> param = (Collection<?>) params.getParameter( + CookieSpecPNames.DATE_PATTERNS); + if (param != null) { + patterns = new String[param.size()]; + patterns = param.toArray(patterns); + } + final boolean singleHeader = params.getBooleanParameter( + CookieSpecPNames.SINGLE_COOKIE_HEADER, false); + + return new RFC2109Spec(patterns, singleHeader); + } else { + return new RFC2109Spec(); + } + } + + public CookieSpec create(final HttpContext context) { + return new RFC2109Spec(this.datepatterns, this.oneHeader); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109VersionHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109VersionHandler.java new file mode 100644 index 000000000..a43fe9b22 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2109VersionHandler.java @@ -0,0 +1,74 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * + * @since 4.0 + */ +@Immutable +public class RFC2109VersionHandler extends AbstractCookieAttributeHandler { + + public RFC2109VersionHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException("Missing value for version attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for version attribute"); + } + try { + cookie.setVersion(Integer.parseInt(value)); + } catch (final NumberFormatException e) { + throw new MalformedCookieException("Invalid version: " + + e.getMessage()); + } + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (cookie.getVersion() < 0) { + throw new CookieRestrictionViolationException("Cookie version may not be negative"); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965CommentUrlAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965CommentUrlAttributeHandler.java new file mode 100644 index 000000000..10f5f40ec --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965CommentUrlAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.cookie.SetCookie2; + +/** + * <tt>"CommentURL"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * @since 4.0 + */ +@Immutable +public class RFC2965CommentUrlAttributeHandler implements CookieAttributeHandler { + + public RFC2965CommentUrlAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + final SetCookie2 cookie2 = (SetCookie2) cookie; + cookie2.setCommentURL(commenturl); + } + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + + } diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965DiscardAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965DiscardAttributeHandler.java new file mode 100644 index 000000000..c4bfca460 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965DiscardAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.cookie.SetCookie2; + +/** + * <tt>"Discard"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * @since 4.0 + */ +@Immutable +public class RFC2965DiscardAttributeHandler implements CookieAttributeHandler { + + public RFC2965DiscardAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + final SetCookie2 cookie2 = (SetCookie2) cookie; + cookie2.setDiscard(true); + } + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + + } diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965DomainAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965DomainAttributeHandler.java new file mode 100644 index 000000000..62c947c55 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965DomainAttributeHandler.java @@ -0,0 +1,185 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Locale; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * + * @since 3.1 + */ +@Immutable +public class RFC2965DomainAttributeHandler implements CookieAttributeHandler { + + public RFC2965DomainAttributeHandler() { + super(); + } + + /** + * Parse cookie domain attribute. + */ + public void parse( + final SetCookie cookie, final String domain) throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (domain == null) { + throw new MalformedCookieException( + "Missing value for domain attribute"); + } + if (domain.trim().length() == 0) { + throw new MalformedCookieException( + "Blank value for domain attribute"); + } + String s = domain; + s = s.toLowerCase(Locale.ENGLISH); + if (!domain.startsWith(".")) { + // Per RFC 2965 section 3.2.2 + // "... If an explicitly specified value does not start with + // a dot, the user agent supplies a leading dot ..." + // That effectively implies that the domain attribute + // MAY NOT be an IP address of a host name + s = '.' + s; + } + cookie.setDomain(s); + } + + /** + * Performs domain-match as defined by the RFC2965. + * <p> + * Host A's name domain-matches host B's if + * <ol> + * <ul>their host name strings string-compare equal; or</ul> + * <ul>A is a HDN string and has the form NB, where N is a non-empty + * name string, B has the form .B', and B' is a HDN string. (So, + * x.y.com domain-matches .Y.com but not Y.com.)</ul> + * </ol> + * + * @param host host name where cookie is received from or being sent to. + * @param domain The cookie domain attribute. + * @return true if the specified host matches the given domain. + */ + public boolean domainMatch(final String host, final String domain) { + final boolean match = host.equals(domain) + || (domain.startsWith(".") && host.endsWith(domain)); + + return match; + } + + /** + * Validate cookie domain attribute. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final String host = origin.getHost().toLowerCase(Locale.ENGLISH); + if (cookie.getDomain() == null) { + throw new CookieRestrictionViolationException("Invalid cookie state: " + + "domain not specified"); + } + final String cookieDomain = cookie.getDomain().toLowerCase(Locale.ENGLISH); + + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + // Domain attribute must start with a dot + if (!cookieDomain.startsWith(".")) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot"); + } + + // Domain attribute must contain at least one embedded dot, + // or the value must be equal to .local. + final int dotIndex = cookieDomain.indexOf('.', 1); + if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1)) + && (!cookieDomain.equals(".local"))) { + throw new CookieRestrictionViolationException( + "Domain attribute \"" + cookie.getDomain() + + "\" violates RFC 2965: the value contains no embedded dots " + + "and the value is not .local"); + } + + // The effective host name must domain-match domain attribute. + if (!domainMatch(host, cookieDomain)) { + throw new CookieRestrictionViolationException( + "Domain attribute \"" + cookie.getDomain() + + "\" violates RFC 2965: effective host name does not " + + "domain-match domain attribute."); + } + + // effective host name minus domain must not contain any dots + final String effectiveHostWithoutDomain = host.substring( + 0, host.length() - cookieDomain.length()); + if (effectiveHostWithoutDomain.indexOf('.') != -1) { + throw new CookieRestrictionViolationException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2965: " + + "effective host minus domain may not contain any dots"); + } + } else { + // Domain was not specified in header. In this case, domain must + // string match request host (case-insensitive). + if (!cookie.getDomain().equals(host)) { + throw new CookieRestrictionViolationException("Illegal domain attribute: \"" + + cookie.getDomain() + "\"." + + "Domain of origin: \"" + + host + "\""); + } + } + } + + /** + * Match cookie domain attribute. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final String host = origin.getHost().toLowerCase(Locale.ENGLISH); + final String cookieDomain = cookie.getDomain(); + + // The effective host name MUST domain-match the Domain + // attribute of the cookie. + if (!domainMatch(host, cookieDomain)) { + return false; + } + // effective host name minus domain must not contain any dots + final String effectiveHostWithoutDomain = host.substring( + 0, host.length() - cookieDomain.length()); + return effectiveHostWithoutDomain.indexOf('.') == -1; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965PortAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965PortAttributeHandler.java new file mode 100644 index 000000000..4835d8eb5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965PortAttributeHandler.java @@ -0,0 +1,160 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.StringTokenizer; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.cookie.SetCookie2; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * @since 4.0 + */ +@Immutable +public class RFC2965PortAttributeHandler implements CookieAttributeHandler { + + public RFC2965PortAttributeHandler() { + super(); + } + + /** + * Parses the given Port attribute value (e.g. "8000,8001,8002") + * into an array of ports. + * + * @param portValue port attribute value + * @return parsed array of ports + * @throws MalformedCookieException if there is a problem in + * parsing due to invalid portValue. + */ + private static int[] parsePortAttribute(final String portValue) + throws MalformedCookieException { + final StringTokenizer st = new StringTokenizer(portValue, ","); + final int[] ports = new int[st.countTokens()]; + try { + int i = 0; + while(st.hasMoreTokens()) { + ports[i] = Integer.parseInt(st.nextToken().trim()); + if (ports[i] < 0) { + throw new MalformedCookieException ("Invalid Port attribute."); + } + ++i; + } + } catch (final NumberFormatException e) { + throw new MalformedCookieException ("Invalid Port " + + "attribute: " + e.getMessage()); + } + return ports; + } + + /** + * Returns <tt>true</tt> if the given port exists in the given + * ports list. + * + * @param port port of host where cookie was received from or being sent to. + * @param ports port list + * @return true returns <tt>true</tt> if the given port exists in + * the given ports list; <tt>false</tt> otherwise. + */ + private static boolean portMatch(final int port, final int[] ports) { + boolean portInList = false; + for (final int port2 : ports) { + if (port == port2) { + portInList = true; + break; + } + } + return portInList; + } + + /** + * Parse cookie port attribute. + */ + public void parse(final SetCookie cookie, final String portValue) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (cookie instanceof SetCookie2) { + final SetCookie2 cookie2 = (SetCookie2) cookie; + if (portValue != null && portValue.trim().length() > 0) { + final int[] ports = parsePortAttribute(portValue); + cookie2.setPorts(ports); + } + } + } + + /** + * Validate cookie port attribute. If the Port attribute was specified + * in header, the request port must be in cookie's port list. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (!portMatch(port, cookie.getPorts())) { + throw new CookieRestrictionViolationException( + "Port attribute violates RFC 2965: " + + "Request port not found in cookie's port list."); + } + } + } + + /** + * Match cookie port attribute. If the Port attribute is not specified + * in header, the cookie can be sent to any port. Otherwise, the request port + * must be in the cookie's port list. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + final int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (cookie.getPorts() == null) { + // Invalid cookie state: port not specified + return false; + } + if (!portMatch(port, cookie.getPorts())) { + return false; + } + } + return true; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965Spec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965Spec.java new file mode 100644 index 000000000..1c0dbc4cb --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965Spec.java @@ -0,0 +1,239 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.NameValuePair; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SM; +import ch.boye.httpclientandroidlib.message.BufferedHeader; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * RFC 2965 compliant {@link ch.boye.httpclientandroidlib.cookie.CookieSpec} implementation. + * + * @since 4.0 + */ +@NotThreadSafe // superclass is @NotThreadSafe +public class RFC2965Spec extends RFC2109Spec { + + /** + * Default constructor + * + */ + public RFC2965Spec() { + this(null, false); + } + + public RFC2965Spec(final String[] datepatterns, final boolean oneHeader) { + super(datepatterns, oneHeader); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2965DomainAttributeHandler()); + registerAttribHandler(ClientCookie.PORT_ATTR, new RFC2965PortAttributeHandler()); + registerAttribHandler(ClientCookie.COMMENTURL_ATTR, new RFC2965CommentUrlAttributeHandler()); + registerAttribHandler(ClientCookie.DISCARD_ATTR, new RFC2965DiscardAttributeHandler()); + registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2965VersionAttributeHandler()); + } + + @Override + public List<Cookie> parse( + final Header header, + final CookieOrigin origin) throws MalformedCookieException { + Args.notNull(header, "Header"); + Args.notNull(origin, "Cookie origin"); + if (!header.getName().equalsIgnoreCase(SM.SET_COOKIE2)) { + throw new MalformedCookieException("Unrecognized cookie header '" + + header.toString() + "'"); + } + final HeaderElement[] elems = header.getElements(); + return createCookies(elems, adjustEffectiveHost(origin)); + } + + @Override + protected List<Cookie> parse( + final HeaderElement[] elems, + final CookieOrigin origin) throws MalformedCookieException { + return createCookies(elems, adjustEffectiveHost(origin)); + } + + private List<Cookie> createCookies( + final HeaderElement[] elems, + final CookieOrigin origin) throws MalformedCookieException { + final List<Cookie> cookies = new ArrayList<Cookie>(elems.length); + for (final HeaderElement headerelement : elems) { + final String name = headerelement.getName(); + final String value = headerelement.getValue(); + if (name == null || name.length() == 0) { + throw new MalformedCookieException("Cookie name may not be empty"); + } + + final BasicClientCookie2 cookie = new BasicClientCookie2(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + cookie.setPorts(new int [] { origin.getPort() }); + // cycle through the parameters + final NameValuePair[] attribs = headerelement.getParameters(); + + // Eliminate duplicate attributes. The first occurrence takes precedence + // See RFC2965: 3.2 Origin Server Role + final Map<String, NameValuePair> attribmap = + new HashMap<String, NameValuePair>(attribs.length); + for (int j = attribs.length - 1; j >= 0; j--) { + final NameValuePair param = attribs[j]; + attribmap.put(param.getName().toLowerCase(Locale.ENGLISH), param); + } + for (final Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) { + final NameValuePair attrib = entry.getValue(); + final String s = attrib.getName().toLowerCase(Locale.ENGLISH); + + cookie.setAttribute(s, attrib.getValue()); + + final CookieAttributeHandler handler = findAttribHandler(s); + if (handler != null) { + handler.parse(cookie, attrib.getValue()); + } + } + cookies.add(cookie); + } + return cookies; + } + + @Override + public void validate( + final Cookie cookie, final CookieOrigin origin) throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + super.validate(cookie, adjustEffectiveHost(origin)); + } + + @Override + public boolean match(final Cookie cookie, final CookieOrigin origin) { + Args.notNull(cookie, "Cookie"); + Args.notNull(origin, "Cookie origin"); + return super.match(cookie, adjustEffectiveHost(origin)); + } + + /** + * Adds valid Port attribute value, e.g. "8000,8001,8002" + */ + @Override + protected void formatCookieAsVer(final CharArrayBuffer buffer, + final Cookie cookie, final int version) { + super.formatCookieAsVer(buffer, cookie, version); + // format port attribute + if (cookie instanceof ClientCookie) { + // Test if the port attribute as set by the origin server is not blank + final String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR); + if (s != null) { + buffer.append("; $Port"); + buffer.append("=\""); + if (s.trim().length() > 0) { + final int[] ports = cookie.getPorts(); + if (ports != null) { + final int len = ports.length; + for (int i = 0; i < len; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(Integer.toString(ports[i])); + } + } + } + buffer.append("\""); + } + } + } + + /** + * Set 'effective host name' as defined in RFC 2965. + * <p> + * If a host name contains no dots, the effective host name is + * that name with the string .local appended to it. Otherwise + * the effective host name is the same as the host name. Note + * that all effective host names contain at least one dot. + * + * @param origin origin where cookie is received from or being sent to. + */ + private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) { + String host = origin.getHost(); + + // Test if the host name appears to be a fully qualified DNS name, + // IPv4 address or IPv6 address + boolean isLocalHost = true; + for (int i = 0; i < host.length(); i++) { + final char ch = host.charAt(i); + if (ch == '.' || ch == ':') { + isLocalHost = false; + break; + } + } + if (isLocalHost) { + host += ".local"; + return new CookieOrigin( + host, + origin.getPort(), + origin.getPath(), + origin.isSecure()); + } else { + return origin; + } + } + + @Override + public int getVersion() { + return 1; + } + + @Override + public Header getVersionHeader() { + final CharArrayBuffer buffer = new CharArrayBuffer(40); + buffer.append(SM.COOKIE2); + buffer.append(": "); + buffer.append("$Version="); + buffer.append(Integer.toString(getVersion())); + return new BufferedHeader(buffer); + } + + @Override + public String toString() { + return "rfc2965"; + } + +} + diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965SpecFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965SpecFactory.java new file mode 100644 index 000000000..83b60407c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965SpecFactory.java @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import java.util.Collection; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.CookieSpec; +import ch.boye.httpclientandroidlib.cookie.CookieSpecFactory; +import ch.boye.httpclientandroidlib.cookie.CookieSpecProvider; +import ch.boye.httpclientandroidlib.cookie.params.CookieSpecPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpContext; + +/** + * {@link CookieSpecProvider} implementation that creates and initializes + * {@link RFC2965Spec} instances. + * + * @since 4.0 + */ +@Immutable +@SuppressWarnings("deprecation") +public class RFC2965SpecFactory implements CookieSpecFactory, CookieSpecProvider { + + private final String[] datepatterns; + private final boolean oneHeader; + + public RFC2965SpecFactory(final String[] datepatterns, final boolean oneHeader) { + super(); + this.datepatterns = datepatterns; + this.oneHeader = oneHeader; + } + + public RFC2965SpecFactory() { + this(null, false); + } + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + + String[] patterns = null; + final Collection<?> param = (Collection<?>) params.getParameter( + CookieSpecPNames.DATE_PATTERNS); + if (param != null) { + patterns = new String[param.size()]; + patterns = param.toArray(patterns); + } + final boolean singleHeader = params.getBooleanParameter( + CookieSpecPNames.SINGLE_COOKIE_HEADER, false); + + return new RFC2965Spec(patterns, singleHeader); + } else { + return new RFC2965Spec(); + } + } + + public CookieSpec create(final HttpContext context) { + return new RFC2965Spec(this.datepatterns, this.oneHeader); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965VersionAttributeHandler.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965VersionAttributeHandler.java new file mode 100644 index 000000000..26ae4faa6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/RFC2965VersionAttributeHandler.java @@ -0,0 +1,94 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.cookie; + +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.cookie.ClientCookie; +import ch.boye.httpclientandroidlib.cookie.Cookie; +import ch.boye.httpclientandroidlib.cookie.CookieAttributeHandler; +import ch.boye.httpclientandroidlib.cookie.CookieOrigin; +import ch.boye.httpclientandroidlib.cookie.CookieRestrictionViolationException; +import ch.boye.httpclientandroidlib.cookie.MalformedCookieException; +import ch.boye.httpclientandroidlib.cookie.SetCookie; +import ch.boye.httpclientandroidlib.cookie.SetCookie2; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * @since 4.0 + */ +@Immutable +public class RFC2965VersionAttributeHandler implements CookieAttributeHandler { + + public RFC2965VersionAttributeHandler() { + super(); + } + + /** + * Parse cookie version attribute. + */ + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (value == null) { + throw new MalformedCookieException( + "Missing value for version attribute"); + } + int version = -1; + try { + version = Integer.parseInt(value); + } catch (final NumberFormatException e) { + version = -1; + } + if (version < 0) { + throw new MalformedCookieException("Invalid cookie version."); + } + cookie.setVersion(version); + } + + /** + * validate cookie version attribute. Version attribute is REQUIRED. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + Args.notNull(cookie, "Cookie"); + if (cookie instanceof SetCookie2) { + if (cookie instanceof ClientCookie + && !((ClientCookie) cookie).containsAttribute(ClientCookie.VERSION_ATTR)) { + throw new CookieRestrictionViolationException( + "Violates RFC 2965. Version attribute is required."); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/package-info.java new file mode 100644 index 000000000..1950e7263 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/cookie/package-info.java @@ -0,0 +1,32 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of standard and common HTTP state + * management policies. + */ +package ch.boye.httpclientandroidlib.impl.cookie; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/DisallowIdentityContentLengthStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/DisallowIdentityContentLengthStrategy.java new file mode 100644 index 000000000..93556d22b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/DisallowIdentityContentLengthStrategy.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.entity; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; + +/** + * Decorator for {@link ContentLengthStrategy} implementations that disallows the use of + * identity transfer encoding. + * + * @since 4.2 + */ +@Immutable +public class DisallowIdentityContentLengthStrategy implements ContentLengthStrategy { + + public static final DisallowIdentityContentLengthStrategy INSTANCE = + new DisallowIdentityContentLengthStrategy(new LaxContentLengthStrategy(0)); + + private final ContentLengthStrategy contentLengthStrategy; + + public DisallowIdentityContentLengthStrategy(final ContentLengthStrategy contentLengthStrategy) { + super(); + this.contentLengthStrategy = contentLengthStrategy; + } + + public long determineLength(final HttpMessage message) throws HttpException { + final long result = this.contentLengthStrategy.determineLength(message); + if (result == ContentLengthStrategy.IDENTITY) { + throw new ProtocolException("Identity transfer encoding cannot be used"); + } + return result; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/EntityDeserializer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/EntityDeserializer.java new file mode 100644 index 000000000..5eba3638c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/EntityDeserializer.java @@ -0,0 +1,143 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.entity; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.entity.BasicHttpEntity; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.ChunkedInputStream; +import ch.boye.httpclientandroidlib.impl.io.ContentLengthInputStream; +import ch.boye.httpclientandroidlib.impl.io.IdentityInputStream; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * HTTP entity deserializer. + * <p> + * This entity deserializer supports "chunked" and "identitiy" transfer-coding + * and content length delimited content. + * <p> + * This class relies on a specific implementation of + * {@link ContentLengthStrategy} to determine the content length or transfer + * encoding of the entity. + * <p> + * This class generates an instance of {@link HttpEntity} based on + * properties of the message. The content of the entity will be decoded + * transparently for the consumer. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link ch.boye.httpclientandroidlib.impl.BHttpConnectionBase} + */ +@Immutable // assuming injected dependencies are immutable +@Deprecated +public class EntityDeserializer { + + private final ContentLengthStrategy lenStrategy; + + public EntityDeserializer(final ContentLengthStrategy lenStrategy) { + super(); + this.lenStrategy = Args.notNull(lenStrategy, "Content length strategy"); + } + + /** + * Creates a {@link BasicHttpEntity} based on properties of the given + * message. The content of the entity is created by wrapping + * {@link SessionInputBuffer} with a content decoder depending on the + * transfer mechanism used by the message. + * <p> + * This method is called by the public + * {@link #deserialize(SessionInputBuffer, HttpMessage)}. + * + * @param inbuffer the session input buffer. + * @param message the message. + * @return HTTP entity. + * @throws HttpException in case of HTTP protocol violation. + * @throws IOException in case of an I/O error. + */ + protected BasicHttpEntity doDeserialize( + final SessionInputBuffer inbuffer, + final HttpMessage message) throws HttpException, IOException { + final BasicHttpEntity entity = new BasicHttpEntity(); + + final long len = this.lenStrategy.determineLength(message); + if (len == ContentLengthStrategy.CHUNKED) { + entity.setChunked(true); + entity.setContentLength(-1); + entity.setContent(new ChunkedInputStream(inbuffer)); + } else if (len == ContentLengthStrategy.IDENTITY) { + entity.setChunked(false); + entity.setContentLength(-1); + entity.setContent(new IdentityInputStream(inbuffer)); + } else { + entity.setChunked(false); + entity.setContentLength(len); + entity.setContent(new ContentLengthInputStream(inbuffer, len)); + } + + final Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE); + if (contentTypeHeader != null) { + entity.setContentType(contentTypeHeader); + } + final Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING); + if (contentEncodingHeader != null) { + entity.setContentEncoding(contentEncodingHeader); + } + return entity; + } + + /** + * Creates an {@link HttpEntity} based on properties of the given message. + * The content of the entity is created by wrapping + * {@link SessionInputBuffer} with a content decoder depending on the + * transfer mechanism used by the message. + * <p> + * The content of the entity is NOT retrieved by this method. + * + * @param inbuffer the session input buffer. + * @param message the message. + * @return HTTP entity. + * @throws HttpException in case of HTTP protocol violation. + * @throws IOException in case of an I/O error. + */ + public HttpEntity deserialize( + final SessionInputBuffer inbuffer, + final HttpMessage message) throws HttpException, IOException { + Args.notNull(inbuffer, "Session input buffer"); + Args.notNull(message, "HTTP message"); + return doDeserialize(inbuffer, message); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/EntitySerializer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/EntitySerializer.java new file mode 100644 index 000000000..f25115cef --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/EntitySerializer.java @@ -0,0 +1,121 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.entity; + +import java.io.IOException; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.impl.io.ChunkedOutputStream; +import ch.boye.httpclientandroidlib.impl.io.ContentLengthOutputStream; +import ch.boye.httpclientandroidlib.impl.io.IdentityOutputStream; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * HTTP entity serializer. + * <p> + * This entity serializer currently supports "chunked" and "identitiy" + * transfer-coding and content length delimited content. + * <p> + * This class relies on a specific implementation of + * {@link ContentLengthStrategy} to determine the content length or transfer + * encoding of the entity. + * <p> + * This class writes out the content of {@link HttpEntity} to the data stream + * using a transfer coding based on properties on the HTTP message. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link ch.boye.httpclientandroidlib.impl.BHttpConnectionBase} + */ +@Immutable // assuming injected dependencies are immutable +@Deprecated +public class EntitySerializer { + + private final ContentLengthStrategy lenStrategy; + + public EntitySerializer(final ContentLengthStrategy lenStrategy) { + super(); + this.lenStrategy = Args.notNull(lenStrategy, "Content length strategy"); + } + + /** + * Creates a transfer codec based on properties of the given HTTP message + * and returns {@link OutputStream} instance that transparently encodes + * output data as it is being written out to the output stream. + * <p> + * This method is called by the public + * {@link #serialize(SessionOutputBuffer, HttpMessage, HttpEntity)}. + * + * @param outbuffer the session output buffer. + * @param message the HTTP message. + * @return output stream. + * @throws HttpException in case of HTTP protocol violation. + * @throws IOException in case of an I/O error. + */ + protected OutputStream doSerialize( + final SessionOutputBuffer outbuffer, + final HttpMessage message) throws HttpException, IOException { + final long len = this.lenStrategy.determineLength(message); + if (len == ContentLengthStrategy.CHUNKED) { + return new ChunkedOutputStream(outbuffer); + } else if (len == ContentLengthStrategy.IDENTITY) { + return new IdentityOutputStream(outbuffer); + } else { + return new ContentLengthOutputStream(outbuffer, len); + } + } + + /** + * Writes out the content of the given HTTP entity to the session output + * buffer based on properties of the given HTTP message. + * + * @param outbuffer the output session buffer. + * @param message the HTTP message. + * @param entity the HTTP entity to be written out. + * @throws HttpException in case of HTTP protocol violation. + * @throws IOException in case of an I/O error. + */ + public void serialize( + final SessionOutputBuffer outbuffer, + final HttpMessage message, + final HttpEntity entity) throws HttpException, IOException { + Args.notNull(outbuffer, "Session output buffer"); + Args.notNull(message, "HTTP message"); + Args.notNull(entity, "HTTP entity"); + final OutputStream outstream = doSerialize(outbuffer, message); + entity.writeTo(outstream); + outstream.close(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/LaxContentLengthStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/LaxContentLengthStrategy.java new file mode 100644 index 000000000..0f0dbaf15 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/LaxContentLengthStrategy.java @@ -0,0 +1,126 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.entity; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderElement; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * The lax implementation of the content length strategy. This class will ignore + * unrecognized transfer encodings and malformed <code>Content-Length</code> + * header values. + * <p/> + * This class recognizes "chunked" and "identitiy" transfer-coding only. + * + * @since 4.0 + */ +@Immutable +public class LaxContentLengthStrategy implements ContentLengthStrategy { + + public static final LaxContentLengthStrategy INSTANCE = new LaxContentLengthStrategy(); + + private final int implicitLen; + + /** + * Creates <tt>LaxContentLengthStrategy</tt> instance with the given length used per default + * when content length is not explicitly specified in the message. + * + * @param implicitLen implicit content length. + * + * @since 4.2 + */ + public LaxContentLengthStrategy(final int implicitLen) { + super(); + this.implicitLen = implicitLen; + } + + /** + * Creates <tt>LaxContentLengthStrategy</tt> instance. {@link ContentLengthStrategy#IDENTITY} + * is used per default when content length is not explicitly specified in the message. + */ + public LaxContentLengthStrategy() { + this(IDENTITY); + } + + public long determineLength(final HttpMessage message) throws HttpException { + Args.notNull(message, "HTTP message"); + + final Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING); + // We use Transfer-Encoding if present and ignore Content-Length. + // RFC2616, 4.4 item number 3 + if (transferEncodingHeader != null) { + final HeaderElement[] encodings; + try { + encodings = transferEncodingHeader.getElements(); + } catch (final ParseException px) { + throw new ProtocolException + ("Invalid Transfer-Encoding header value: " + + transferEncodingHeader, px); + } + // The chunked encoding must be the last one applied RFC2616, 14.41 + final int len = encodings.length; + if (HTTP.IDENTITY_CODING.equalsIgnoreCase(transferEncodingHeader.getValue())) { + return IDENTITY; + } else if ((len > 0) && (HTTP.CHUNK_CODING.equalsIgnoreCase( + encodings[len - 1].getName()))) { + return CHUNKED; + } else { + return IDENTITY; + } + } + final Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN); + if (contentLengthHeader != null) { + long contentlen = -1; + final Header[] headers = message.getHeaders(HTTP.CONTENT_LEN); + for (int i = headers.length - 1; i >= 0; i--) { + final Header header = headers[i]; + try { + contentlen = Long.parseLong(header.getValue()); + break; + } catch (final NumberFormatException ignore) { + } + // See if we can have better luck with another header, if present + } + if (contentlen >= 0) { + return contentlen; + } else { + return IDENTITY; + } + } + return this.implicitLen; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/StrictContentLengthStrategy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/StrictContentLengthStrategy.java new file mode 100644 index 000000000..1693c3111 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/StrictContentLengthStrategy.java @@ -0,0 +1,116 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.entity; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpVersion; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.entity.ContentLengthStrategy; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * The strict implementation of the content length strategy. This class + * will throw {@link ProtocolException} if it encounters an unsupported + * transfer encoding or a malformed <code>Content-Length</code> header + * value. + * <p> + * This class recognizes "chunked" and "identitiy" transfer-coding only. + * + * @since 4.0 + */ +@Immutable +public class StrictContentLengthStrategy implements ContentLengthStrategy { + + public static final StrictContentLengthStrategy INSTANCE = new StrictContentLengthStrategy(); + + private final int implicitLen; + + /** + * Creates <tt>StrictContentLengthStrategy</tt> instance with the given length used per default + * when content length is not explicitly specified in the message. + * + * @param implicitLen implicit content length. + * + * @since 4.2 + */ + public StrictContentLengthStrategy(final int implicitLen) { + super(); + this.implicitLen = implicitLen; + } + + /** + * Creates <tt>StrictContentLengthStrategy</tt> instance. {@link ContentLengthStrategy#IDENTITY} + * is used per default when content length is not explicitly specified in the message. + */ + public StrictContentLengthStrategy() { + this(IDENTITY); + } + + public long determineLength(final HttpMessage message) throws HttpException { + Args.notNull(message, "HTTP message"); + // Although Transfer-Encoding is specified as a list, in practice + // it is either missing or has the single value "chunked". So we + // treat it as a single-valued header here. + final Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING); + if (transferEncodingHeader != null) { + final String s = transferEncodingHeader.getValue(); + if (HTTP.CHUNK_CODING.equalsIgnoreCase(s)) { + if (message.getProtocolVersion().lessEquals(HttpVersion.HTTP_1_0)) { + throw new ProtocolException( + "Chunked transfer encoding not allowed for " + + message.getProtocolVersion()); + } + return CHUNKED; + } else if (HTTP.IDENTITY_CODING.equalsIgnoreCase(s)) { + return IDENTITY; + } else { + throw new ProtocolException( + "Unsupported transfer encoding: " + s); + } + } + final Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN); + if (contentLengthHeader != null) { + final String s = contentLengthHeader.getValue(); + try { + final long len = Long.parseLong(s); + if (len < 0) { + throw new ProtocolException("Negative content length: " + s); + } + return len; + } catch (final NumberFormatException e) { + throw new ProtocolException("Invalid content length: " + s); + } + } + return this.implicitLen; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/package-info.java new file mode 100644 index 000000000..7566c4570 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/entity/package-info.java @@ -0,0 +1,31 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of entity content strategies. + */ +package ch.boye.httpclientandroidlib.impl.entity; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/BackoffStrategyExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/BackoffStrategyExec.java new file mode 100644 index 000000000..a639da934 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/BackoffStrategyExec.java @@ -0,0 +1,104 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.BackoffManager; +import ch.boye.httpclientandroidlib.client.ConnectionBackoffStrategy; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * @since 4.3 + */ +@Immutable +public class BackoffStrategyExec implements ClientExecChain { + + private final ClientExecChain requestExecutor; + private final ConnectionBackoffStrategy connectionBackoffStrategy; + private final BackoffManager backoffManager; + + public BackoffStrategyExec( + final ClientExecChain requestExecutor, + final ConnectionBackoffStrategy connectionBackoffStrategy, + final BackoffManager backoffManager) { + super(); + Args.notNull(requestExecutor, "HTTP client request executor"); + Args.notNull(connectionBackoffStrategy, "Connection backoff strategy"); + Args.notNull(backoffManager, "Backoff manager"); + this.requestExecutor = requestExecutor; + this.connectionBackoffStrategy = connectionBackoffStrategy; + this.backoffManager = backoffManager; + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + Args.notNull(route, "HTTP route"); + Args.notNull(request, "HTTP request"); + Args.notNull(context, "HTTP context"); + CloseableHttpResponse out = null; + try { + out = this.requestExecutor.execute(route, request, context, execAware); + } catch (final Exception ex) { + if (out != null) { + out.close(); + } + if (this.connectionBackoffStrategy.shouldBackoff(ex)) { + this.backoffManager.backOff(route); + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + if (ex instanceof HttpException) { + throw (HttpException) ex; + } + if (ex instanceof IOException) { + throw (IOException) ex; + } + throw new UndeclaredThrowableException(ex); + } + if (this.connectionBackoffStrategy.shouldBackoff(out)) { + this.backoffManager.backOff(route); + } else { + this.backoffManager.probe(route); + } + return out; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ClientExecChain.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ClientExecChain.java new file mode 100644 index 000000000..2b5e3c8f6 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ClientExecChain.java @@ -0,0 +1,74 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; + +/** + * This interface represents an element in the HTTP request execution chain. Each element can + * either be a decorator around another element that implements a cross cutting aspect or + * a self-contained executor capable of producing a response for the given request. + * <p/> + * Important: please note it is required for decorators that implement post execution aspects + * or response post-processing of any sort to release resources associated with the response + * by calling {@link CloseableHttpResponse#close()} methods in case of an I/O, protocol or + * runtime exception, or in case the response is not propagated to the caller. + * + * @since 4.3 + */ +public interface ClientExecChain { + + /** + * Executes th request either by transmitting it to the target server or + * by passing it onto the next executor in the request execution chain. + * + * @param route connection route. + * @param request current request. + * @param clientContext current HTTP context. + * @param execAware receiver of notifications of blocking I/O operations. + * @return HTTP response either received from the opposite endpoint + * or generated locally. + * @throws IOException in case of a I/O error. + * (this type of exceptions are potentially recoverable). + * @throws HttpException in case of an HTTP protocol error + * (usually this type of exceptions are non-recoverable). + */ + CloseableHttpResponse execute( + HttpRoute route, + HttpRequestWrapper request, + HttpClientContext clientContext, + HttpExecutionAware execAware) throws IOException, HttpException; + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ConnectionHolder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ConnectionHolder.java new file mode 100644 index 000000000..d9bb17286 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ConnectionHolder.java @@ -0,0 +1,153 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.concurrent.Cancellable; +import ch.boye.httpclientandroidlib.conn.ConnectionReleaseTrigger; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; + +/** + * Internal connection holder. + * + * @since 4.3 + */ +@ThreadSafe +class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeable { + + public HttpClientAndroidLog log; + + private final HttpClientConnectionManager manager; + private final HttpClientConnection managedConn; + private volatile boolean reusable; + private volatile Object state; + private volatile long validDuration; + private volatile TimeUnit tunit; + + private volatile boolean released; + + public ConnectionHolder( + final HttpClientAndroidLog log, + final HttpClientConnectionManager manager, + final HttpClientConnection managedConn) { + super(); + this.log = log; + this.manager = manager; + this.managedConn = managedConn; + } + + public boolean isReusable() { + return this.reusable; + } + + public void markReusable() { + this.reusable = true; + } + + public void markNonReusable() { + this.reusable = false; + } + + public void setState(final Object state) { + this.state = state; + } + + public void setValidFor(final long duration, final TimeUnit tunit) { + synchronized (this.managedConn) { + this.validDuration = duration; + this.tunit = tunit; + } + } + + public void releaseConnection() { + synchronized (this.managedConn) { + if (this.released) { + return; + } + this.released = true; + if (this.reusable) { + this.manager.releaseConnection(this.managedConn, + this.state, this.validDuration, this.tunit); + } else { + try { + this.managedConn.close(); + log.debug("Connection discarded"); + } catch (final IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } finally { + this.manager.releaseConnection( + this.managedConn, null, 0, TimeUnit.MILLISECONDS); + } + } + } + } + + public void abortConnection() { + synchronized (this.managedConn) { + if (this.released) { + return; + } + this.released = true; + try { + this.managedConn.shutdown(); + log.debug("Connection discarded"); + } catch (final IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } finally { + this.manager.releaseConnection( + this.managedConn, null, 0, TimeUnit.MILLISECONDS); + } + } + } + + public boolean cancel() { + final boolean alreadyReleased = this.released; + log.debug("Cancelling request execution"); + abortConnection(); + return !alreadyReleased; + } + + public boolean isReleased() { + return this.released; + } + + public void close() throws IOException { + abortConnection(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/HttpResponseProxy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/HttpResponseProxy.java new file mode 100644 index 000000000..1fdc027c8 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/HttpResponseProxy.java @@ -0,0 +1,185 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.util.Locale; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderIterator; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.ProtocolVersion; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * A proxy class for {@link ch.boye.httpclientandroidlib.HttpResponse} that can be used to release client connection + * associated with the original response. + * + * @since 4.3 + */ +@NotThreadSafe +class HttpResponseProxy implements CloseableHttpResponse { + + private final HttpResponse original; + private final ConnectionHolder connHolder; + + public HttpResponseProxy(final HttpResponse original, final ConnectionHolder connHolder) { + this.original = original; + this.connHolder = connHolder; + ResponseEntityProxy.enchance(original, connHolder); + } + + public void close() throws IOException { + if (this.connHolder != null) { + this.connHolder.abortConnection(); + } + } + + public StatusLine getStatusLine() { + return original.getStatusLine(); + } + + public void setStatusLine(final StatusLine statusline) { + original.setStatusLine(statusline); + } + + public void setStatusLine(final ProtocolVersion ver, final int code) { + original.setStatusLine(ver, code); + } + + public void setStatusLine(final ProtocolVersion ver, final int code, final String reason) { + original.setStatusLine(ver, code, reason); + } + + public void setStatusCode(final int code) throws IllegalStateException { + original.setStatusCode(code); + } + + public void setReasonPhrase(final String reason) throws IllegalStateException { + original.setReasonPhrase(reason); + } + + public HttpEntity getEntity() { + return original.getEntity(); + } + + public void setEntity(final HttpEntity entity) { + original.setEntity(entity); + } + + public Locale getLocale() { + return original.getLocale(); + } + + public void setLocale(final Locale loc) { + original.setLocale(loc); + } + + public ProtocolVersion getProtocolVersion() { + return original.getProtocolVersion(); + } + + public boolean containsHeader(final String name) { + return original.containsHeader(name); + } + + public Header[] getHeaders(final String name) { + return original.getHeaders(name); + } + + public Header getFirstHeader(final String name) { + return original.getFirstHeader(name); + } + + public Header getLastHeader(final String name) { + return original.getLastHeader(name); + } + + public Header[] getAllHeaders() { + return original.getAllHeaders(); + } + + public void addHeader(final Header header) { + original.addHeader(header); + } + + public void addHeader(final String name, final String value) { + original.addHeader(name, value); + } + + public void setHeader(final Header header) { + original.setHeader(header); + } + + public void setHeader(final String name, final String value) { + original.setHeader(name, value); + } + + public void setHeaders(final Header[] headers) { + original.setHeaders(headers); + } + + public void removeHeader(final Header header) { + original.removeHeader(header); + } + + public void removeHeaders(final String name) { + original.removeHeaders(name); + } + + public HeaderIterator headerIterator() { + return original.headerIterator(); + } + + public HeaderIterator headerIterator(final String name) { + return original.headerIterator(name); + } + + @Deprecated + public HttpParams getParams() { + return original.getParams(); + } + + @Deprecated + public void setParams(final HttpParams params) { + original.setParams(params); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("HttpResponseProxy{"); + sb.append(original); + sb.append('}'); + return sb.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/MainClientExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/MainClientExec.java new file mode 100644 index 000000000..cf1dbfde4 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/MainClientExec.java @@ -0,0 +1,568 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AUTH; +import ch.boye.httpclientandroidlib.auth.AuthProtocolState; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.client.AuthenticationStrategy; +import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException; +import ch.boye.httpclientandroidlib.client.UserTokenHandler; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.ConnectionRequest; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.routing.BasicRouteDirector; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRouteDirector; +import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; +import ch.boye.httpclientandroidlib.entity.BufferedHttpEntity; +import ch.boye.httpclientandroidlib.impl.auth.HttpAuthenticator; +import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException; +import ch.boye.httpclientandroidlib.message.BasicHttpRequest; +import ch.boye.httpclientandroidlib.protocol.HttpCoreContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * The last request executor in the HTTP request execution chain + * that is responsible for execution of request / response + * exchanges with the opposite endpoint. + * This executor will automatically retry the request in case + * of an authentication challenge by an intermediate proxy or + * by the target server. + * + * @since 4.3 + */ +@Immutable +public class MainClientExec implements ClientExecChain { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final HttpRequestExecutor requestExecutor; + private final HttpClientConnectionManager connManager; + private final ConnectionReuseStrategy reuseStrategy; + private final ConnectionKeepAliveStrategy keepAliveStrategy; + private final HttpProcessor proxyHttpProcessor; + private final AuthenticationStrategy targetAuthStrategy; + private final AuthenticationStrategy proxyAuthStrategy; + private final HttpAuthenticator authenticator; + private final UserTokenHandler userTokenHandler; + private final HttpRouteDirector routeDirector; + + + public MainClientExec( + final HttpRequestExecutor requestExecutor, + final HttpClientConnectionManager connManager, + final ConnectionReuseStrategy reuseStrategy, + final ConnectionKeepAliveStrategy keepAliveStrategy, + final AuthenticationStrategy targetAuthStrategy, + final AuthenticationStrategy proxyAuthStrategy, + final UserTokenHandler userTokenHandler) { + Args.notNull(requestExecutor, "HTTP request executor"); + Args.notNull(connManager, "Client connection manager"); + Args.notNull(reuseStrategy, "Connection reuse strategy"); + Args.notNull(keepAliveStrategy, "Connection keep alive strategy"); + Args.notNull(targetAuthStrategy, "Target authentication strategy"); + Args.notNull(proxyAuthStrategy, "Proxy authentication strategy"); + Args.notNull(userTokenHandler, "User token handler"); + this.authenticator = new HttpAuthenticator(); + this.proxyHttpProcessor = new ImmutableHttpProcessor( + new RequestTargetHost(), new RequestClientConnControl()); + this.routeDirector = new BasicRouteDirector(); + this.requestExecutor = requestExecutor; + this.connManager = connManager; + this.reuseStrategy = reuseStrategy; + this.keepAliveStrategy = keepAliveStrategy; + this.targetAuthStrategy = targetAuthStrategy; + this.proxyAuthStrategy = proxyAuthStrategy; + this.userTokenHandler = userTokenHandler; + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + Args.notNull(route, "HTTP route"); + Args.notNull(request, "HTTP request"); + Args.notNull(context, "HTTP context"); + + AuthState targetAuthState = context.getTargetAuthState(); + if (targetAuthState == null) { + targetAuthState = new AuthState(); + context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, targetAuthState); + } + AuthState proxyAuthState = context.getProxyAuthState(); + if (proxyAuthState == null) { + proxyAuthState = new AuthState(); + context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, proxyAuthState); + } + + if (request instanceof HttpEntityEnclosingRequest) { + RequestEntityProxy.enhance((HttpEntityEnclosingRequest) request); + } + + Object userToken = context.getUserToken(); + + final ConnectionRequest connRequest = connManager.requestConnection(route, userToken); + if (execAware != null) { + if (execAware.isAborted()) { + connRequest.cancel(); + throw new RequestAbortedException("Request aborted"); + } else { + execAware.setCancellable(connRequest); + } + } + + final RequestConfig config = context.getRequestConfig(); + + final HttpClientConnection managedConn; + try { + final int timeout = config.getConnectionRequestTimeout(); + managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS); + } catch(final InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new RequestAbortedException("Request aborted", interrupted); + } catch(final ExecutionException ex) { + Throwable cause = ex.getCause(); + if (cause == null) { + cause = ex; + } + throw new RequestAbortedException("Request execution failed", cause); + } + + context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn); + + if (config.isStaleConnectionCheckEnabled()) { + // validate connection + if (managedConn.isOpen()) { + this.log.debug("Stale connection check"); + if (managedConn.isStale()) { + this.log.debug("Stale connection detected"); + managedConn.close(); + } + } + } + + final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn); + try { + if (execAware != null) { + execAware.setCancellable(connHolder); + } + + HttpResponse response; + for (int execCount = 1;; execCount++) { + + if (execCount > 1 && !RequestEntityProxy.isRepeatable(request)) { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity."); + } + + if (execAware != null && execAware.isAborted()) { + throw new RequestAbortedException("Request aborted"); + } + + if (!managedConn.isOpen()) { + this.log.debug("Opening connection " + route); + try { + establishRoute(proxyAuthState, managedConn, route, request, context); + } catch (final TunnelRefusedException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage()); + } + response = ex.getResponse(); + break; + } + } + final int timeout = config.getSocketTimeout(); + if (timeout >= 0) { + managedConn.setSocketTimeout(timeout); + } + + if (execAware != null && execAware.isAborted()) { + throw new RequestAbortedException("Request aborted"); + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Executing request " + request.getRequestLine()); + } + + if (!request.containsHeader(AUTH.WWW_AUTH_RESP)) { + if (this.log.isDebugEnabled()) { + this.log.debug("Target auth state: " + targetAuthState.getState()); + } + this.authenticator.generateAuthResponse(request, targetAuthState, context); + } + if (!request.containsHeader(AUTH.PROXY_AUTH_RESP) && !route.isTunnelled()) { + if (this.log.isDebugEnabled()) { + this.log.debug("Proxy auth state: " + proxyAuthState.getState()); + } + this.authenticator.generateAuthResponse(request, proxyAuthState, context); + } + + response = requestExecutor.execute(request, managedConn, context); + + // The connection is in or can be brought to a re-usable state. + if (reuseStrategy.keepAlive(response, context)) { + // Set the idle duration of this connection + final long duration = keepAliveStrategy.getKeepAliveDuration(response, context); + if (this.log.isDebugEnabled()) { + final String s; + if (duration > 0) { + s = "for " + duration + " " + TimeUnit.MILLISECONDS; + } else { + s = "indefinitely"; + } + this.log.debug("Connection can be kept alive " + s); + } + connHolder.setValidFor(duration, TimeUnit.MILLISECONDS); + connHolder.markReusable(); + } else { + connHolder.markNonReusable(); + } + + if (needAuthentication( + targetAuthState, proxyAuthState, route, response, context)) { + // Make sure the response body is fully consumed, if present + final HttpEntity entity = response.getEntity(); + if (connHolder.isReusable()) { + EntityUtils.consume(entity); + } else { + managedConn.close(); + if (proxyAuthState.getState() == AuthProtocolState.SUCCESS + && proxyAuthState.getAuthScheme() != null + && proxyAuthState.getAuthScheme().isConnectionBased()) { + this.log.debug("Resetting proxy auth state"); + proxyAuthState.reset(); + } + if (targetAuthState.getState() == AuthProtocolState.SUCCESS + && targetAuthState.getAuthScheme() != null + && targetAuthState.getAuthScheme().isConnectionBased()) { + this.log.debug("Resetting target auth state"); + targetAuthState.reset(); + } + } + // discard previous auth headers + final HttpRequest original = request.getOriginal(); + if (!original.containsHeader(AUTH.WWW_AUTH_RESP)) { + request.removeHeaders(AUTH.WWW_AUTH_RESP); + } + if (!original.containsHeader(AUTH.PROXY_AUTH_RESP)) { + request.removeHeaders(AUTH.PROXY_AUTH_RESP); + } + } else { + break; + } + } + + if (userToken == null) { + userToken = userTokenHandler.getUserToken(context); + context.setAttribute(HttpClientContext.USER_TOKEN, userToken); + } + if (userToken != null) { + connHolder.setState(userToken); + } + + // check for entity, release connection if possible + final HttpEntity entity = response.getEntity(); + if (entity == null || !entity.isStreaming()) { + // connection not needed and (assumed to be) in re-usable state + connHolder.releaseConnection(); + return new HttpResponseProxy(response, null); + } else { + return new HttpResponseProxy(response, connHolder); + } + } catch (final ConnectionShutdownException ex) { + final InterruptedIOException ioex = new InterruptedIOException( + "Connection has been shut down"); + ioex.initCause(ex); + throw ioex; + } catch (final HttpException ex) { + connHolder.abortConnection(); + throw ex; + } catch (final IOException ex) { + connHolder.abortConnection(); + throw ex; + } catch (final RuntimeException ex) { + connHolder.abortConnection(); + throw ex; + } + } + + /** + * Establishes the target route. + */ + void establishRoute( + final AuthState proxyAuthState, + final HttpClientConnection managedConn, + final HttpRoute route, + final HttpRequest request, + final HttpClientContext context) throws HttpException, IOException { + final RequestConfig config = context.getRequestConfig(); + final int timeout = config.getConnectTimeout(); + final RouteTracker tracker = new RouteTracker(route); + int step; + do { + final HttpRoute fact = tracker.toRoute(); + step = this.routeDirector.nextStep(route, fact); + + switch (step) { + + case HttpRouteDirector.CONNECT_TARGET: + this.connManager.connect( + managedConn, + route, + timeout > 0 ? timeout : 0, + context); + tracker.connectTarget(route.isSecure()); + break; + case HttpRouteDirector.CONNECT_PROXY: + this.connManager.connect( + managedConn, + route, + timeout > 0 ? timeout : 0, + context); + final HttpHost proxy = route.getProxyHost(); + tracker.connectProxy(proxy, false); + break; + case HttpRouteDirector.TUNNEL_TARGET: { + final boolean secure = createTunnelToTarget( + proxyAuthState, managedConn, route, request, context); + this.log.debug("Tunnel to target created."); + tracker.tunnelTarget(secure); + } break; + + case HttpRouteDirector.TUNNEL_PROXY: { + // The most simple example for this case is a proxy chain + // of two proxies, where P1 must be tunnelled to P2. + // route: Source -> P1 -> P2 -> Target (3 hops) + // fact: Source -> P1 -> Target (2 hops) + final int hop = fact.getHopCount()-1; // the hop to establish + final boolean secure = createTunnelToProxy(route, hop, context); + this.log.debug("Tunnel to proxy created."); + tracker.tunnelProxy(route.getHopTarget(hop), secure); + } break; + + case HttpRouteDirector.LAYER_PROTOCOL: + this.connManager.upgrade(managedConn, route, context); + tracker.layerProtocol(route.isSecure()); + break; + + case HttpRouteDirector.UNREACHABLE: + throw new HttpException("Unable to establish route: " + + "planned = " + route + "; current = " + fact); + case HttpRouteDirector.COMPLETE: + this.connManager.routeComplete(managedConn, route, context); + break; + default: + throw new IllegalStateException("Unknown step indicator " + + step + " from RouteDirector."); + } + + } while (step > HttpRouteDirector.COMPLETE); + } + + /** + * Creates a tunnel to the target server. + * The connection must be established to the (last) proxy. + * A CONNECT request for tunnelling through the proxy will + * be created and sent, the response received and checked. + * This method does <i>not</i> update the connection with + * information about the tunnel, that is left to the caller. + */ + private boolean createTunnelToTarget( + final AuthState proxyAuthState, + final HttpClientConnection managedConn, + final HttpRoute route, + final HttpRequest request, + final HttpClientContext context) throws HttpException, IOException { + + final RequestConfig config = context.getRequestConfig(); + final int timeout = config.getConnectTimeout(); + + final HttpHost target = route.getTargetHost(); + final HttpHost proxy = route.getProxyHost(); + HttpResponse response = null; + + final String authority = target.toHostString(); + final HttpRequest connect = new BasicHttpRequest("CONNECT", authority, request.getProtocolVersion()); + + this.requestExecutor.preProcess(connect, this.proxyHttpProcessor, context); + + while (response == null) { + if (!managedConn.isOpen()) { + this.connManager.connect( + managedConn, + route, + timeout > 0 ? timeout : 0, + context); + } + + connect.removeHeaders(AUTH.PROXY_AUTH_RESP); + this.authenticator.generateAuthResponse(connect, proxyAuthState, context); + + response = this.requestExecutor.execute(connect, managedConn, context); + + final int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + + if (config.isAuthenticationEnabled()) { + if (this.authenticator.isAuthenticationRequested(proxy, response, + this.proxyAuthStrategy, proxyAuthState, context)) { + if (this.authenticator.handleAuthChallenge(proxy, response, + this.proxyAuthStrategy, proxyAuthState, context)) { + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + this.log.debug("Connection kept alive"); + // Consume response content + final HttpEntity entity = response.getEntity(); + EntityUtils.consume(entity); + } else { + managedConn.close(); + } + response = null; + } + } + } + } + + final int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + final HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + managedConn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + + // How to decide on security of the tunnelled connection? + // The socket factory knows only about the segment to the proxy. + // Even if that is secure, the hop to the target may be insecure. + // Leave it to derived classes, consider insecure by default here. + return false; + } + + /** + * Creates a tunnel to an intermediate proxy. + * This method is <i>not</i> implemented in this class. + * It just throws an exception here. + */ + private boolean createTunnelToProxy( + final HttpRoute route, + final int hop, + final HttpClientContext context) throws HttpException { + + // Have a look at createTunnelToTarget and replicate the parts + // you need in a custom derived class. If your proxies don't require + // authentication, it is not too hard. But for the stock version of + // HttpClient, we cannot make such simplifying assumptions and would + // have to include proxy authentication code. The HttpComponents team + // is currently not in a position to support rarely used code of this + // complexity. Feel free to submit patches that refactor the code in + // createTunnelToTarget to facilitate re-use for proxy tunnelling. + + throw new HttpException("Proxy chains are not supported."); + } + + private boolean needAuthentication( + final AuthState targetAuthState, + final AuthState proxyAuthState, + final HttpRoute route, + final HttpResponse response, + final HttpClientContext context) { + final RequestConfig config = context.getRequestConfig(); + if (config.isAuthenticationEnabled()) { + HttpHost target = context.getTargetHost(); + if (target == null) { + target = route.getTargetHost(); + } + if (target.getPort() < 0) { + target = new HttpHost( + target.getHostName(), + route.getTargetHost().getPort(), + target.getSchemeName()); + } + final boolean targetAuthRequested = this.authenticator.isAuthenticationRequested( + target, response, this.targetAuthStrategy, targetAuthState, context); + + HttpHost proxy = route.getProxyHost(); + // if proxy is not set use target host instead + if (proxy == null) { + proxy = route.getTargetHost(); + } + final boolean proxyAuthRequested = this.authenticator.isAuthenticationRequested( + proxy, response, this.proxyAuthStrategy, proxyAuthState, context); + + if (targetAuthRequested) { + return this.authenticator.handleAuthChallenge(target, response, + this.targetAuthStrategy, targetAuthState, context); + } + if (proxyAuthRequested) { + return this.authenticator.handleAuthChallenge(proxy, response, + this.proxyAuthStrategy, proxyAuthState, context); + } + } + return false; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/MinimalClientExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/MinimalClientExec.java new file mode 100644 index 000000000..16f6176eb --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/MinimalClientExec.java @@ -0,0 +1,251 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.protocol.RequestClientConnControl; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.ConnectionKeepAliveStrategy; +import ch.boye.httpclientandroidlib.conn.ConnectionRequest; +import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.conn.ConnectionShutdownException; +import ch.boye.httpclientandroidlib.protocol.HttpCoreContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.protocol.HttpRequestExecutor; +import ch.boye.httpclientandroidlib.protocol.ImmutableHttpProcessor; +import ch.boye.httpclientandroidlib.protocol.RequestContent; +import ch.boye.httpclientandroidlib.protocol.RequestTargetHost; +import ch.boye.httpclientandroidlib.protocol.RequestUserAgent; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.VersionInfo; + +/** + * Request executor that implements the most fundamental aspects of + * the HTTP specification and the most straight-forward request / response + * exchange with the target server. This executor does not support + * execution via proxy and will make no attempts to retry the request + * in case of a redirect, authentication challenge or I/O error. + * + * @since 4.3 + */ +@Immutable +public class MinimalClientExec implements ClientExecChain { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final HttpRequestExecutor requestExecutor; + private final HttpClientConnectionManager connManager; + private final ConnectionReuseStrategy reuseStrategy; + private final ConnectionKeepAliveStrategy keepAliveStrategy; + private final HttpProcessor httpProcessor; + + public MinimalClientExec( + final HttpRequestExecutor requestExecutor, + final HttpClientConnectionManager connManager, + final ConnectionReuseStrategy reuseStrategy, + final ConnectionKeepAliveStrategy keepAliveStrategy) { + Args.notNull(requestExecutor, "HTTP request executor"); + Args.notNull(connManager, "Client connection manager"); + Args.notNull(reuseStrategy, "Connection reuse strategy"); + Args.notNull(keepAliveStrategy, "Connection keep alive strategy"); + this.httpProcessor = new ImmutableHttpProcessor( + new RequestContent(), + new RequestTargetHost(), + new RequestClientConnControl(), + new RequestUserAgent(VersionInfo.getUserAgent( + "Apache-HttpClient", "ch.boye.httpclientandroidlib.client", getClass()))); + this.requestExecutor = requestExecutor; + this.connManager = connManager; + this.reuseStrategy = reuseStrategy; + this.keepAliveStrategy = keepAliveStrategy; + } + + static void rewriteRequestURI( + final HttpRequestWrapper request, + final HttpRoute route) throws ProtocolException { + try { + URI uri = request.getURI(); + if (uri != null) { + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + request.setURI(uri); + } + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), ex); + } + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + Args.notNull(route, "HTTP route"); + Args.notNull(request, "HTTP request"); + Args.notNull(context, "HTTP context"); + + rewriteRequestURI(request, route); + + final ConnectionRequest connRequest = connManager.requestConnection(route, null); + if (execAware != null) { + if (execAware.isAborted()) { + connRequest.cancel(); + throw new RequestAbortedException("Request aborted"); + } else { + execAware.setCancellable(connRequest); + } + } + + final RequestConfig config = context.getRequestConfig(); + + final HttpClientConnection managedConn; + try { + final int timeout = config.getConnectionRequestTimeout(); + managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS); + } catch(final InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new RequestAbortedException("Request aborted", interrupted); + } catch(final ExecutionException ex) { + Throwable cause = ex.getCause(); + if (cause == null) { + cause = ex; + } + throw new RequestAbortedException("Request execution failed", cause); + } + + final ConnectionHolder releaseTrigger = new ConnectionHolder(log, connManager, managedConn); + try { + if (execAware != null) { + if (execAware.isAborted()) { + releaseTrigger.close(); + throw new RequestAbortedException("Request aborted"); + } else { + execAware.setCancellable(releaseTrigger); + } + } + + if (!managedConn.isOpen()) { + final int timeout = config.getConnectTimeout(); + this.connManager.connect( + managedConn, + route, + timeout > 0 ? timeout : 0, + context); + this.connManager.routeComplete(managedConn, route, context); + } + final int timeout = config.getSocketTimeout(); + if (timeout >= 0) { + managedConn.setSocketTimeout(timeout); + } + + HttpHost target = null; + final HttpRequest original = request.getOriginal(); + if (original instanceof HttpUriRequest) { + final URI uri = ((HttpUriRequest) original).getURI(); + if (uri.isAbsolute()) { + target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()); + } + } + if (target == null) { + target = route.getTargetHost(); + } + + context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target); + context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn); + context.setAttribute(HttpClientContext.HTTP_ROUTE, route); + + httpProcessor.process(request, context); + final HttpResponse response = requestExecutor.execute(request, managedConn, context); + httpProcessor.process(response, context); + + // The connection is in or can be brought to a re-usable state. + if (reuseStrategy.keepAlive(response, context)) { + // Set the idle duration of this connection + final long duration = keepAliveStrategy.getKeepAliveDuration(response, context); + releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS); + releaseTrigger.markReusable(); + } else { + releaseTrigger.markNonReusable(); + } + + // check for entity, release connection if possible + final HttpEntity entity = response.getEntity(); + if (entity == null || !entity.isStreaming()) { + // connection not needed and (assumed to be) in re-usable state + releaseTrigger.releaseConnection(); + return new HttpResponseProxy(response, null); + } else { + return new HttpResponseProxy(response, releaseTrigger); + } + } catch (final ConnectionShutdownException ex) { + final InterruptedIOException ioex = new InterruptedIOException( + "Connection has been shut down"); + ioex.initCause(ex); + throw ioex; + } catch (final HttpException ex) { + releaseTrigger.abortConnection(); + throw ex; + } catch (final IOException ex) { + releaseTrigger.abortConnection(); + throw ex; + } catch (final RuntimeException ex) { + releaseTrigger.abortConnection(); + throw ex; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ProtocolExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ProtocolExec.java new file mode 100644 index 000000000..4effac603 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ProtocolExec.java @@ -0,0 +1,214 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.auth.AuthScope; +import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials; +import ch.boye.httpclientandroidlib.client.CredentialsProvider; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; +import ch.boye.httpclientandroidlib.client.params.ClientPNames; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.impl.client.BasicCredentialsProvider; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HttpCoreContext; +import ch.boye.httpclientandroidlib.protocol.HttpProcessor; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Request executor in the request execution chain that is responsible + * for implementation of HTTP specification requirements. + * Internally this executor relies on a {@link HttpProcessor} to populate + * requisite HTTP request headers, process HTTP response headers and update + * session state in {@link HttpClientContext}. + * <p/> + * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 4.3 + */ +@Immutable +@SuppressWarnings("deprecation") +public class ProtocolExec implements ClientExecChain { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ClientExecChain requestExecutor; + private final HttpProcessor httpProcessor; + + public ProtocolExec(final ClientExecChain requestExecutor, final HttpProcessor httpProcessor) { + Args.notNull(requestExecutor, "HTTP client request executor"); + Args.notNull(httpProcessor, "HTTP protocol processor"); + this.requestExecutor = requestExecutor; + this.httpProcessor = httpProcessor; + } + + void rewriteRequestURI( + final HttpRequestWrapper request, + final HttpRoute route) throws ProtocolException { + try { + URI uri = request.getURI(); + if (uri != null) { + if (route.getProxyHost() != null && !route.isTunnelled()) { + // Make sure the request URI is absolute + if (!uri.isAbsolute()) { + final HttpHost target = route.getTargetHost(); + uri = URIUtils.rewriteURI(uri, target, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + } else { + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null, true); + } else { + uri = URIUtils.rewriteURI(uri); + } + } + request.setURI(uri); + } + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid URI: " + request.getRequestLine().getUri(), ex); + } + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, + HttpException { + Args.notNull(route, "HTTP route"); + Args.notNull(request, "HTTP request"); + Args.notNull(context, "HTTP context"); + + final HttpRequest original = request.getOriginal(); + URI uri = null; + if (original instanceof HttpUriRequest) { + uri = ((HttpUriRequest) original).getURI(); + } else { + final String uriString = original.getRequestLine().getUri(); + try { + uri = URI.create(uriString); + } catch (final IllegalArgumentException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug("Unable to parse '" + uriString + "' as a valid URI; " + + "request URI and Host header may be inconsistent", ex); + } + } + + } + request.setURI(uri); + + // Re-write request URI if needed + rewriteRequestURI(request, route); + + final HttpParams params = request.getParams(); + HttpHost virtualHost = (HttpHost) params.getParameter(ClientPNames.VIRTUAL_HOST); + // HTTPCLIENT-1092 - add the port if necessary + if (virtualHost != null && virtualHost.getPort() == -1) { + final int port = route.getTargetHost().getPort(); + if (port != -1) { + virtualHost = new HttpHost(virtualHost.getHostName(), port, + virtualHost.getSchemeName()); + } + if (this.log.isDebugEnabled()) { + this.log.debug("Using virtual host" + virtualHost); + } + } + + HttpHost target = null; + if (virtualHost != null) { + target = virtualHost; + } else { + if (uri != null && uri.isAbsolute() && uri.getHost() != null) { + target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()); + } + } + if (target == null) { + target = route.getTargetHost(); + } + + // Get user info from the URI + if (uri != null) { + final String userinfo = uri.getUserInfo(); + if (userinfo != null) { + CredentialsProvider credsProvider = context.getCredentialsProvider(); + if (credsProvider == null) { + credsProvider = new BasicCredentialsProvider(); + context.setCredentialsProvider(credsProvider); + } + credsProvider.setCredentials( + new AuthScope(target), + new UsernamePasswordCredentials(userinfo)); + } + } + + // Run request protocol interceptors + context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target); + context.setAttribute(HttpClientContext.HTTP_ROUTE, route); + context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); + + this.httpProcessor.process(request, context); + + final CloseableHttpResponse response = this.requestExecutor.execute(route, request, + context, execAware); + try { + // Run response protocol interceptors + context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); + this.httpProcessor.process(response, context); + return response; + } catch (final RuntimeException ex) { + response.close(); + throw ex; + } catch (final IOException ex) { + response.close(); + throw ex; + } catch (final HttpException ex) { + response.close(); + throw ex; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RedirectExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RedirectExec.java new file mode 100644 index 000000000..c870662f7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RedirectExec.java @@ -0,0 +1,185 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.auth.AuthScheme; +import ch.boye.httpclientandroidlib.auth.AuthState; +import ch.boye.httpclientandroidlib.client.RedirectException; +import ch.boye.httpclientandroidlib.client.RedirectStrategy; +import ch.boye.httpclientandroidlib.client.config.RequestConfig; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.client.utils.URIUtils; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoutePlanner; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.EntityUtils; + +/** + * Request executor in the request execution chain that is responsible + * for handling of request redirects. + * <p/> + * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 4.3 + */ +@ThreadSafe +public class RedirectExec implements ClientExecChain { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ClientExecChain requestExecutor; + private final RedirectStrategy redirectStrategy; + private final HttpRoutePlanner routePlanner; + + public RedirectExec( + final ClientExecChain requestExecutor, + final HttpRoutePlanner routePlanner, + final RedirectStrategy redirectStrategy) { + super(); + Args.notNull(requestExecutor, "HTTP client request executor"); + Args.notNull(routePlanner, "HTTP route planner"); + Args.notNull(redirectStrategy, "HTTP redirect strategy"); + this.requestExecutor = requestExecutor; + this.routePlanner = routePlanner; + this.redirectStrategy = redirectStrategy; + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + Args.notNull(route, "HTTP route"); + Args.notNull(request, "HTTP request"); + Args.notNull(context, "HTTP context"); + + final List<URI> redirectLocations = context.getRedirectLocations(); + if (redirectLocations != null) { + redirectLocations.clear(); + } + + final RequestConfig config = context.getRequestConfig(); + final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50; + HttpRoute currentRoute = route; + HttpRequestWrapper currentRequest = request; + for (int redirectCount = 0;;) { + final CloseableHttpResponse response = requestExecutor.execute( + currentRoute, currentRequest, context, execAware); + try { + if (config.isRedirectsEnabled() && + this.redirectStrategy.isRedirected(currentRequest, response, context)) { + + if (redirectCount >= maxRedirects) { + throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded"); + } + redirectCount++; + + final HttpRequest redirect = this.redirectStrategy.getRedirect( + currentRequest, response, context); + if (!redirect.headerIterator().hasNext()) { + final HttpRequest original = request.getOriginal(); + redirect.setHeaders(original.getAllHeaders()); + } + currentRequest = HttpRequestWrapper.wrap(redirect); + + if (currentRequest instanceof HttpEntityEnclosingRequest) { + RequestEntityProxy.enhance((HttpEntityEnclosingRequest) currentRequest); + } + + final URI uri = currentRequest.getURI(); + final HttpHost newTarget = URIUtils.extractHost(uri); + if (newTarget == null) { + throw new ProtocolException("Redirect URI does not specify a valid host name: " + + uri); + } + + // Reset virtual host and auth states if redirecting to another host + if (!currentRoute.getTargetHost().equals(newTarget)) { + final AuthState targetAuthState = context.getTargetAuthState(); + if (targetAuthState != null) { + this.log.debug("Resetting target auth state"); + targetAuthState.reset(); + } + final AuthState proxyAuthState = context.getProxyAuthState(); + if (proxyAuthState != null) { + final AuthScheme authScheme = proxyAuthState.getAuthScheme(); + if (authScheme != null && authScheme.isConnectionBased()) { + this.log.debug("Resetting proxy auth state"); + proxyAuthState.reset(); + } + } + } + + currentRoute = this.routePlanner.determineRoute(newTarget, currentRequest, context); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirecting to '" + uri + "' via " + currentRoute); + } + EntityUtils.consume(response.getEntity()); + response.close(); + } else { + return response; + } + } catch (final RuntimeException ex) { + response.close(); + throw ex; + } catch (final IOException ex) { + response.close(); + throw ex; + } catch (final HttpException ex) { + // Protocol exception related to a direct. + // The underlying connection may still be salvaged. + try { + EntityUtils.consume(response.getEntity()); + } catch (final IOException ioex) { + this.log.debug("I/O error while releasing connection", ioex); + } finally { + response.close(); + } + throw ex; + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RequestAbortedException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RequestAbortedException.java new file mode 100644 index 000000000..be58be8f8 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RequestAbortedException.java @@ -0,0 +1,55 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.InterruptedIOException; + +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Signals that the request has been aborted. + * + * @since 4.3 + */ +@Immutable +public class RequestAbortedException extends InterruptedIOException { + + private static final long serialVersionUID = 4973849966012490112L; + + public RequestAbortedException(final String message) { + super(message); + } + + public RequestAbortedException(final String message, final Throwable cause) { + super(message); + if (cause != null) { + initCause(cause); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RequestEntityProxy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RequestEntityProxy.java new file mode 100644 index 000000000..d5b396684 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RequestEntityProxy.java @@ -0,0 +1,137 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; + +/** + * A Proxy class for {@link ch.boye.httpclientandroidlib.HttpEntity} enclosed in a request message. + * + * @since 4.3 + */ +@NotThreadSafe +class RequestEntityProxy implements HttpEntity { + + static void enhance(final HttpEntityEnclosingRequest request) { + final HttpEntity entity = request.getEntity(); + if (entity != null && !entity.isRepeatable() && !isEnhanced(entity)) { + request.setEntity(new RequestEntityProxy(entity)); + } + } + + static boolean isEnhanced(final HttpEntity entity) { + return entity instanceof RequestEntityProxy; + } + + static boolean isRepeatable(final HttpRequest request) { + if (request instanceof HttpEntityEnclosingRequest) { + final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); + if (entity != null) { + if (isEnhanced(entity)) { + final RequestEntityProxy proxy = (RequestEntityProxy) entity; + if (!proxy.isConsumed()) { + return true; + } + } + return entity.isRepeatable(); + } + } + return true; + } + + private final HttpEntity original; + private boolean consumed = false; + + RequestEntityProxy(final HttpEntity original) { + super(); + this.original = original; + } + + public HttpEntity getOriginal() { + return original; + } + + public boolean isConsumed() { + return consumed; + } + + public boolean isRepeatable() { + return original.isRepeatable(); + } + + public boolean isChunked() { + return original.isChunked(); + } + + public long getContentLength() { + return original.getContentLength(); + } + + public Header getContentType() { + return original.getContentType(); + } + + public Header getContentEncoding() { + return original.getContentEncoding(); + } + + public InputStream getContent() throws IOException, IllegalStateException { + return original.getContent(); + } + + public void writeTo(final OutputStream outstream) throws IOException { + consumed = true; + original.writeTo(outstream); + } + + public boolean isStreaming() { + return original.isStreaming(); + } + + @Deprecated + public void consumeContent() throws IOException { + consumed = true; + original.consumeContent(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("RequestEntityProxy{"); + sb.append(original); + sb.append('}'); + return sb.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ResponseEntityProxy.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ResponseEntityProxy.java new file mode 100644 index 000000000..8a5df1cf8 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ResponseEntityProxy.java @@ -0,0 +1,152 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketException; + +import ch.boye.httpclientandroidlib.HttpEntity; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.conn.EofSensorInputStream; +import ch.boye.httpclientandroidlib.conn.EofSensorWatcher; +import ch.boye.httpclientandroidlib.entity.HttpEntityWrapper; + +/** + * A wrapper class for {@link HttpEntity} enclosed in a response message. + * + * @since 4.3 + */ +@NotThreadSafe +class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher { + + private final ConnectionHolder connHolder; + + public static void enchance(final HttpResponse response, final ConnectionHolder connHolder) { + final HttpEntity entity = response.getEntity(); + if (entity != null && entity.isStreaming() && connHolder != null) { + response.setEntity(new ResponseEntityProxy(entity, connHolder)); + } + } + + ResponseEntityProxy(final HttpEntity entity, final ConnectionHolder connHolder) { + super(entity); + this.connHolder = connHolder; + } + + private void cleanup() { + if (this.connHolder != null) { + this.connHolder.abortConnection(); + } + } + + public void releaseConnection() throws IOException { + if (this.connHolder != null) { + try { + if (this.connHolder.isReusable()) { + this.connHolder.releaseConnection(); + } + } finally { + cleanup(); + } + } + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public InputStream getContent() throws IOException { + return new EofSensorInputStream(this.wrappedEntity.getContent(), this); + } + + @Deprecated + @Override + public void consumeContent() throws IOException { + releaseConnection(); + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + try { + this.wrappedEntity.writeTo(outstream); + releaseConnection(); + } finally { + cleanup(); + } + } + + public boolean eofDetected(final InputStream wrapped) throws IOException { + try { + // there may be some cleanup required, such as + // reading trailers after the response body: + wrapped.close(); + releaseConnection(); + } finally { + cleanup(); + } + return false; + } + + public boolean streamClosed(final InputStream wrapped) throws IOException { + try { + final boolean open = connHolder != null && !connHolder.isReleased(); + // this assumes that closing the stream will + // consume the remainder of the response body: + try { + wrapped.close(); + releaseConnection(); + } catch (final SocketException ex) { + if (open) { + throw ex; + } + } + } finally { + cleanup(); + } + return false; + } + + public boolean streamAbort(final InputStream wrapped) throws IOException { + cleanup(); + return false; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ResponseEntityProxy{"); + sb.append(wrappedEntity); + sb.append('}'); + return sb.toString(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RetryExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RetryExec.java new file mode 100644 index 000000000..ab78e3a83 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/RetryExec.java @@ -0,0 +1,126 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.HttpRequestRetryHandler; +import ch.boye.httpclientandroidlib.client.NonRepeatableRequestException; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Request executor in the request execution chain that is responsible + * for making a decision whether a request failed due to an I/O error + * should be re-executed. + * <p/> + * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 4.3 + */ +@Immutable +public class RetryExec implements ClientExecChain { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ClientExecChain requestExecutor; + private final HttpRequestRetryHandler retryHandler; + + public RetryExec( + final ClientExecChain requestExecutor, + final HttpRequestRetryHandler retryHandler) { + Args.notNull(requestExecutor, "HTTP request executor"); + Args.notNull(retryHandler, "HTTP request retry handler"); + this.requestExecutor = requestExecutor; + this.retryHandler = retryHandler; + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + Args.notNull(route, "HTTP route"); + Args.notNull(request, "HTTP request"); + Args.notNull(context, "HTTP context"); + final Header[] origheaders = request.getAllHeaders(); + for (int execCount = 1;; execCount++) { + try { + return this.requestExecutor.execute(route, request, context, execAware); + } catch (final IOException ex) { + if (execAware != null && execAware.isAborted()) { + this.log.debug("Request has been aborted"); + throw ex; + } + if (retryHandler.retryRequest(ex, execCount, context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when processing request to " + + route + + ": " + + ex.getMessage()); + } + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + if (!RequestEntityProxy.isRepeatable(request)) { + this.log.debug("Cannot retry non-repeatable request"); + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity", ex); + } + request.setHeaders(origheaders); + if (this.log.isInfoEnabled()) { + this.log.info("Retrying request to " + route); + } + } else { + if (ex instanceof NoHttpResponseException) { + final NoHttpResponseException updatedex = new NoHttpResponseException( + route.getTargetHost().toHostString() + " failed to respond"); + updatedex.setStackTrace(ex.getStackTrace()); + throw updatedex; + } else { + throw ex; + } + } + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ServiceUnavailableRetryExec.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ServiceUnavailableRetryExec.java new file mode 100644 index 000000000..1382d998a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/ServiceUnavailableRetryExec.java @@ -0,0 +1,108 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import java.io.IOException; +import java.io.InterruptedIOException; + +import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; +/* LogFactory removed by HttpClient for Android script. */ +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.client.ServiceUnavailableRetryStrategy; +import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; +import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware; +import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper; +import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; +import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Request executor in the request execution chain that is responsible + * for making a decision whether a request that received a non-2xx response + * from the target server should be re-executed. + * <p/> + * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 4.3 + */ +@Immutable +public class ServiceUnavailableRetryExec implements ClientExecChain { + + public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); + + private final ClientExecChain requestExecutor; + private final ServiceUnavailableRetryStrategy retryStrategy; + + public ServiceUnavailableRetryExec( + final ClientExecChain requestExecutor, + final ServiceUnavailableRetryStrategy retryStrategy) { + super(); + Args.notNull(requestExecutor, "HTTP request executor"); + Args.notNull(retryStrategy, "Retry strategy"); + this.requestExecutor = requestExecutor; + this.retryStrategy = retryStrategy; + } + + public CloseableHttpResponse execute( + final HttpRoute route, + final HttpRequestWrapper request, + final HttpClientContext context, + final HttpExecutionAware execAware) throws IOException, HttpException { + final Header[] origheaders = request.getAllHeaders(); + for (int c = 1;; c++) { + final CloseableHttpResponse response = this.requestExecutor.execute( + route, request, context, execAware); + try { + if (this.retryStrategy.retryRequest(response, c, context)) { + response.close(); + final long nextInterval = this.retryStrategy.getRetryInterval(); + if (nextInterval > 0) { + try { + this.log.trace("Wait for " + nextInterval); + Thread.sleep(nextInterval); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + } + request.setHeaders(origheaders); + } else { + return response; + } + } catch (final RuntimeException ex) { + response.close(); + throw ex; + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/TunnelRefusedException.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/TunnelRefusedException.java new file mode 100644 index 000000000..5545c7b84 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/TunnelRefusedException.java @@ -0,0 +1,55 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.execchain; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; + +/** + * Signals that the tunnel request was rejected by the proxy host. + * + * @since 4.0 + */ +@Immutable +public class TunnelRefusedException extends HttpException { + + private static final long serialVersionUID = -8646722842745617323L; + + private final HttpResponse response; + + public TunnelRefusedException(final String message, final HttpResponse response) { + super(message); + this.response = response; + } + + public HttpResponse getResponse() { + return this.response; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/package-info.java new file mode 100644 index 000000000..740a9eec7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/execchain/package-info.java @@ -0,0 +1,31 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * HTTP request execution chain APIs. + */ +package ch.boye.httpclientandroidlib.impl.execchain; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractMessageParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractMessageParser.java new file mode 100644 index 000000000..cd7aceb52 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractMessageParser.java @@ -0,0 +1,284 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.MessageConstraintException; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.ProtocolException; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineParser; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.params.HttpParamConfig; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract base class for HTTP message parsers that obtain input from + * an instance of {@link SessionInputBuffer}. + * + * @since 4.0 + */ +@SuppressWarnings("deprecation") +@NotThreadSafe +public abstract class AbstractMessageParser<T extends HttpMessage> implements HttpMessageParser<T> { + + private static final int HEAD_LINE = 0; + private static final int HEADERS = 1; + + private final SessionInputBuffer sessionBuffer; + private final MessageConstraints messageConstraints; + private final List<CharArrayBuffer> headerLines; + protected final LineParser lineParser; + + private int state; + private T message; + + /** + * Creates an instance of AbstractMessageParser. + * + * @param buffer the session input buffer. + * @param parser the line parser. + * @param params HTTP parameters. + * + * @deprecated (4.3) use {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer, + * LineParser, MessageConstraints)} + */ + @Deprecated + public AbstractMessageParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpParams params) { + super(); + Args.notNull(buffer, "Session input buffer"); + Args.notNull(params, "HTTP parameters"); + this.sessionBuffer = buffer; + this.messageConstraints = HttpParamConfig.getMessageConstraints(params); + this.lineParser = (parser != null) ? parser : BasicLineParser.INSTANCE; + this.headerLines = new ArrayList<CharArrayBuffer>(); + this.state = HEAD_LINE; + } + + /** + * Creates new instance of AbstractMessageParser. + * + * @param buffer the session input buffer. + * @param lineParser the line parser. If <code>null</code> {@link BasicLineParser#INSTANCE} + * will be used. + * @param constraints the message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * + * @since 4.3 + */ + public AbstractMessageParser( + final SessionInputBuffer buffer, + final LineParser lineParser, + final MessageConstraints constraints) { + super(); + this.sessionBuffer = Args.notNull(buffer, "Session input buffer"); + this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE; + this.messageConstraints = constraints != null ? constraints : MessageConstraints.DEFAULT; + this.headerLines = new ArrayList<CharArrayBuffer>(); + this.state = HEAD_LINE; + } + + /** + * Parses HTTP headers from the data receiver stream according to the generic + * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. + * + * @param inbuffer Session input buffer + * @param maxHeaderCount maximum number of headers allowed. If the number + * of headers received from the data stream exceeds maxCount value, an + * IOException will be thrown. Setting this parameter to a negative value + * or zero will disable the check. + * @param maxLineLen maximum number of characters for a header line, + * including the continuation lines. Setting this parameter to a negative + * value or zero will disable the check. + * @return array of HTTP headers + * @param parser line parser to use. Can be <code>null</code>, in which case + * the default implementation of this interface will be used. + * + * @throws IOException in case of an I/O error + * @throws HttpException in case of HTTP protocol violation + */ + public static Header[] parseHeaders( + final SessionInputBuffer inbuffer, + final int maxHeaderCount, + final int maxLineLen, + final LineParser parser) throws HttpException, IOException { + final List<CharArrayBuffer> headerLines = new ArrayList<CharArrayBuffer>(); + return parseHeaders(inbuffer, maxHeaderCount, maxLineLen, + parser != null ? parser : BasicLineParser.INSTANCE, + headerLines); + } + + /** + * Parses HTTP headers from the data receiver stream according to the generic + * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. + * + * @param inbuffer Session input buffer + * @param maxHeaderCount maximum number of headers allowed. If the number + * of headers received from the data stream exceeds maxCount value, an + * IOException will be thrown. Setting this parameter to a negative value + * or zero will disable the check. + * @param maxLineLen maximum number of characters for a header line, + * including the continuation lines. Setting this parameter to a negative + * value or zero will disable the check. + * @param parser line parser to use. + * @param headerLines List of header lines. This list will be used to store + * intermediate results. This makes it possible to resume parsing of + * headers in case of a {@link java.io.InterruptedIOException}. + * + * @return array of HTTP headers + * + * @throws IOException in case of an I/O error + * @throws HttpException in case of HTTP protocol violation + * + * @since 4.1 + */ + public static Header[] parseHeaders( + final SessionInputBuffer inbuffer, + final int maxHeaderCount, + final int maxLineLen, + final LineParser parser, + final List<CharArrayBuffer> headerLines) throws HttpException, IOException { + Args.notNull(inbuffer, "Session input buffer"); + Args.notNull(parser, "Line parser"); + Args.notNull(headerLines, "Header line list"); + + CharArrayBuffer current = null; + CharArrayBuffer previous = null; + for (;;) { + if (current == null) { + current = new CharArrayBuffer(64); + } else { + current.clear(); + } + final int l = inbuffer.readLine(current); + if (l == -1 || current.length() < 1) { + break; + } + // Parse the header name and value + // Check for folded headers first + // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 + // discussion on folded headers + if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) { + // we have continuation folded header + // so append value + int i = 0; + while (i < current.length()) { + final char ch = current.charAt(i); + if (ch != ' ' && ch != '\t') { + break; + } + i++; + } + if (maxLineLen > 0 + && previous.length() + 1 + current.length() - i > maxLineLen) { + throw new MessageConstraintException("Maximum line length limit exceeded"); + } + previous.append(' '); + previous.append(current, i, current.length() - i); + } else { + headerLines.add(current); + previous = current; + current = null; + } + if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) { + throw new MessageConstraintException("Maximum header count exceeded"); + } + } + final Header[] headers = new Header[headerLines.size()]; + for (int i = 0; i < headerLines.size(); i++) { + final CharArrayBuffer buffer = headerLines.get(i); + try { + headers[i] = parser.parseHeader(buffer); + } catch (final ParseException ex) { + throw new ProtocolException(ex.getMessage()); + } + } + return headers; + } + + /** + * Subclasses must override this method to generate an instance of + * {@link HttpMessage} based on the initial input from the session buffer. + * <p> + * Usually this method is expected to read just the very first line or + * the very first valid from the data stream and based on the input generate + * an appropriate instance of {@link HttpMessage}. + * + * @param sessionBuffer the session input buffer. + * @return HTTP message based on the input from the session buffer. + * @throws IOException in case of an I/O error. + * @throws HttpException in case of HTTP protocol violation. + * @throws ParseException in case of a parse error. + */ + protected abstract T parseHead(SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException; + + public T parse() throws IOException, HttpException { + final int st = this.state; + switch (st) { + case HEAD_LINE: + try { + this.message = parseHead(this.sessionBuffer); + } catch (final ParseException px) { + throw new ProtocolException(px.getMessage(), px); + } + this.state = HEADERS; + //$FALL-THROUGH$ + case HEADERS: + final Header[] headers = AbstractMessageParser.parseHeaders( + this.sessionBuffer, + this.messageConstraints.getMaxHeaderCount(), + this.messageConstraints.getMaxLineLength(), + this.lineParser, + this.headerLines); + this.message.setHeaders(headers); + final T result = this.message; + this.message = null; + this.headerLines.clear(); + this.state = HEAD_LINE; + return result; + default: + throw new IllegalStateException("Inconsistent parser state"); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractMessageWriter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractMessageWriter.java new file mode 100644 index 000000000..56caf58d5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractMessageWriter.java @@ -0,0 +1,119 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HeaderIterator; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineFormatter; +import ch.boye.httpclientandroidlib.message.LineFormatter; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract base class for HTTP message writers that serialize output to + * an instance of {@link SessionOutputBuffer}. + * + * @since 4.0 + */ +@SuppressWarnings("deprecation") +@NotThreadSafe +public abstract class AbstractMessageWriter<T extends HttpMessage> implements HttpMessageWriter<T> { + + protected final SessionOutputBuffer sessionBuffer; + protected final CharArrayBuffer lineBuf; + protected final LineFormatter lineFormatter; + + /** + * Creates an instance of AbstractMessageWriter. + * + * @param buffer the session output buffer. + * @param formatter the line formatter. + * @param params HTTP parameters. + * + * @deprecated (4.3) use + * {@link AbstractMessageWriter#AbstractMessageWriter(SessionOutputBuffer, LineFormatter)} + */ + @Deprecated + public AbstractMessageWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(); + Args.notNull(buffer, "Session input buffer"); + this.sessionBuffer = buffer; + this.lineBuf = new CharArrayBuffer(128); + this.lineFormatter = (formatter != null) ? formatter : BasicLineFormatter.INSTANCE; + } + + /** + * Creates an instance of AbstractMessageWriter. + * + * @param buffer the session output buffer. + * @param formatter the line formatter If <code>null</code> {@link BasicLineFormatter#INSTANCE} + * will be used. + * + * @since 4.3 + */ + public AbstractMessageWriter( + final SessionOutputBuffer buffer, + final LineFormatter formatter) { + super(); + this.sessionBuffer = Args.notNull(buffer, "Session input buffer"); + this.lineFormatter = (formatter != null) ? formatter : BasicLineFormatter.INSTANCE; + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * Subclasses must override this method to write out the first header line + * based on the {@link HttpMessage} passed as a parameter. + * + * @param message the message whose first line is to be written out. + * @throws IOException in case of an I/O error. + */ + protected abstract void writeHeadLine(T message) throws IOException; + + public void write(final T message) throws IOException, HttpException { + Args.notNull(message, "HTTP message"); + writeHeadLine(message); + for (final HeaderIterator it = message.headerIterator(); it.hasNext(); ) { + final Header header = it.nextHeader(); + this.sessionBuffer.writeLine + (lineFormatter.formatHeader(this.lineBuf, header)); + } + this.lineBuf.clear(); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java new file mode 100644 index 000000000..e53e07a30 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java @@ -0,0 +1,401 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.CoreProtocolPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; +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. + * <p> + * {@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. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link SessionInputBufferImpl} + */ +@NotThreadSafe +@Deprecated +public abstract class AbstractSessionInputBuffer implements SessionInputBuffer, BufferInfo { + + private InputStream instream; + private byte[] buffer; + private ByteArrayBuffer linebuffer; + private Charset charset; + private boolean ascii; + private int maxLineLen; + private int minChunkLimit; + private HttpTransportMetricsImpl metrics; + private CodingErrorAction onMalformedCharAction; + private CodingErrorAction onUnmappableCharAction; + + private int bufferpos; + private int bufferlen; + private CharsetDecoder decoder; + private CharBuffer cbuf; + + public AbstractSessionInputBuffer() { + } + + /** + * 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, final int buffersize, final HttpParams params) { + Args.notNull(instream, "Input stream"); + Args.notNegative(buffersize, "Buffer size"); + Args.notNull(params, "HTTP parameters"); + this.instream = instream; + this.buffer = new byte[buffersize]; + this.bufferpos = 0; + this.bufferlen = 0; + this.linebuffer = new ByteArrayBuffer(buffersize); + final String charset = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET); + this.charset = charset != null ? Charset.forName(charset) : Consts.ASCII; + this.ascii = this.charset.equals(Consts.ASCII); + this.decoder = null; + this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1); + this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512); + this.metrics = createTransportMetrics(); + final CodingErrorAction a1 = (CodingErrorAction) params.getParameter( + CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION); + this.onMalformedCharAction = a1 != null ? a1 : CodingErrorAction.REPORT; + final CodingErrorAction a2 = (CodingErrorAction) params.getParameter( + CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION); + this.onUnmappableCharAction = a2 != null? a2 : CodingErrorAction.REPORT; + } + + /** + * @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) { + final 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; + } + final int l; + final int off = this.bufferlen; + final 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; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + return this.buffer[this.bufferpos++] & 0xff; + } + + public int read(final byte[] b, final int off, final int len) throws IOException { + if (b == null) { + return 0; + } + if (hasBufferedData()) { + final 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) { + final 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()) { + final int noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + final 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 <code>-1</code> is returned. This method blocks until input + * data is available, end of file is detected, or an exception is thrown. + * <p> + * 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 { + Args.notNull(charbuffer, "Char array buffer"); + int noRead = 0; + boolean retry = true; + while (retry) { + // attempt to find end of line (LF) + final 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; + final 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()) { + final 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, + * <code>null</code> is returned. This method blocks until input data is + * available, end of file is detected, or an exception is thrown. + * <p> + * 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 len = this.linebuffer.length(); + if (len > 0) { + if (this.linebuffer.byteAt(len - 1) == HTTP.LF) { + len--; + } + // discard CR if found + if (len > 0) { + if (this.linebuffer.byteAt(len - 1) == HTTP.CR) { + len--; + } + } + } + if (this.ascii) { + charbuffer.append(this.linebuffer, 0, len); + } else { + final ByteBuffer bbuf = ByteBuffer.wrap(this.linebuffer.buffer(), 0, len); + len = appendDecoded(charbuffer, bbuf); + } + this.linebuffer.clear(); + return len; + } + + private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position) + throws IOException { + final int off = this.bufferpos; + int i = position; + this.bufferpos = i + 1; + if (i > off && this.buffer[i - 1] == HTTP.CR) { + // skip CR if found + i--; + } + int len = i - off; + if (this.ascii) { + charbuffer.append(this.buffer, off, len); + } else { + final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len); + len = appendDecoded(charbuffer, bbuf); + } + return len; + } + + private int appendDecoded( + final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException { + if (!bbuf.hasRemaining()) { + return 0; + } + if (this.decoder == null) { + this.decoder = this.charset.newDecoder(); + this.decoder.onMalformedInput(this.onMalformedCharAction); + this.decoder.onUnmappableCharacter(this.onUnmappableCharAction); + } + if (this.cbuf == null) { + this.cbuf = CharBuffer.allocate(1024); + } + this.decoder.reset(); + int len = 0; + while (bbuf.hasRemaining()) { + final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true); + len += handleDecodingResult(result, charbuffer, bbuf); + } + final CoderResult result = this.decoder.flush(this.cbuf); + len += handleDecodingResult(result, charbuffer, bbuf); + this.cbuf.clear(); + return len; + } + + private int handleDecodingResult( + final CoderResult result, + final CharArrayBuffer charbuffer, + final ByteBuffer bbuf) throws IOException { + if (result.isError()) { + result.throwException(); + } + this.cbuf.flip(); + final int len = this.cbuf.remaining(); + while (this.cbuf.hasRemaining()) { + charbuffer.append(this.cbuf.get()); + } + this.cbuf.compact(); + return len; + } + + public String readLine() throws IOException { + final CharArrayBuffer charbuffer = new CharArrayBuffer(64); + final int l = readLine(charbuffer); + if (l != -1) { + return charbuffer.toString(); + } else { + return null; + } + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionOutputBuffer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionOutputBuffer.java new file mode 100644 index 000000000..a2c87d084 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionOutputBuffer.java @@ -0,0 +1,307 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +import ch.boye.httpclientandroidlib.Consts; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.CoreProtocolPNames; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.ByteArrayBuffer; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract base class for session output buffers that stream data to + * an arbitrary {@link OutputStream}. This class buffers small chunks of + * output data in an internal byte array for optimal output performance. + * </p> + * {@link #writeLine(CharArrayBuffer)} and {@link #writeLine(String)} methods + * of this class use CR-LF as a line delimiter. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link SessionOutputBufferImpl} + */ +@NotThreadSafe +@Deprecated +public abstract class AbstractSessionOutputBuffer implements SessionOutputBuffer, BufferInfo { + + private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF}; + + private OutputStream outstream; + private ByteArrayBuffer buffer; + private Charset charset; + private boolean ascii; + private int minChunkLimit; + private HttpTransportMetricsImpl metrics; + private CodingErrorAction onMalformedCharAction; + private CodingErrorAction onUnmappableCharAction; + + private CharsetEncoder encoder; + private ByteBuffer bbuf; + + protected AbstractSessionOutputBuffer( + final OutputStream outstream, + final int buffersize, + final Charset charset, + final int minChunkLimit, + final CodingErrorAction malformedCharAction, + final CodingErrorAction unmappableCharAction) { + super(); + Args.notNull(outstream, "Input stream"); + Args.notNegative(buffersize, "Buffer size"); + this.outstream = outstream; + this.buffer = new ByteArrayBuffer(buffersize); + this.charset = charset != null ? charset : Consts.ASCII; + this.ascii = this.charset.equals(Consts.ASCII); + this.encoder = null; + this.minChunkLimit = minChunkLimit >= 0 ? minChunkLimit : 512; + this.metrics = createTransportMetrics(); + this.onMalformedCharAction = malformedCharAction != null ? malformedCharAction : + CodingErrorAction.REPORT; + this.onUnmappableCharAction = unmappableCharAction != null? unmappableCharAction : + CodingErrorAction.REPORT; + } + + public AbstractSessionOutputBuffer() { + } + + protected void init(final OutputStream outstream, final int buffersize, final HttpParams params) { + Args.notNull(outstream, "Input stream"); + Args.notNegative(buffersize, "Buffer size"); + Args.notNull(params, "HTTP parameters"); + this.outstream = outstream; + this.buffer = new ByteArrayBuffer(buffersize); + final String charset = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET); + this.charset = charset != null ? Charset.forName(charset) : Consts.ASCII; + this.ascii = this.charset.equals(Consts.ASCII); + this.encoder = null; + this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512); + this.metrics = createTransportMetrics(); + final CodingErrorAction a1 = (CodingErrorAction) params.getParameter( + CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION); + this.onMalformedCharAction = a1 != null ? a1 : CodingErrorAction.REPORT; + final CodingErrorAction a2 = (CodingErrorAction) params.getParameter( + CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION); + this.onUnmappableCharAction = a2 != null? a2 : CodingErrorAction.REPORT; + } + + /** + * @since 4.1 + */ + protected HttpTransportMetricsImpl createTransportMetrics() { + return new HttpTransportMetricsImpl(); + } + + /** + * @since 4.1 + */ + public int capacity() { + return this.buffer.capacity(); + } + + /** + * @since 4.1 + */ + public int length() { + return this.buffer.length(); + } + + /** + * @since 4.1 + */ + public int available() { + return capacity() - length(); + } + + protected void flushBuffer() throws IOException { + final int len = this.buffer.length(); + if (len > 0) { + this.outstream.write(this.buffer.buffer(), 0, len); + this.buffer.clear(); + this.metrics.incrementBytesTransferred(len); + } + } + + public void flush() throws IOException { + flushBuffer(); + this.outstream.flush(); + } + + public void write(final byte[] b, final int off, final int len) throws IOException { + if (b == null) { + return; + } + // Do not want to buffer large-ish chunks + // if the byte array is larger then MIN_CHUNK_LIMIT + // write it directly to the output stream + if (len > this.minChunkLimit || len > this.buffer.capacity()) { + // flush the buffer + flushBuffer(); + // write directly to the out stream + this.outstream.write(b, off, len); + this.metrics.incrementBytesTransferred(len); + } else { + // Do not let the buffer grow unnecessarily + final int freecapacity = this.buffer.capacity() - this.buffer.length(); + if (len > freecapacity) { + // flush the buffer + flushBuffer(); + } + // buffer + this.buffer.append(b, off, len); + } + } + + public void write(final byte[] b) throws IOException { + if (b == null) { + return; + } + write(b, 0, b.length); + } + + public void write(final int b) throws IOException { + if (this.buffer.isFull()) { + flushBuffer(); + } + this.buffer.append(b); + } + + /** + * Writes characters from the specified string followed by a line delimiter + * to this session buffer. + * <p> + * This method uses CR-LF as a line delimiter. + * + * @param s the line. + * @exception IOException if an I/O error occurs. + */ + public void writeLine(final String s) throws IOException { + if (s == null) { + return; + } + if (s.length() > 0) { + if (this.ascii) { + for (int i = 0; i < s.length(); i++) { + write(s.charAt(i)); + } + } else { + final CharBuffer cbuf = CharBuffer.wrap(s); + writeEncoded(cbuf); + } + } + write(CRLF); + } + + /** + * Writes characters from the specified char array followed by a line + * delimiter to this session buffer. + * <p> + * This method uses CR-LF as a line delimiter. + * + * @param charbuffer the buffer containing chars of the line. + * @exception IOException if an I/O error occurs. + */ + public void writeLine(final CharArrayBuffer charbuffer) throws IOException { + if (charbuffer == null) { + return; + } + if (this.ascii) { + int off = 0; + int remaining = charbuffer.length(); + while (remaining > 0) { + int chunk = this.buffer.capacity() - this.buffer.length(); + chunk = Math.min(chunk, remaining); + if (chunk > 0) { + this.buffer.append(charbuffer, off, chunk); + } + if (this.buffer.isFull()) { + flushBuffer(); + } + off += chunk; + remaining -= chunk; + } + } else { + final CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length()); + writeEncoded(cbuf); + } + write(CRLF); + } + + private void writeEncoded(final CharBuffer cbuf) throws IOException { + if (!cbuf.hasRemaining()) { + return; + } + if (this.encoder == null) { + this.encoder = this.charset.newEncoder(); + this.encoder.onMalformedInput(this.onMalformedCharAction); + this.encoder.onUnmappableCharacter(this.onUnmappableCharAction); + } + if (this.bbuf == null) { + this.bbuf = ByteBuffer.allocate(1024); + } + this.encoder.reset(); + while (cbuf.hasRemaining()) { + final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true); + handleEncodingResult(result); + } + final CoderResult result = this.encoder.flush(this.bbuf); + handleEncodingResult(result); + this.bbuf.clear(); + } + + private void handleEncodingResult(final CoderResult result) throws IOException { + if (result.isError()) { + result.throwException(); + } + this.bbuf.flip(); + while (this.bbuf.hasRemaining()) { + write(this.bbuf.get()); + } + this.bbuf.compact(); + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedInputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedInputStream.java new file mode 100644 index 000000000..447bd676d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedInputStream.java @@ -0,0 +1,301 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.Header; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.MalformedChunkCodingException; +import ch.boye.httpclientandroidlib.TruncatedChunkException; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Implements chunked transfer coding. The content is received in small chunks. + * Entities transferred using this input stream can be of unlimited length. + * After the stream is read to the end, it provides access to the trailers, + * if any. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * + * + * @since 4.0 + * + */ +@NotThreadSafe +public class ChunkedInputStream extends InputStream { + + private static final int CHUNK_LEN = 1; + private static final int CHUNK_DATA = 2; + private static final int CHUNK_CRLF = 3; + + private static final int BUFFER_SIZE = 2048; + + /** The session input buffer */ + private final SessionInputBuffer in; + + private final CharArrayBuffer buffer; + + private int state; + + /** The chunk size */ + private int chunkSize; + + /** The current position within the current chunk */ + private int pos; + + /** True if we've reached the end of stream */ + private boolean eof = false; + + /** True if this stream is closed */ + private boolean closed = false; + + private Header[] footers = new Header[] {}; + + /** + * Wraps session input stream and reads chunk coded input. + * + * @param in The session input buffer + */ + public ChunkedInputStream(final SessionInputBuffer in) { + super(); + this.in = Args.notNull(in, "Session input buffer"); + this.pos = 0; + this.buffer = new CharArrayBuffer(16); + this.state = CHUNK_LEN; + } + + @Override + public int available() throws IOException { + if (this.in instanceof BufferInfo) { + final int len = ((BufferInfo) this.in).length(); + return Math.min(len, this.chunkSize - this.pos); + } else { + return 0; + } + } + + /** + * <p> Returns all the data in a chunked stream in coalesced form. A chunk + * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 + * is detected.</p> + * + * <p> Trailer headers are read automatically at the end of the stream and + * can be obtained with the getResponseFooters() method.</p> + * + * @return -1 of the end of the stream has been reached or the next data + * byte + * @throws IOException in case of an I/O error + */ + @Override + public int read() throws IOException { + if (this.closed) { + throw new IOException("Attempted read from closed stream."); + } + if (this.eof) { + return -1; + } + if (state != CHUNK_DATA) { + nextChunk(); + if (this.eof) { + return -1; + } + } + final int b = in.read(); + if (b != -1) { + pos++; + if (pos >= chunkSize) { + state = CHUNK_CRLF; + } + } + return b; + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @param off The offset into the byte array at which bytes will start to be + * placed. + * @param len the maximum number of bytes that can be returned. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @throws IOException in case of an I/O error + */ + @Override + public int read (final byte[] b, final int off, final int len) throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (eof) { + return -1; + } + if (state != CHUNK_DATA) { + nextChunk(); + if (eof) { + return -1; + } + } + final int bytesRead = in.read(b, off, Math.min(len, chunkSize - pos)); + if (bytesRead != -1) { + pos += bytesRead; + if (pos >= chunkSize) { + state = CHUNK_CRLF; + } + return bytesRead; + } else { + eof = true; + throw new TruncatedChunkException("Truncated chunk " + + "( expected size: " + chunkSize + + "; actual size: " + pos + ")"); + } + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @throws IOException in case of an I/O error + */ + @Override + public int read (final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the next chunk. + * @throws IOException in case of an I/O error + */ + private void nextChunk() throws IOException { + chunkSize = getChunkSize(); + if (chunkSize < 0) { + throw new MalformedChunkCodingException("Negative chunk size"); + } + state = CHUNK_DATA; + pos = 0; + if (chunkSize == 0) { + eof = true; + parseTrailerHeaders(); + } + } + + /** + * Expects the stream to start with a chunksize in hex with optional + * comments after a semicolon. The line must end with a CRLF: "a3; some + * comment\r\n" Positions the stream at the start of the next line. + */ + private int getChunkSize() throws IOException { + final int st = this.state; + switch (st) { + case CHUNK_CRLF: + this.buffer.clear(); + final int bytesRead1 = this.in.readLine(this.buffer); + if (bytesRead1 == -1) { + return 0; + } + if (!this.buffer.isEmpty()) { + throw new MalformedChunkCodingException( + "Unexpected content at the end of chunk"); + } + state = CHUNK_LEN; + //$FALL-THROUGH$ + case CHUNK_LEN: + this.buffer.clear(); + final int bytesRead2 = this.in.readLine(this.buffer); + if (bytesRead2 == -1) { + return 0; + } + int separator = this.buffer.indexOf(';'); + if (separator < 0) { + separator = this.buffer.length(); + } + try { + return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16); + } catch (final NumberFormatException e) { + throw new MalformedChunkCodingException("Bad chunk header"); + } + default: + throw new IllegalStateException("Inconsistent codec state"); + } + } + + /** + * Reads and stores the Trailer headers. + * @throws IOException in case of an I/O error + */ + private void parseTrailerHeaders() throws IOException { + try { + this.footers = AbstractMessageParser.parseHeaders + (in, -1, -1, null); + } catch (final HttpException ex) { + final IOException ioe = new MalformedChunkCodingException("Invalid footer: " + + ex.getMessage()); + ioe.initCause(ex); + throw ioe; + } + } + + /** + * Upon close, this reads the remainder of the chunked message, + * leaving the underlying socket at a position to start reading the + * next response without scanning. + * @throws IOException in case of an I/O error + */ + @Override + public void close() throws IOException { + if (!closed) { + try { + if (!eof) { + // read and discard the remainder of the message + final byte buff[] = new byte[BUFFER_SIZE]; + while (read(buff) >= 0) { + } + } + } finally { + eof = true; + closed = true; + } + } + } + + public Header[] getFooters() { + return this.footers.clone(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedOutputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedOutputStream.java new file mode 100644 index 000000000..828df923c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedOutputStream.java @@ -0,0 +1,208 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; + +/** + * Implements chunked transfer coding. The content is sent in small chunks. + * Entities transferred using this output stream can be of unlimited length. + * Writes are buffered to an internal buffer (2048 default size). + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, the stream will be marked as closed and no further + * output will be permitted. + * + * + * @since 4.0 + */ +@NotThreadSafe +public class ChunkedOutputStream extends OutputStream { + + // ----------------------------------------------------- Instance Variables + private final SessionOutputBuffer out; + + private final byte[] cache; + + private int cachePosition = 0; + + private boolean wroteLastChunk = false; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Wraps a session output buffer and chunk-encodes the output. + * + * @param out The session output buffer + * @param bufferSize The minimum chunk size (excluding last chunk) + * @throws IOException not thrown + * + * @deprecated (4.3) use {@link ChunkedOutputStream#ChunkedOutputStream(int, SessionOutputBuffer)} + */ + @Deprecated + public ChunkedOutputStream(final SessionOutputBuffer out, final int bufferSize) + throws IOException { + this(bufferSize, out); + } + + /** + * Wraps a session output buffer and chunks the output. The default buffer + * size of 2048 was chosen because the chunk overhead is less than 0.5% + * + * @param out the output buffer to wrap + * @throws IOException not thrown + * + * @deprecated (4.3) use {@link ChunkedOutputStream#ChunkedOutputStream(int, SessionOutputBuffer)} + */ + @Deprecated + public ChunkedOutputStream(final SessionOutputBuffer out) + throws IOException { + this(2048, out); + } + + /** + * Wraps a session output buffer and chunk-encodes the output. + * + * @param bufferSize The minimum chunk size (excluding last chunk) + * @param out The session output buffer + */ + public ChunkedOutputStream(final int bufferSize, final SessionOutputBuffer out) { + super(); + this.cache = new byte[bufferSize]; + this.out = out; + } + + /** + * Writes the cache out onto the underlying stream + */ + protected void flushCache() throws IOException { + if (this.cachePosition > 0) { + this.out.writeLine(Integer.toHexString(this.cachePosition)); + this.out.write(this.cache, 0, this.cachePosition); + this.out.writeLine(""); + this.cachePosition = 0; + } + } + + /** + * Writes the cache and bufferToAppend to the underlying stream + * as one large chunk + */ + protected void flushCacheWithAppend(final byte bufferToAppend[], final int off, final int len) throws IOException { + this.out.writeLine(Integer.toHexString(this.cachePosition + len)); + this.out.write(this.cache, 0, this.cachePosition); + this.out.write(bufferToAppend, off, len); + this.out.writeLine(""); + this.cachePosition = 0; + } + + protected void writeClosingChunk() throws IOException { + // Write the final chunk. + this.out.writeLine("0"); + this.out.writeLine(""); + } + + // ----------------------------------------------------------- Public Methods + /** + * Must be called to ensure the internal cache is flushed and the closing + * chunk is written. + * @throws IOException in case of an I/O error + */ + public void finish() throws IOException { + if (!this.wroteLastChunk) { + flushCache(); + writeClosingChunk(); + this.wroteLastChunk = true; + } + } + + // -------------------------------------------- OutputStream Methods + @Override + public void write(final int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.cache[this.cachePosition] = (byte) b; + this.cachePosition++; + if (this.cachePosition == this.cache.length) { + flushCache(); + } + } + + /** + * Writes the array. If the array does not fit within the buffer, it is + * not split, but rather written out as one large chunk. + */ + @Override + public void write(final byte b[]) throws IOException { + write(b, 0, b.length); + } + + /** + * Writes the array. If the array does not fit within the buffer, it is + * not split, but rather written out as one large chunk. + */ + @Override + public void write(final byte src[], final int off, final int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (len >= this.cache.length - this.cachePosition) { + flushCacheWithAppend(src, off, len); + } else { + System.arraycopy(src, off, cache, this.cachePosition, len); + this.cachePosition += len; + } + } + + /** + * Flushes the content buffer and the underlying stream. + */ + @Override + public void flush() throws IOException { + flushCache(); + this.out.flush(); + } + + /** + * Finishes writing to the underlying stream, but does NOT close the underlying stream. + */ + @Override + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + finish(); + this.out.flush(); + } + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ContentLengthInputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ContentLengthInputStream.java new file mode 100644 index 000000000..65a271cb8 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ContentLengthInputStream.java @@ -0,0 +1,232 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.ConnectionClosedException; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Input stream that cuts off after a defined number of bytes. This class + * is used to receive content of HTTP messages where the end of the content + * entity is determined by the value of the <code>Content-Length header</code>. + * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} + * long. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its limit on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * + * + * @since 4.0 + */ +@NotThreadSafe +public class ContentLengthInputStream extends InputStream { + + private static final int BUFFER_SIZE = 2048; + /** + * The maximum number of bytes that can be read from the stream. Subsequent + * read operations will return -1. + */ + private final long contentLength; + + /** The current position */ + private long pos = 0; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Wrapped input stream that all calls are delegated to. + */ + private SessionInputBuffer in = null; + + /** + * Wraps a session input buffer and cuts off output after a defined number + * of bytes. + * + * @param in The session input buffer + * @param contentLength The maximum number of bytes that can be read from + * the stream. Subsequent read operations will return -1. + */ + public ContentLengthInputStream(final SessionInputBuffer in, final long contentLength) { + super(); + this.in = Args.notNull(in, "Session input buffer"); + this.contentLength = Args.notNegative(contentLength, "Content length"); + } + + /** + * <p>Reads until the end of the known length of content.</p> + * + * <p>Does not close the underlying socket input, but instead leaves it + * primed to parse the next response.</p> + * @throws IOException If an IO problem occurs. + */ + @Override + public void close() throws IOException { + if (!closed) { + try { + if (pos < contentLength) { + final byte buffer[] = new byte[BUFFER_SIZE]; + while (read(buffer) >= 0) { + } + } + } finally { + // close after above so that we don't throw an exception trying + // to read after closed! + closed = true; + } + } + } + + @Override + public int available() throws IOException { + if (this.in instanceof BufferInfo) { + final int len = ((BufferInfo) this.in).length(); + return Math.min(len, (int) (this.contentLength - this.pos)); + } else { + return 0; + } + } + + /** + * Read the next byte from the stream + * @return The next byte or -1 if the end of stream has been reached. + * @throws IOException If an IO problem occurs + * @see java.io.InputStream#read() + */ + @Override + public int read() throws IOException { + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (pos >= contentLength) { + return -1; + } + final int b = this.in.read(); + if (b == -1) { + if (pos < contentLength) { + throw new ConnectionClosedException( + "Premature end of Content-Length delimited message body (expected: " + + contentLength + "; received: " + pos); + } + } else { + pos++; + } + return b; + } + + /** + * Does standard {@link InputStream#read(byte[], int, int)} behavior, but + * also notifies the watcher when the contents have been consumed. + * + * @param b The byte array to fill. + * @param off Start filling at this position. + * @param len The number of bytes to attempt to read. + * @return The number of bytes read, or -1 if the end of content has been + * reached. + * + * @throws java.io.IOException Should an error occur on the wrapped stream. + */ + @Override + public int read (final byte[] b, final int off, final int len) throws java.io.IOException { + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (pos >= contentLength) { + return -1; + } + + int chunk = len; + if (pos + len > contentLength) { + chunk = (int) (contentLength - pos); + } + final int count = this.in.read(b, off, chunk); + if (count == -1 && pos < contentLength) { + throw new ConnectionClosedException( + "Premature end of Content-Length delimited message body (expected: " + + contentLength + "; received: " + pos); + } + if (count > 0) { + pos += count; + } + return count; + } + + + /** + * Read more bytes from the stream. + * @param b The byte array to put the new data in. + * @return The number of bytes read into the buffer. + * @throws IOException If an IO problem occurs + * @see java.io.InputStream#read(byte[]) + */ + @Override + public int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Skips and discards a number of bytes from the input stream. + * @param n The number of bytes to skip. + * @return The actual number of bytes skipped. <= 0 if no bytes + * are skipped. + * @throws IOException If an error occurs while skipping bytes. + * @see InputStream#skip(long) + */ + @Override + public long skip(final long n) throws IOException { + if (n <= 0) { + return 0; + } + final byte[] buffer = new byte[BUFFER_SIZE]; + // make sure we don't skip more bytes than are + // still available + long remaining = Math.min(n, this.contentLength - this.pos); + // skip and keep track of the bytes actually skipped + long count = 0; + while (remaining > 0) { + final int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); + if (l == -1) { + break; + } + count += l; + remaining -= l; + } + return count; + } +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ContentLengthOutputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ContentLengthOutputStream.java new file mode 100644 index 000000000..1c4e588d0 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ContentLengthOutputStream.java @@ -0,0 +1,136 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Output stream that cuts off after a defined number of bytes. This class + * is used to send content of HTTP messages where the end of the content entity + * is determined by the value of the <code>Content-Length header</code>. + * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} + * long. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, the stream will be marked as closed and no further + * output will be permitted. + * + * @since 4.0 + */ +@NotThreadSafe +public class ContentLengthOutputStream extends OutputStream { + + /** + * Wrapped session output buffer. + */ + private final SessionOutputBuffer out; + + /** + * The maximum number of bytes that can be written the stream. Subsequent + * write operations will be ignored. + */ + private final long contentLength; + + /** Total bytes written */ + private long total = 0; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Wraps a session output buffer and cuts off output after a defined number + * of bytes. + * + * @param out The session output buffer + * @param contentLength The maximum number of bytes that can be written to + * the stream. Subsequent write operations will be ignored. + * + * @since 4.0 + */ + public ContentLengthOutputStream(final SessionOutputBuffer out, final long contentLength) { + super(); + this.out = Args.notNull(out, "Session output buffer"); + this.contentLength = Args.notNegative(contentLength, "Content length"); + } + + /** + * <p>Does not close the underlying socket output.</p> + * + * @throws IOException If an I/O problem occurs. + */ + @Override + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + this.out.flush(); + } + } + + @Override + public void flush() throws IOException { + this.out.flush(); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (this.total < this.contentLength) { + final long max = this.contentLength - this.total; + int chunk = len; + if (chunk > max) { + chunk = (int) max; + } + this.out.write(b, off, chunk); + this.total += chunk; + } + } + + @Override + public void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(final int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (this.total < this.contentLength) { + this.out.write(b); + this.total++; + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParser.java new file mode 100644 index 000000000..70383a22e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParser.java @@ -0,0 +1,140 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.ConnectionClosedException; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestFactory; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.impl.DefaultHttpRequestFactory; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * HTTP request parser that obtain its input from an instance + * of {@link SessionInputBuffer}. + * + * @since 4.2 + */ +@SuppressWarnings("deprecation") +@NotThreadSafe +public class DefaultHttpRequestParser extends AbstractMessageParser<HttpRequest> { + + private final HttpRequestFactory requestFactory; + private final CharArrayBuffer lineBuf; + + /** + * Creates an instance of this class. + * + * @param buffer the session input buffer. + * @param lineParser the line parser. + * @param requestFactory the factory to use to create + * {@link HttpRequest}s. + * @param params HTTP parameters. + * + * @deprecated (4.3) use + * {@link DefaultHttpRequestParser#DefaultHttpRequestParser(SessionInputBuffer, LineParser, + * HttpRequestFactory, MessageConstraints)} + */ + @Deprecated + public DefaultHttpRequestParser( + final SessionInputBuffer buffer, + final LineParser lineParser, + final HttpRequestFactory requestFactory, + final HttpParams params) { + super(buffer, lineParser, params); + this.requestFactory = Args.notNull(requestFactory, "Request factory"); + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * Creates new instance of DefaultHttpRequestParser. + * + * @param buffer the session input buffer. + * @param lineParser the line parser. If <code>null</code> + * {@link ch.boye.httpclientandroidlib.message.BasicLineParser#INSTANCE} will be used. + * @param requestFactory the response factory. If <code>null</code> + * {@link DefaultHttpRequestFactory#INSTANCE} will be used. + * @param constraints the message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * + * @since 4.3 + */ + public DefaultHttpRequestParser( + final SessionInputBuffer buffer, + final LineParser lineParser, + final HttpRequestFactory requestFactory, + final MessageConstraints constraints) { + super(buffer, lineParser, constraints); + this.requestFactory = requestFactory != null ? requestFactory : + DefaultHttpRequestFactory.INSTANCE; + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * @since 4.3 + */ + public DefaultHttpRequestParser( + final SessionInputBuffer buffer, + final MessageConstraints constraints) { + this(buffer, null, null, constraints); + } + + /** + * @since 4.3 + */ + public DefaultHttpRequestParser(final SessionInputBuffer buffer) { + this(buffer, null, null, MessageConstraints.DEFAULT); + } + + @Override + protected HttpRequest parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + final int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new ConnectionClosedException("Client closed connection"); + } + final ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + final RequestLine requestline = this.lineParser.parseRequestLine(this.lineBuf, cursor); + return this.requestFactory.newHttpRequest(requestline); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParserFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParserFactory.java new file mode 100644 index 000000000..81e886059 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParserFactory.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.HttpRequestFactory; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.impl.DefaultHttpRequestFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineParser; +import ch.boye.httpclientandroidlib.message.LineParser; + +/** + * Default factory for request message parsers. + * + * @since 4.3 + */ +@Immutable +public class DefaultHttpRequestParserFactory implements HttpMessageParserFactory<HttpRequest> { + + public static final DefaultHttpRequestParserFactory INSTANCE = new DefaultHttpRequestParserFactory(); + + private final LineParser lineParser; + private final HttpRequestFactory requestFactory; + + public DefaultHttpRequestParserFactory(final LineParser lineParser, + final HttpRequestFactory requestFactory) { + super(); + this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE; + this.requestFactory = requestFactory != null ? requestFactory + : DefaultHttpRequestFactory.INSTANCE; + } + + public DefaultHttpRequestParserFactory() { + this(null, null); + } + + public HttpMessageParser<HttpRequest> create(final SessionInputBuffer buffer, + final MessageConstraints constraints) { + return new DefaultHttpRequestParser(buffer, lineParser, requestFactory, constraints); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriter.java new file mode 100644 index 000000000..0f3b31e18 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriter.java @@ -0,0 +1,69 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.LineFormatter; + +/** + * HTTP request writer that serializes its output to an instance of {@link SessionOutputBuffer}. + * + * @since 4.3 + */ +@NotThreadSafe +public class DefaultHttpRequestWriter extends AbstractMessageWriter<HttpRequest> { + + /** + * Creates an instance of DefaultHttpRequestWriter. + * + * @param buffer the session output buffer. + * @param formatter the line formatter If <code>null</code> + * {@link ch.boye.httpclientandroidlib.message.BasicLineFormatter#INSTANCE} + * will be used. + */ + public DefaultHttpRequestWriter( + final SessionOutputBuffer buffer, + final LineFormatter formatter) { + super(buffer, formatter); + } + + public DefaultHttpRequestWriter(final SessionOutputBuffer buffer) { + this(buffer, null); + } + + @Override + protected void writeHeadLine(final HttpRequest message) throws IOException { + lineFormatter.formatRequestLine(this.lineBuf, message.getRequestLine()); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriterFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriterFactory.java new file mode 100644 index 000000000..410cc2ac3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriterFactory.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineFormatter; +import ch.boye.httpclientandroidlib.message.LineFormatter; + +/** + * Default factory for request message writers. + * + * @since 4.3 + */ +@Immutable +public class DefaultHttpRequestWriterFactory implements HttpMessageWriterFactory<HttpRequest> { + + public static final DefaultHttpRequestWriterFactory INSTANCE = new DefaultHttpRequestWriterFactory(); + + private final LineFormatter lineFormatter; + + public DefaultHttpRequestWriterFactory(final LineFormatter lineFormatter) { + super(); + this.lineFormatter = lineFormatter != null ? lineFormatter : BasicLineFormatter.INSTANCE; + } + + public DefaultHttpRequestWriterFactory() { + this(null); + } + + public HttpMessageWriter<HttpRequest> create(final SessionOutputBuffer buffer) { + return new DefaultHttpRequestWriter(buffer, lineFormatter); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParser.java new file mode 100644 index 000000000..a8428e812 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParser.java @@ -0,0 +1,141 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.impl.DefaultHttpResponseFactory; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * HTTP response parser that obtain its input from an instance + * of {@link SessionInputBuffer}. + * + * @since 4.2 + */ +@SuppressWarnings("deprecation") +@NotThreadSafe +public class DefaultHttpResponseParser extends AbstractMessageParser<HttpResponse> { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + + /** + * Creates an instance of this class. + * + * @param buffer the session input buffer. + * @param lineParser the line parser. + * @param responseFactory the factory to use to create + * {@link HttpResponse}s. + * @param params HTTP parameters. + * + * @deprecated (4.3) use + * {@link DefaultHttpResponseParser#DefaultHttpResponseParser(SessionInputBuffer, LineParser, + * HttpResponseFactory, MessageConstraints)} + */ + @Deprecated + public DefaultHttpResponseParser( + final SessionInputBuffer buffer, + final LineParser lineParser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, lineParser, params); + this.responseFactory = Args.notNull(responseFactory, "Response factory"); + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * Creates new instance of DefaultHttpResponseParser. + * + * @param buffer the session input buffer. + * @param lineParser the line parser. If <code>null</code> + * {@link ch.boye.httpclientandroidlib.message.BasicLineParser#INSTANCE} will be used + * @param responseFactory the response factory. If <code>null</code> + * {@link DefaultHttpResponseFactory#INSTANCE} will be used. + * @param constraints the message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * + * @since 4.3 + */ + public DefaultHttpResponseParser( + final SessionInputBuffer buffer, + final LineParser lineParser, + final HttpResponseFactory responseFactory, + final MessageConstraints constraints) { + super(buffer, lineParser, constraints); + this.responseFactory = responseFactory != null ? responseFactory : + DefaultHttpResponseFactory.INSTANCE; + this.lineBuf = new CharArrayBuffer(128); + } + + /** + * @since 4.3 + */ + public DefaultHttpResponseParser( + final SessionInputBuffer buffer, + final MessageConstraints constraints) { + this(buffer, null, null, constraints); + } + + /** + * @since 4.3 + */ + public DefaultHttpResponseParser(final SessionInputBuffer buffer) { + this(buffer, null, null, MessageConstraints.DEFAULT); + } + + @Override + protected HttpResponse parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + final int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new NoHttpResponseException("The target server failed to respond"); + } + //create the status line from the status string + final ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + final StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParserFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParserFactory.java new file mode 100644 index 000000000..318abcf70 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParserFactory.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.impl.DefaultHttpResponseFactory; +import ch.boye.httpclientandroidlib.io.HttpMessageParser; +import ch.boye.httpclientandroidlib.io.HttpMessageParserFactory; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineParser; +import ch.boye.httpclientandroidlib.message.LineParser; + +/** + * Default factory for response message parsers. + * + * @since 4.3 + */ +@Immutable +public class DefaultHttpResponseParserFactory implements HttpMessageParserFactory<HttpResponse> { + + public static final DefaultHttpResponseParserFactory INSTANCE = new DefaultHttpResponseParserFactory(); + + private final LineParser lineParser; + private final HttpResponseFactory responseFactory; + + public DefaultHttpResponseParserFactory(final LineParser lineParser, + final HttpResponseFactory responseFactory) { + super(); + this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE; + this.responseFactory = responseFactory != null ? responseFactory + : DefaultHttpResponseFactory.INSTANCE; + } + + public DefaultHttpResponseParserFactory() { + this(null, null); + } + + public HttpMessageParser<HttpResponse> create(final SessionInputBuffer buffer, + final MessageConstraints constraints) { + return new DefaultHttpResponseParser(buffer, lineParser, responseFactory, constraints); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriter.java new file mode 100644 index 000000000..8cdef00a5 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriter.java @@ -0,0 +1,69 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.LineFormatter; + +/** + * HTTP response writer that serializes its output to an instance of {@link SessionOutputBuffer}. + * + * @since 4.3 + */ +@NotThreadSafe +public class DefaultHttpResponseWriter extends AbstractMessageWriter<HttpResponse> { + + /** + * Creates an instance of DefaultHttpResponseWriter. + * + * @param buffer the session output buffer. + * @param formatter the line formatter If <code>null</code> + * {@link ch.boye.httpclientandroidlib.message.BasicLineFormatter#INSTANCE} + * will be used. + */ + public DefaultHttpResponseWriter( + final SessionOutputBuffer buffer, + final LineFormatter formatter) { + super(buffer, formatter); + } + + public DefaultHttpResponseWriter(final SessionOutputBuffer buffer) { + super(buffer, null); + } + + @Override + protected void writeHeadLine(final HttpResponse message) throws IOException { + lineFormatter.formatStatusLine(this.lineBuf, message.getStatusLine()); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriterFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriterFactory.java new file mode 100644 index 000000000..9dc29606d --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriterFactory.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.io.HttpMessageWriter; +import ch.boye.httpclientandroidlib.io.HttpMessageWriterFactory; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.BasicLineFormatter; +import ch.boye.httpclientandroidlib.message.LineFormatter; + +/** + * Default factory for response message writers. + * + * @since 4.3 + */ +@Immutable +public class DefaultHttpResponseWriterFactory implements HttpMessageWriterFactory<HttpResponse> { + + public static final DefaultHttpResponseWriterFactory INSTANCE = new DefaultHttpResponseWriterFactory(); + + private final LineFormatter lineFormatter; + + public DefaultHttpResponseWriterFactory(final LineFormatter lineFormatter) { + super(); + this.lineFormatter = lineFormatter != null ? lineFormatter : BasicLineFormatter.INSTANCE; + } + + public DefaultHttpResponseWriterFactory() { + this(null); + } + + public HttpMessageWriter<HttpResponse> create(final SessionOutputBuffer buffer) { + return new DefaultHttpResponseWriter(buffer, lineFormatter); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpRequestParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpRequestParser.java new file mode 100644 index 000000000..9c68b6938 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpRequestParser.java @@ -0,0 +1,102 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.ConnectionClosedException; +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpRequestFactory; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.RequestLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * HTTP request parser that obtain its input from an instance + * of {@link SessionInputBuffer}. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.2) use {@link DefaultHttpRequestParser} + */ +@Deprecated +@NotThreadSafe +public class HttpRequestParser extends AbstractMessageParser<HttpMessage> { + + private final HttpRequestFactory requestFactory; + private final CharArrayBuffer lineBuf; + + /** + * Creates an instance of this class. + * + * @param buffer the session input buffer. + * @param parser the line parser. + * @param requestFactory the factory to use to create + * {@link ch.boye.httpclientandroidlib.HttpRequest}s. + * @param params HTTP parameters. + */ + public HttpRequestParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpRequestFactory requestFactory, + final HttpParams params) { + super(buffer, parser, params); + this.requestFactory = Args.notNull(requestFactory, "Request factory"); + this.lineBuf = new CharArrayBuffer(128); + } + + @Override + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + final int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new ConnectionClosedException("Client closed connection"); + } + final ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + final RequestLine requestline = this.lineParser.parseRequestLine(this.lineBuf, cursor); + return this.requestFactory.newHttpRequest(requestline); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpRequestWriter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpRequestWriter.java new file mode 100644 index 000000000..3603ef573 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpRequestWriter.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpRequest; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.LineFormatter; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * HTTP request writer that serializes its output to an instance + * of {@link SessionOutputBuffer}. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultHttpRequestWriter} + */ +@NotThreadSafe +@Deprecated +public class HttpRequestWriter extends AbstractMessageWriter<HttpRequest> { + + public HttpRequestWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(buffer, formatter, params); + } + + @Override + protected void writeHeadLine(final HttpRequest message) throws IOException { + lineFormatter.formatRequestLine(this.lineBuf, message.getRequestLine()); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpResponseParser.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpResponseParser.java new file mode 100644 index 000000000..a94eae36c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpResponseParser.java @@ -0,0 +1,103 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpException; +import ch.boye.httpclientandroidlib.HttpMessage; +import ch.boye.httpclientandroidlib.HttpResponseFactory; +import ch.boye.httpclientandroidlib.NoHttpResponseException; +import ch.boye.httpclientandroidlib.ParseException; +import ch.boye.httpclientandroidlib.StatusLine; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.message.LineParser; +import ch.boye.httpclientandroidlib.message.ParserCursor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * HTTP response parser that obtain its input from an instance + * of {@link SessionInputBuffer}. + * <p> + * The following parameters can be used to customize the behavior of this + * class: + * <ul> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li> + * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> + * </ul> + * + * @since 4.0 + * + * @deprecated (4.2) use {@link DefaultHttpResponseParser} + */ +@Deprecated +@NotThreadSafe +public class HttpResponseParser extends AbstractMessageParser<HttpMessage> { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + + /** + * Creates an instance of this class. + * + * @param buffer the session input buffer. + * @param parser the line parser. + * @param responseFactory the factory to use to create + * {@link ch.boye.httpclientandroidlib.HttpResponse}s. + * @param params HTTP parameters. + */ + public HttpResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + this.responseFactory = Args.notNull(responseFactory, "Response factory"); + this.lineBuf = new CharArrayBuffer(128); + } + + @Override + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + final int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new NoHttpResponseException("The target server failed to respond"); + } + //create the status line from the status string + final ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + final StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpResponseWriter.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpResponseWriter.java new file mode 100644 index 000000000..2e82ade18 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpResponseWriter.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpResponse; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.message.LineFormatter; +import ch.boye.httpclientandroidlib.params.HttpParams; + +/** + * HTTP response writer that serializes its output to an instance + * of {@link SessionOutputBuffer}. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link DefaultHttpResponseWriter} + */ +@NotThreadSafe +@Deprecated +public class HttpResponseWriter extends AbstractMessageWriter<HttpResponse> { + + public HttpResponseWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(buffer, formatter, params); + } + + @Override + protected void writeHeadLine(final HttpResponse message) throws IOException { + lineFormatter.formatStatusLine(this.lineBuf, message.getStatusLine()); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpTransportMetricsImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpTransportMetricsImpl.java new file mode 100644 index 000000000..8bd4eae5f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/HttpTransportMetricsImpl.java @@ -0,0 +1,63 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; + +/** + * Default implementation of {@link HttpTransportMetrics}. + * + * @since 4.0 + */ +@NotThreadSafe +public class HttpTransportMetricsImpl implements HttpTransportMetrics { + + private long bytesTransferred = 0; + + public HttpTransportMetricsImpl() { + super(); + } + + public long getBytesTransferred() { + return this.bytesTransferred; + } + + public void setBytesTransferred(final long count) { + this.bytesTransferred = count; + } + + public void incrementBytesTransferred(final long count) { + this.bytesTransferred += count; + } + + public void reset() { + this.bytesTransferred = 0; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/IdentityInputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/IdentityInputStream.java new file mode 100644 index 000000000..98e1cf81a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/IdentityInputStream.java @@ -0,0 +1,99 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Input stream that reads data without any transformation. The end of the + * content entity is demarcated by closing the underlying connection + * (EOF condition). Entities transferred using this input stream can be of + * unlimited length. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the end of the stream (until + * <code>-1</code> is returned). + * + * @since 4.0 + */ +@NotThreadSafe +public class IdentityInputStream extends InputStream { + + private final SessionInputBuffer in; + + private boolean closed = false; + + /** + * Wraps session input stream and reads input until the the end of stream. + * + * @param in The session input buffer + */ + public IdentityInputStream(final SessionInputBuffer in) { + super(); + this.in = Args.notNull(in, "Session input buffer"); + } + + @Override + public int available() throws IOException { + if (this.in instanceof BufferInfo) { + return ((BufferInfo) this.in).length(); + } else { + return 0; + } + } + + @Override + public void close() throws IOException { + this.closed = true; + } + + @Override + public int read() throws IOException { + if (this.closed) { + return -1; + } else { + return this.in.read(); + } + } + + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + if (this.closed) { + return -1; + } else { + return this.in.read(b, off, len); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/IdentityOutputStream.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/IdentityOutputStream.java new file mode 100644 index 000000000..ac2f613b7 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/IdentityOutputStream.java @@ -0,0 +1,104 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * Output stream that writes data without any transformation. The end of + * the content entity is demarcated by closing the underlying connection + * (EOF condition). Entities transferred using this input stream can be of + * unlimited length. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, the stream will be marked as closed and no further + * output will be permitted. + * + * @since 4.0 + */ +@NotThreadSafe +public class IdentityOutputStream extends OutputStream { + + /** + * Wrapped session output buffer. + */ + private final SessionOutputBuffer out; + + /** True if the stream is closed. */ + private boolean closed = false; + + public IdentityOutputStream(final SessionOutputBuffer out) { + super(); + this.out = Args.notNull(out, "Session output buffer"); + } + + /** + * <p>Does not close the underlying socket output.</p> + * + * @throws IOException If an I/O problem occurs. + */ + @Override + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + this.out.flush(); + } + } + + @Override + public void flush() throws IOException { + this.out.flush(); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.out.write(b, off, len); + } + + @Override + public void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(final int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.out.write(b); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SessionInputBufferImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SessionInputBufferImpl.java new file mode 100644 index 000000000..ed86cb94b --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SessionInputBufferImpl.java @@ -0,0 +1,399 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; + +import ch.boye.httpclientandroidlib.MessageConstraintException; +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.config.MessageConstraints; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionInputBuffer; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +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. + * <p/> + * {@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. + * + * @since 4.3 + */ +@NotThreadSafe +public class SessionInputBufferImpl implements SessionInputBuffer, BufferInfo { + + private final HttpTransportMetricsImpl metrics; + private final byte[] buffer; + private final ByteArrayBuffer linebuffer; + private final int minChunkLimit; + private final MessageConstraints constraints; + private final CharsetDecoder decoder; + + private InputStream instream; + private int bufferpos; + private int bufferlen; + private CharBuffer cbuf; + + /** + * Creates new instance of SessionInputBufferImpl. + * + * @param metrics HTTP transport metrics. + * @param buffersize buffer size. Must be a positive number. + * @param minChunkLimit size limit below which data chunks should be buffered in memory + * in order to minimize native method invocations on the underlying network socket. + * The optimal value of this parameter can be platform specific and defines a trade-off + * between performance of memory copy operations and that of native method invocation. + * If negative default chunk limited will be used. + * @param constraints Message constraints. If <code>null</code> + * {@link MessageConstraints#DEFAULT} will be used. + * @param chardecoder chardecoder to be used for decoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for byte to char conversion. + */ + public SessionInputBufferImpl( + final HttpTransportMetricsImpl metrics, + final int buffersize, + final int minChunkLimit, + final MessageConstraints constraints, + final CharsetDecoder chardecoder) { + Args.notNull(metrics, "HTTP transport metrcis"); + Args.positive(buffersize, "Buffer size"); + this.metrics = metrics; + this.buffer = new byte[buffersize]; + this.bufferpos = 0; + this.bufferlen = 0; + this.minChunkLimit = minChunkLimit >= 0 ? minChunkLimit : 512; + this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT; + this.linebuffer = new ByteArrayBuffer(buffersize); + this.decoder = chardecoder; + } + + public SessionInputBufferImpl( + final HttpTransportMetricsImpl metrics, + final int buffersize) { + this(metrics, buffersize, buffersize, null, null); + } + + public void bind(final InputStream instream) { + this.instream = instream; + } + + public boolean isBound() { + return this.instream != null; + } + + public int capacity() { + return this.buffer.length; + } + + public int length() { + return this.bufferlen - this.bufferpos; + } + + public int available() { + return capacity() - length(); + } + + private int streamRead(final byte[] b, final int off, final int len) throws IOException { + Asserts.notNull(this.instream, "Input stream"); + return this.instream.read(b, off, len); + } + + public int fillBuffer() throws IOException { + // compact the buffer if necessary + if (this.bufferpos > 0) { + final 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; + } + final int l; + final int off = this.bufferlen; + final int len = this.buffer.length - off; + l = streamRead(this.buffer, off, len); + if (l == -1) { + return -1; + } else { + this.bufferlen = off + l; + this.metrics.incrementBytesTransferred(l); + return l; + } + } + + public boolean hasBufferedData() { + return this.bufferpos < this.bufferlen; + } + + public void clear() { + this.bufferpos = 0; + this.bufferlen = 0; + } + + public int read() throws IOException { + int noRead; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + return this.buffer[this.bufferpos++] & 0xff; + } + + public int read(final byte[] b, final int off, final int len) throws IOException { + if (b == null) { + return 0; + } + if (hasBufferedData()) { + final 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) { + final int read = streamRead(b, off, len); + if (read > 0) { + this.metrics.incrementBytesTransferred(read); + } + return read; + } else { + // otherwise read to the buffer first + while (!hasBufferedData()) { + final int noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + final 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 <code>-1</code> is returned. This method blocks until input + * data is available, end of file is detected, or an exception is thrown. + * <p> + * 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 { + Args.notNull(charbuffer, "Char array buffer"); + int noRead = 0; + boolean retry = true; + while (retry) { + // attempt to find end of line (LF) + final 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; + final 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()) { + final 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; + } + } + final int maxLineLen = this.constraints.getMaxLineLength(); + if (maxLineLen > 0 && this.linebuffer.length() >= maxLineLen) { + throw new MessageConstraintException("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, + * <code>null</code> is returned. This method blocks until input data is + * available, end of file is detected, or an exception is thrown. + * <p> + * 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 len = this.linebuffer.length(); + if (len > 0) { + if (this.linebuffer.byteAt(len - 1) == HTTP.LF) { + len--; + } + // discard CR if found + if (len > 0) { + if (this.linebuffer.byteAt(len - 1) == HTTP.CR) { + len--; + } + } + } + if (this.decoder == null) { + charbuffer.append(this.linebuffer, 0, len); + } else { + final ByteBuffer bbuf = ByteBuffer.wrap(this.linebuffer.buffer(), 0, len); + len = appendDecoded(charbuffer, bbuf); + } + this.linebuffer.clear(); + return len; + } + + private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position) + throws IOException { + int pos = position; + final int off = this.bufferpos; + int len; + this.bufferpos = pos + 1; + if (pos > off && this.buffer[pos - 1] == HTTP.CR) { + // skip CR if found + pos--; + } + len = pos - off; + if (this.decoder == null) { + charbuffer.append(this.buffer, off, len); + } else { + final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len); + len = appendDecoded(charbuffer, bbuf); + } + return len; + } + + private int appendDecoded( + final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException { + if (!bbuf.hasRemaining()) { + return 0; + } + if (this.cbuf == null) { + this.cbuf = CharBuffer.allocate(1024); + } + this.decoder.reset(); + int len = 0; + while (bbuf.hasRemaining()) { + final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true); + len += handleDecodingResult(result, charbuffer, bbuf); + } + final CoderResult result = this.decoder.flush(this.cbuf); + len += handleDecodingResult(result, charbuffer, bbuf); + this.cbuf.clear(); + return len; + } + + private int handleDecodingResult( + final CoderResult result, + final CharArrayBuffer charbuffer, + final ByteBuffer bbuf) throws IOException { + if (result.isError()) { + result.throwException(); + } + this.cbuf.flip(); + final int len = this.cbuf.remaining(); + while (this.cbuf.hasRemaining()) { + charbuffer.append(this.cbuf.get()); + } + this.cbuf.compact(); + return len; + } + + public String readLine() throws IOException { + final CharArrayBuffer charbuffer = new CharArrayBuffer(64); + final int l = readLine(charbuffer); + if (l != -1) { + return charbuffer.toString(); + } else { + return null; + } + } + + public boolean isDataAvailable(final int timeout) throws IOException { + return hasBufferedData(); + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SessionOutputBufferImpl.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SessionOutputBufferImpl.java new file mode 100644 index 000000000..99ca871ce --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SessionOutputBufferImpl.java @@ -0,0 +1,283 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.BufferInfo; +import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; +import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; +import ch.boye.httpclientandroidlib.protocol.HTTP; +import ch.boye.httpclientandroidlib.util.Args; +import ch.boye.httpclientandroidlib.util.Asserts; +import ch.boye.httpclientandroidlib.util.ByteArrayBuffer; +import ch.boye.httpclientandroidlib.util.CharArrayBuffer; + +/** + * Abstract base class for session output buffers that stream data to + * an arbitrary {@link OutputStream}. This class buffers small chunks of + * output data in an internal byte array for optimal output performance. + * </p> + * {@link #writeLine(CharArrayBuffer)} and {@link #writeLine(String)} methods + * of this class use CR-LF as a line delimiter. + * + * @since 4.3 + */ +@NotThreadSafe +public class SessionOutputBufferImpl implements SessionOutputBuffer, BufferInfo { + + private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF}; + + private final HttpTransportMetricsImpl metrics; + private final ByteArrayBuffer buffer; + private final int fragementSizeHint; + private final CharsetEncoder encoder; + + private OutputStream outstream; + private ByteBuffer bbuf; + + /** + * Creates new instance of SessionOutputBufferImpl. + * + * @param metrics HTTP transport metrics. + * @param buffersize buffer size. Must be a positive number. + * @param fragementSizeHint fragment size hint defining a minimal size of a fragment + * that should be written out directly to the socket bypassing the session buffer. + * Value <code>0</code> disables fragment buffering. + * @param charencoder charencoder to be used for encoding HTTP protocol elements. + * If <code>null</code> simple type cast will be used for char to byte conversion. + */ + public SessionOutputBufferImpl( + final HttpTransportMetricsImpl metrics, + final int buffersize, + final int fragementSizeHint, + final CharsetEncoder charencoder) { + super(); + Args.positive(buffersize, "Buffer size"); + Args.notNull(metrics, "HTTP transport metrcis"); + this.metrics = metrics; + this.buffer = new ByteArrayBuffer(buffersize); + this.fragementSizeHint = fragementSizeHint >= 0 ? fragementSizeHint : 0; + this.encoder = charencoder; + } + + public SessionOutputBufferImpl( + final HttpTransportMetricsImpl metrics, + final int buffersize) { + this(metrics, buffersize, buffersize, null); + } + + public void bind(final OutputStream outstream) { + this.outstream = outstream; + } + + public boolean isBound() { + return this.outstream != null; + } + + public int capacity() { + return this.buffer.capacity(); + } + + public int length() { + return this.buffer.length(); + } + + public int available() { + return capacity() - length(); + } + + private void streamWrite(final byte[] b, final int off, final int len) throws IOException { + Asserts.notNull(outstream, "Output stream"); + this.outstream.write(b, off, len); + } + + private void flushStream() throws IOException { + if (this.outstream != null) { + this.outstream.flush(); + } + } + + private void flushBuffer() throws IOException { + final int len = this.buffer.length(); + if (len > 0) { + streamWrite(this.buffer.buffer(), 0, len); + this.buffer.clear(); + this.metrics.incrementBytesTransferred(len); + } + } + + public void flush() throws IOException { + flushBuffer(); + flushStream(); + } + + public void write(final byte[] b, final int off, final int len) throws IOException { + if (b == null) { + return; + } + // Do not want to buffer large-ish chunks + // if the byte array is larger then MIN_CHUNK_LIMIT + // write it directly to the output stream + if (len > this.fragementSizeHint || len > this.buffer.capacity()) { + // flush the buffer + flushBuffer(); + // write directly to the out stream + streamWrite(b, off, len); + this.metrics.incrementBytesTransferred(len); + } else { + // Do not let the buffer grow unnecessarily + final int freecapacity = this.buffer.capacity() - this.buffer.length(); + if (len > freecapacity) { + // flush the buffer + flushBuffer(); + } + // buffer + this.buffer.append(b, off, len); + } + } + + public void write(final byte[] b) throws IOException { + if (b == null) { + return; + } + write(b, 0, b.length); + } + + public void write(final int b) throws IOException { + if (this.fragementSizeHint > 0) { + if (this.buffer.isFull()) { + flushBuffer(); + } + this.buffer.append(b); + } else { + flushBuffer(); + this.outstream.write(b); + } + } + + /** + * Writes characters from the specified string followed by a line delimiter + * to this session buffer. + * <p> + * This method uses CR-LF as a line delimiter. + * + * @param s the line. + * @exception IOException if an I/O error occurs. + */ + public void writeLine(final String s) throws IOException { + if (s == null) { + return; + } + if (s.length() > 0) { + if (this.encoder == null) { + for (int i = 0; i < s.length(); i++) { + write(s.charAt(i)); + } + } else { + final CharBuffer cbuf = CharBuffer.wrap(s); + writeEncoded(cbuf); + } + } + write(CRLF); + } + + /** + * Writes characters from the specified char array followed by a line + * delimiter to this session buffer. + * <p> + * This method uses CR-LF as a line delimiter. + * + * @param charbuffer the buffer containing chars of the line. + * @exception IOException if an I/O error occurs. + */ + public void writeLine(final CharArrayBuffer charbuffer) throws IOException { + if (charbuffer == null) { + return; + } + if (this.encoder == null) { + int off = 0; + int remaining = charbuffer.length(); + while (remaining > 0) { + int chunk = this.buffer.capacity() - this.buffer.length(); + chunk = Math.min(chunk, remaining); + if (chunk > 0) { + this.buffer.append(charbuffer, off, chunk); + } + if (this.buffer.isFull()) { + flushBuffer(); + } + off += chunk; + remaining -= chunk; + } + } else { + final CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length()); + writeEncoded(cbuf); + } + write(CRLF); + } + + private void writeEncoded(final CharBuffer cbuf) throws IOException { + if (!cbuf.hasRemaining()) { + return; + } + if (this.bbuf == null) { + this.bbuf = ByteBuffer.allocate(1024); + } + this.encoder.reset(); + while (cbuf.hasRemaining()) { + final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true); + handleEncodingResult(result); + } + final CoderResult result = this.encoder.flush(this.bbuf); + handleEncodingResult(result); + this.bbuf.clear(); + } + + private void handleEncodingResult(final CoderResult result) throws IOException { + if (result.isError()) { + result.throwException(); + } + this.bbuf.flip(); + while (this.bbuf.hasRemaining()) { + write(this.bbuf.get()); + } + this.bbuf.compact(); + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SocketInputBuffer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SocketInputBuffer.java new file mode 100644 index 000000000..4bba8f894 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SocketInputBuffer.java @@ -0,0 +1,108 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.net.Socket; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.io.EofSensor; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * {@link ch.boye.httpclientandroidlib.io.SessionInputBuffer} implementation + * bound to a {@link Socket}. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link SessionInputBufferImpl} + */ +@NotThreadSafe +@Deprecated +public class SocketInputBuffer extends AbstractSessionInputBuffer implements EofSensor { + + private final Socket socket; + + private boolean eof; + + /** + * Creates an instance of this class. + * + * @param socket the socket to read data from. + * @param buffersize the size of the internal buffer. If this number is less + * than <code>0</code> it is set to the value of + * {@link Socket#getReceiveBufferSize()}. If resultant number is less + * than <code>1024</code> it is set to <code>1024</code>. + * @param params HTTP parameters. + */ + public SocketInputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + super(); + Args.notNull(socket, "Socket"); + this.socket = socket; + this.eof = false; + int n = buffersize; + if (n < 0) { + n = socket.getReceiveBufferSize(); + } + if (n < 1024) { + n = 1024; + } + init(socket.getInputStream(), n, params); + } + + @Override + protected int fillBuffer() throws IOException { + final int i = super.fillBuffer(); + this.eof = i == -1; + return i; + } + + public boolean isDataAvailable(final int timeout) throws IOException { + boolean result = hasBufferedData(); + if (!result) { + final int oldtimeout = this.socket.getSoTimeout(); + try { + this.socket.setSoTimeout(timeout); + fillBuffer(); + result = hasBufferedData(); + } finally { + socket.setSoTimeout(oldtimeout); + } + } + return result; + } + + public boolean isEof() { + return this.eof; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SocketOutputBuffer.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SocketOutputBuffer.java new file mode 100644 index 000000000..9c9ff2d9f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/SocketOutputBuffer.java @@ -0,0 +1,75 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +package ch.boye.httpclientandroidlib.impl.io; + +import java.io.IOException; +import java.net.Socket; + +import ch.boye.httpclientandroidlib.annotation.NotThreadSafe; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * {@link ch.boye.httpclientandroidlib.io.SessionOutputBuffer} implementation + * bound to a {@link Socket}. + * + * @since 4.0 + * + * @deprecated (4.3) use {@link SessionOutputBufferImpl} + */ +@NotThreadSafe +@Deprecated +public class SocketOutputBuffer extends AbstractSessionOutputBuffer { + + /** + * Creates an instance of this class. + * + * @param socket the socket to write data to. + * @param buffersize the size of the internal buffer. If this number is less + * than <code>0</code> it is set to the value of + * {@link Socket#getSendBufferSize()}. If resultant number is less + * than <code>1024</code> it is set to <code>1024</code>. + * @param params HTTP parameters. + */ + public SocketOutputBuffer( + final Socket socket, + final int buffersize, + final HttpParams params) throws IOException { + super(); + Args.notNull(socket, "Socket"); + int n = buffersize; + if (n < 0) { + n = socket.getSendBufferSize(); + } + if (n < 1024) { + n = 1024; + } + init(socket.getOutputStream(), n, params); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/package-info.java new file mode 100644 index 000000000..24c7bee79 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/package-info.java @@ -0,0 +1,32 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of message parses and writers + * for synchronous, blocking communication. + */ +package ch.boye.httpclientandroidlib.impl.io; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/package-info.java new file mode 100644 index 000000000..4cf3cdb3a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/package-info.java @@ -0,0 +1,32 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of HTTP connections for synchronous, + * blocking communication. + */ +package ch.boye.httpclientandroidlib.impl; diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicConnFactory.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicConnFactory.java new file mode 100644 index 000000000..a9e26a435 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicConnFactory.java @@ -0,0 +1,177 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.pool; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; + +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpConnectionFactory; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.Immutable; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.impl.DefaultBHttpClientConnection; +import ch.boye.httpclientandroidlib.impl.DefaultBHttpClientConnectionFactory; +import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; +import ch.boye.httpclientandroidlib.params.HttpParamConfig; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.pool.ConnFactory; +import ch.boye.httpclientandroidlib.util.Args; + +/** + * A very basic {@link ConnFactory} implementation that creates + * {@link HttpClientConnection} instances given a {@link HttpHost} instance. + * + * @see HttpHost + * @since 4.2 + */ +@SuppressWarnings("deprecation") +@Immutable +public class BasicConnFactory implements ConnFactory<HttpHost, HttpClientConnection> { + + private final SocketFactory plainfactory; + private final SSLSocketFactory sslfactory; + private final int connectTimeout; + private final SocketConfig sconfig; + private final HttpConnectionFactory<? extends HttpClientConnection> connFactory; + + /** + * @deprecated (4.3) use + * {@link BasicConnFactory#BasicConnFactory(SocketFactory, SSLSocketFactory, int, + * SocketConfig, ConnectionConfig)}. + */ + @Deprecated + public BasicConnFactory(final SSLSocketFactory sslfactory, final HttpParams params) { + super(); + Args.notNull(params, "HTTP params"); + this.plainfactory = null; + this.sslfactory = sslfactory; + this.connectTimeout = params.getIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 0); + this.sconfig = HttpParamConfig.getSocketConfig(params); + this.connFactory = new DefaultBHttpClientConnectionFactory( + HttpParamConfig.getConnectionConfig(params)); + } + + /** + * @deprecated (4.3) use + * {@link BasicConnFactory#BasicConnFactory(int, SocketConfig, ConnectionConfig)}. + */ + @Deprecated + public BasicConnFactory(final HttpParams params) { + this(null, params); + } + + /** + * @since 4.3 + */ + public BasicConnFactory( + final SocketFactory plainfactory, + final SSLSocketFactory sslfactory, + final int connectTimeout, + final SocketConfig sconfig, + final ConnectionConfig cconfig) { + super(); + this.plainfactory = plainfactory; + this.sslfactory = sslfactory; + this.connectTimeout = connectTimeout; + this.sconfig = sconfig != null ? sconfig : SocketConfig.DEFAULT; + this.connFactory = new DefaultBHttpClientConnectionFactory( + cconfig != null ? cconfig : ConnectionConfig.DEFAULT); + } + + /** + * @since 4.3 + */ + public BasicConnFactory( + final int connectTimeout, final SocketConfig sconfig, final ConnectionConfig cconfig) { + this(null, null, connectTimeout, sconfig, cconfig); + } + + /** + * @since 4.3 + */ + public BasicConnFactory(final SocketConfig sconfig, final ConnectionConfig cconfig) { + this(null, null, 0, sconfig, cconfig); + } + + /** + * @since 4.3 + */ + public BasicConnFactory() { + this(null, null, 0, SocketConfig.DEFAULT, ConnectionConfig.DEFAULT); + } + + /** + * @deprecated (4.3) no longer used. + */ + @Deprecated + protected HttpClientConnection create(final Socket socket, final HttpParams params) throws IOException { + final int bufsize = params.getIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024); + final DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(bufsize); + conn.bind(socket); + return conn; + } + + public HttpClientConnection create(final HttpHost host) throws IOException { + final String scheme = host.getSchemeName(); + Socket socket = null; + if ("http".equalsIgnoreCase(scheme)) { + socket = this.plainfactory != null ? this.plainfactory.createSocket() : + new Socket(); + } if ("https".equalsIgnoreCase(scheme)) { + socket = (this.sslfactory != null ? this.sslfactory : + SSLSocketFactory.getDefault()).createSocket(); + } + if (socket == null) { + throw new IOException(scheme + " scheme is not supported"); + } + final String hostname = host.getHostName(); + int port = host.getPort(); + if (port == -1) { + if (host.getSchemeName().equalsIgnoreCase("http")) { + port = 80; + } else if (host.getSchemeName().equalsIgnoreCase("https")) { + port = 443; + } + } + socket.setSoTimeout(this.sconfig.getSoTimeout()); + socket.connect(new InetSocketAddress(hostname, port), this.connectTimeout); + socket.setTcpNoDelay(this.sconfig.isTcpNoDelay()); + final int linger = this.sconfig.getSoLinger(); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + socket.setKeepAlive(this.sconfig.isSoKeepAlive()); + return this.connFactory.createConnection(socket); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicConnPool.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicConnPool.java new file mode 100644 index 000000000..c99badd3e --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicConnPool.java @@ -0,0 +1,89 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.pool; + +import java.util.concurrent.atomic.AtomicLong; + +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.config.ConnectionConfig; +import ch.boye.httpclientandroidlib.config.SocketConfig; +import ch.boye.httpclientandroidlib.params.HttpParams; +import ch.boye.httpclientandroidlib.pool.AbstractConnPool; +import ch.boye.httpclientandroidlib.pool.ConnFactory; + +/** + * A very basic {@link ch.boye.httpclientandroidlib.pool.ConnPool} implementation that + * represents a pool of blocking {@link HttpClientConnection} connections + * identified by an {@link HttpHost} instance. Please note this pool + * implementation does not support complex routes via a proxy cannot + * differentiate between direct and proxied connections. + * + * @see HttpHost + * @since 4.2 + */ +@SuppressWarnings("deprecation") +@ThreadSafe +public class BasicConnPool extends AbstractConnPool<HttpHost, HttpClientConnection, BasicPoolEntry> { + + private static final AtomicLong COUNTER = new AtomicLong(); + + public BasicConnPool(final ConnFactory<HttpHost, HttpClientConnection> connFactory) { + super(connFactory, 2, 20); + } + + /** + * @deprecated (4.3) use {@link BasicConnPool#BasicConnPool(SocketConfig, ConnectionConfig)} + */ + @Deprecated + public BasicConnPool(final HttpParams params) { + super(new BasicConnFactory(params), 2, 20); + } + + /** + * @since 4.3 + */ + public BasicConnPool(final SocketConfig sconfig, final ConnectionConfig cconfig) { + super(new BasicConnFactory(sconfig, cconfig), 2, 20); + } + + /** + * @since 4.3 + */ + public BasicConnPool() { + super(new BasicConnFactory(SocketConfig.DEFAULT, ConnectionConfig.DEFAULT), 2, 20); + } + + @Override + protected BasicPoolEntry createEntry( + final HttpHost host, + final HttpClientConnection conn) { + return new BasicPoolEntry(Long.toString(COUNTER.getAndIncrement()), host, conn); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicPoolEntry.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicPoolEntry.java new file mode 100644 index 000000000..465525c33 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/BasicPoolEntry.java @@ -0,0 +1,64 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ +package ch.boye.httpclientandroidlib.impl.pool; + +import java.io.IOException; + +import ch.boye.httpclientandroidlib.HttpClientConnection; +import ch.boye.httpclientandroidlib.HttpHost; +import ch.boye.httpclientandroidlib.annotation.ThreadSafe; +import ch.boye.httpclientandroidlib.pool.PoolEntry; + +/** + * A very basic {@link PoolEntry} implementation that represents an entry + * in a pool of blocking {@link HttpClientConnection}s identified by + * an {@link HttpHost} instance. + * + * @see HttpHost + * @since 4.2 + */ +@ThreadSafe +public class BasicPoolEntry extends PoolEntry<HttpHost, HttpClientConnection> { + + public BasicPoolEntry(final String id, final HttpHost route, final HttpClientConnection conn) { + super(id, route, conn); + } + + @Override + public void close() { + try { + this.getConnection().close(); + } catch (final IOException ignore) { + } + } + + @Override + public boolean isClosed() { + return !this.getConnection().isOpen(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/package-info.java new file mode 100644 index 000000000..534e1885c --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/pool/package-info.java @@ -0,0 +1,32 @@ +/* + * ==================================================================== + * 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 + * <http://www.apache.org/>. + * + */ + +/** + * Default implementations of client side connection pools + * for synchronous, blocking communication. + */ +package ch.boye.httpclientandroidlib.impl.pool; |