summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNarayan Kamath <narayan@google.com>2014-11-27 18:15:37 +0000
committerNarayan Kamath <narayan@google.com>2015-02-04 13:13:58 +0000
commita8b46a3d3b6ed1488df10740653829283572903b (patch)
treef2e7c14a60fbcdac9ac45dcfb2548975de03b554
parent9ba0e2c70ba567fb802bf23f94b9b173a524431a (diff)
downloadandroid_external_apache-http-a8b46a3d3b6ed1488df10740653829283572903b.tar.gz
android_external_apache-http-a8b46a3d3b6ed1488df10740653829283572903b.tar.bz2
android_external_apache-http-a8b46a3d3b6ed1488df10740653829283572903b.zip
Move apache specific portions of android.net.http to apache-http.
.. and move some parts of apache-http into the framework. The parts that have been moved need to be in the bootclasspath because we have public API that returns org.apache.http.conn.ssl.SSLSocketFactory :( . This change also removes the placeholder library shim. bug: 18027885 Change-Id: I37aa7562bcd5e05191b83676fae4533e03b86d1d
-rw-r--r--Android.mk4
-rw-r--r--android/src/android/net/http/AndroidHttpClient.java509
-rw-r--r--android/src/android/net/http/AndroidHttpClientConnection.java460
-rw-r--r--android/src/android/net/http/CertificateChainValidator.java279
-rw-r--r--android/src/android/net/http/Connection.java575
-rw-r--r--android/src/android/net/http/ConnectionThread.java137
-rw-r--r--android/src/android/net/http/DelegatingSSLSession.java158
-rw-r--r--android/src/android/net/http/EventHandler.java131
-rw-r--r--android/src/android/net/http/Headers.java521
-rw-r--r--android/src/android/net/http/HttpAuthHeader.java424
-rw-r--r--android/src/android/net/http/HttpConnection.java93
-rw-r--r--android/src/android/net/http/HttpLog.java43
-rw-r--r--android/src/android/net/http/HttpsConnection.java433
-rw-r--r--android/src/android/net/http/IdleCache.java175
-rw-r--r--android/src/android/net/http/LoggingEventHandler.java92
-rw-r--r--android/src/android/net/http/Request.java526
-rw-r--r--android/src/android/net/http/RequestFeeder.java (renamed from placeholder/org/apache/http/PlaceHolder.java)28
-rw-r--r--android/src/android/net/http/RequestHandle.java478
-rw-r--r--android/src/android/net/http/RequestQueue.java542
-rw-r--r--android/src/com/android/internal/http/multipart/ByteArrayPartSource.java86
-rw-r--r--android/src/com/android/internal/http/multipart/FilePart.java254
-rw-r--r--android/src/com/android/internal/http/multipart/FilePartSource.java131
-rw-r--r--android/src/com/android/internal/http/multipart/MultipartEntity.java230
-rw-r--r--android/src/com/android/internal/http/multipart/Part.java439
-rw-r--r--android/src/com/android/internal/http/multipart/PartBase.java150
-rw-r--r--android/src/com/android/internal/http/multipart/PartSource.java72
-rw-r--r--android/src/com/android/internal/http/multipart/StringPart.java150
-rw-r--r--src/org/apache/http/conn/ConnectTimeoutException.java69
-rw-r--r--src/org/apache/http/conn/scheme/HostNameResolver.java47
-rw-r--r--src/org/apache/http/conn/scheme/LayeredSocketFactory.java77
-rw-r--r--src/org/apache/http/conn/scheme/SocketFactory.java143
-rw-r--r--src/org/apache/http/conn/ssl/AbstractVerifier.java283
-rw-r--r--src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java59
-rw-r--r--src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java67
-rw-r--r--src/org/apache/http/conn/ssl/SSLSocketFactory.java409
-rw-r--r--src/org/apache/http/conn/ssl/StrictHostnameVerifier.java74
-rw-r--r--src/org/apache/http/conn/ssl/X509HostnameVerifier.java91
-rw-r--r--src/org/apache/http/conn/ssl/package.html40
-rw-r--r--src/org/apache/http/params/CoreConnectionPNames.java136
-rw-r--r--src/org/apache/http/params/HttpConnectionParams.java229
-rw-r--r--src/org/apache/http/params/HttpParams.java192
41 files changed, 7116 insertions, 1920 deletions
diff --git a/Android.mk b/Android.mk
index 463fb6b..f3eb679 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,8 +17,10 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := org.apache.http.legacy
LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := conscrypt
LOCAL_SRC_FILES := \
- $(call all-java-files-under,placeholder)
+ $(call all-java-files-under,src) \
+ $(call all-java-files-under,android)
LOCAL_MODULE_TAGS := optional
include $(BUILD_JAVA_LIBRARY)
diff --git a/android/src/android/net/http/AndroidHttpClient.java b/android/src/android/net/http/AndroidHttpClient.java
new file mode 100644
index 0000000..04f3974
--- /dev/null
+++ b/android/src/android/net/http/AndroidHttpClient.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import com.android.internal.http.HttpDateTime;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.SSLCertificateSocketFactory;
+import android.net.SSLSessionCache;
+import android.os.Looper;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Implementation of the Apache {@link DefaultHttpClient} that is configured with
+ * reasonable default settings and registered schemes for Android.
+ * Don't create this directly, use the {@link #newInstance} factory method.
+ *
+ * <p>This client processes cookies but does not retain them by default.
+ * To retain cookies, simply add a cookie store to the HttpContext:</p>
+ *
+ * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
+ */
+public final class AndroidHttpClient implements HttpClient {
+
+ // Gzip of data shorter than this probably won't be worthwhile
+ public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
+
+ // Default connection and socket timeout of 60 seconds. Tweak to taste.
+ private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;
+
+ private static final String TAG = "AndroidHttpClient";
+
+ private static String[] textContentTypes = new String[] {
+ "text/",
+ "application/xml",
+ "application/json"
+ };
+
+ /** Interceptor throws an exception if the executing thread is blocked */
+ private static final HttpRequestInterceptor sThreadCheckInterceptor =
+ new HttpRequestInterceptor() {
+ public void process(HttpRequest request, HttpContext context) {
+ // Prevent the HttpRequest from being sent on the main thread
+ if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
+ throw new RuntimeException("This thread forbids HTTP requests");
+ }
+ }
+ };
+
+ /**
+ * Create a new HttpClient with reasonable defaults (which you can update).
+ *
+ * @param userAgent to report in your HTTP requests
+ * @param context to use for caching SSL sessions (may be null for no caching)
+ * @return AndroidHttpClient for you to use for all your requests.
+ */
+ public static AndroidHttpClient newInstance(String userAgent, Context context) {
+ HttpParams params = new BasicHttpParams();
+
+ // Turn off stale checking. Our connections break all the time anyway,
+ // and it's not worth it to pay the penalty of checking every time.
+ HttpConnectionParams.setStaleCheckingEnabled(params, false);
+
+ HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
+ HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
+ HttpConnectionParams.setSocketBufferSize(params, 8192);
+
+ // Don't handle redirects -- return them to the caller. Our code
+ // often wants to re-POST after a redirect, which we must do ourselves.
+ HttpClientParams.setRedirecting(params, false);
+
+ // Use a session cache for SSL sockets
+ SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context);
+
+ // Set the specified user agent and register standard protocols.
+ HttpProtocolParams.setUserAgent(params, userAgent);
+ SchemeRegistry schemeRegistry = new SchemeRegistry();
+ schemeRegistry.register(new Scheme("http",
+ PlainSocketFactory.getSocketFactory(), 80));
+ schemeRegistry.register(new Scheme("https",
+ SSLCertificateSocketFactory.getHttpSocketFactory(
+ SOCKET_OPERATION_TIMEOUT, sessionCache), 443));
+
+ ClientConnectionManager manager =
+ new ThreadSafeClientConnManager(params, schemeRegistry);
+
+ // We use a factory method to modify superclass initialization
+ // parameters without the funny call-a-static-method dance.
+ return new AndroidHttpClient(manager, params);
+ }
+
+ /**
+ * Create a new HttpClient with reasonable defaults (which you can update).
+ * @param userAgent to report in your HTTP requests.
+ * @return AndroidHttpClient for you to use for all your requests.
+ */
+ public static AndroidHttpClient newInstance(String userAgent) {
+ return newInstance(userAgent, null /* session cache */);
+ }
+
+ private final HttpClient delegate;
+
+ private RuntimeException mLeakedException = new IllegalStateException(
+ "AndroidHttpClient created and never closed");
+
+ private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
+ this.delegate = new DefaultHttpClient(ccm, params) {
+ @Override
+ protected BasicHttpProcessor createHttpProcessor() {
+ // Add interceptor to prevent making requests from main thread.
+ BasicHttpProcessor processor = super.createHttpProcessor();
+ processor.addRequestInterceptor(sThreadCheckInterceptor);
+ processor.addRequestInterceptor(new CurlLogger());
+
+ return processor;
+ }
+
+ @Override
+ protected HttpContext createHttpContext() {
+ // Same as DefaultHttpClient.createHttpContext() minus the
+ // cookie store.
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(
+ ClientContext.AUTHSCHEME_REGISTRY,
+ getAuthSchemes());
+ context.setAttribute(
+ ClientContext.COOKIESPEC_REGISTRY,
+ getCookieSpecs());
+ context.setAttribute(
+ ClientContext.CREDS_PROVIDER,
+ getCredentialsProvider());
+ return context;
+ }
+ };
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (mLeakedException != null) {
+ Log.e(TAG, "Leak found", mLeakedException);
+ mLeakedException = null;
+ }
+ }
+
+ /**
+ * Modifies a request to indicate to the server that we would like a
+ * gzipped response. (Uses the "Accept-Encoding" HTTP header.)
+ * @param request the request to modify
+ * @see #getUngzippedContent
+ */
+ public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
+ request.addHeader("Accept-Encoding", "gzip");
+ }
+
+ /**
+ * Gets the input stream from a response entity. If the entity is gzipped
+ * then this will get a stream over the uncompressed data.
+ *
+ * @param entity the entity whose content should be read
+ * @return the input stream to read from
+ * @throws IOException
+ */
+ public static InputStream getUngzippedContent(HttpEntity entity)
+ throws IOException {
+ InputStream responseStream = entity.getContent();
+ if (responseStream == null) return responseStream;
+ Header header = entity.getContentEncoding();
+ if (header == null) return responseStream;
+ String contentEncoding = header.getValue();
+ if (contentEncoding == null) return responseStream;
+ if (contentEncoding.contains("gzip")) responseStream
+ = new GZIPInputStream(responseStream);
+ return responseStream;
+ }
+
+ /**
+ * Release resources associated with this client. You must call this,
+ * or significant resources (sockets and memory) may be leaked.
+ */
+ public void close() {
+ if (mLeakedException != null) {
+ getConnectionManager().shutdown();
+ mLeakedException = null;
+ }
+ }
+
+ public HttpParams getParams() {
+ return delegate.getParams();
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return delegate.getConnectionManager();
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException {
+ return delegate.execute(request);
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context)
+ throws IOException {
+ return delegate.execute(request, context);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request)
+ throws IOException {
+ return delegate.execute(target, request);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException {
+ return delegate.execute(target, request, context);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(request, responseHandler);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(request, responseHandler, context);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ return delegate.execute(target, request, responseHandler);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return delegate.execute(target, request, responseHandler, context);
+ }
+
+ /**
+ * Compress data to send to server.
+ * Creates a Http Entity holding the gzipped data.
+ * The data will not be compressed if it is too short.
+ * @param data The bytes to compress
+ * @return Entity holding the data
+ */
+ public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
+ throws IOException {
+ AbstractHttpEntity entity;
+ if (data.length < getMinGzipSize(resolver)) {
+ entity = new ByteArrayEntity(data);
+ } else {
+ ByteArrayOutputStream arr = new ByteArrayOutputStream();
+ OutputStream zipper = new GZIPOutputStream(arr);
+ zipper.write(data);
+ zipper.close();
+ entity = new ByteArrayEntity(arr.toByteArray());
+ entity.setContentEncoding("gzip");
+ }
+ return entity;
+ }
+
+ /**
+ * Retrieves the minimum size for compressing data.
+ * Shorter data will not be compressed.
+ */
+ public static long getMinGzipSize(ContentResolver resolver) {
+ return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant.
+ }
+
+ /* cURL logging support. */
+
+ /**
+ * Logging tag and level.
+ */
+ private static class LoggingConfiguration {
+
+ private final String tag;
+ private final int level;
+
+ private LoggingConfiguration(String tag, int level) {
+ this.tag = tag;
+ this.level = level;
+ }
+
+ /**
+ * Returns true if logging is turned on for this configuration.
+ */
+ private boolean isLoggable() {
+ return Log.isLoggable(tag, level);
+ }
+
+ /**
+ * Prints a message using this configuration.
+ */
+ private void println(String message) {
+ Log.println(level, tag, message);
+ }
+ }
+
+ /** cURL logging configuration. */
+ private volatile LoggingConfiguration curlConfiguration;
+
+ /**
+ * Enables cURL request logging for this client.
+ *
+ * @param name to log messages with
+ * @param level at which to log messages (see {@link android.util.Log})
+ */
+ public void enableCurlLogging(String name, int level) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ if (level < Log.VERBOSE || level > Log.ASSERT) {
+ throw new IllegalArgumentException("Level is out of range ["
+ + Log.VERBOSE + ".." + Log.ASSERT + "]");
+ }
+
+ curlConfiguration = new LoggingConfiguration(name, level);
+ }
+
+ /**
+ * Disables cURL logging for this client.
+ */
+ public void disableCurlLogging() {
+ curlConfiguration = null;
+ }
+
+ /**
+ * Logs cURL commands equivalent to requests.
+ */
+ private class CurlLogger implements HttpRequestInterceptor {
+ public void process(HttpRequest request, HttpContext context)
+ throws HttpException, IOException {
+ LoggingConfiguration configuration = curlConfiguration;
+ if (configuration != null
+ && configuration.isLoggable()
+ && request instanceof HttpUriRequest) {
+ // Never print auth token -- we used to check ro.secure=0 to
+ // enable that, but can't do that in unbundled code.
+ configuration.println(toCurl((HttpUriRequest) request, false));
+ }
+ }
+ }
+
+ /**
+ * Generates a cURL command equivalent to the given request.
+ */
+ private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("curl ");
+
+ // add in the method
+ builder.append("-X ");
+ builder.append(request.getMethod());
+ builder.append(" ");
+
+ for (Header header: request.getAllHeaders()) {
+ if (!logAuthToken
+ && (header.getName().equals("Authorization") ||
+ header.getName().equals("Cookie"))) {
+ continue;
+ }
+ builder.append("--header \"");
+ builder.append(header.toString().trim());
+ builder.append("\" ");
+ }
+
+ URI uri = request.getURI();
+
+ // If this is a wrapped request, use the URI from the original
+ // request instead. getURI() on the wrapper seems to return a
+ // relative URI. We want an absolute URI.
+ if (request instanceof RequestWrapper) {
+ HttpRequest original = ((RequestWrapper) request).getOriginal();
+ if (original instanceof HttpUriRequest) {
+ uri = ((HttpUriRequest) original).getURI();
+ }
+ }
+
+ builder.append("\"");
+ builder.append(uri);
+ builder.append("\"");
+
+ if (request instanceof HttpEntityEnclosingRequest) {
+ HttpEntityEnclosingRequest entityRequest =
+ (HttpEntityEnclosingRequest) request;
+ HttpEntity entity = entityRequest.getEntity();
+ if (entity != null && entity.isRepeatable()) {
+ if (entity.getContentLength() < 1024) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ entity.writeTo(stream);
+
+ if (isBinaryContent(request)) {
+ String base64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP);
+ builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; ");
+ builder.append(" --data-binary @/tmp/$$.bin");
+ } else {
+ String entityString = stream.toString();
+ builder.append(" --data-ascii \"")
+ .append(entityString)
+ .append("\"");
+ }
+ } else {
+ builder.append(" [TOO MUCH DATA TO INCLUDE]");
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ private static boolean isBinaryContent(HttpUriRequest request) {
+ Header[] headers;
+ headers = request.getHeaders(Headers.CONTENT_ENCODING);
+ if (headers != null) {
+ for (Header header : headers) {
+ if ("gzip".equalsIgnoreCase(header.getValue())) {
+ return true;
+ }
+ }
+ }
+
+ headers = request.getHeaders(Headers.CONTENT_TYPE);
+ if (headers != null) {
+ for (Header header : headers) {
+ for (String contentType : textContentTypes) {
+ if (header.getValue().startsWith(contentType)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the date of the given HTTP date string. This method can identify
+ * and parse the date formats emitted by common HTTP servers, such as
+ * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>,
+ * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>,
+ * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>,
+ * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and
+ * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI
+ * C's asctime()</a>.
+ *
+ * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
+ * @throws IllegalArgumentException if {@code dateString} is not a date or
+ * of an unsupported format.
+ */
+ public static long parseDate(String dateString) {
+ return HttpDateTime.parse(dateString);
+ }
+}
diff --git a/android/src/android/net/http/AndroidHttpClientConnection.java b/android/src/android/net/http/AndroidHttpClientConnection.java
new file mode 100644
index 0000000..6d48fce
--- /dev/null
+++ b/android/src/android/net/http/AndroidHttpClientConnection.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import org.apache.http.HttpConnection;
+import org.apache.http.HttpClientConnection;
+import org.apache.http.HttpConnectionMetrics;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpInetConnection;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.NoHttpResponseException;
+import org.apache.http.StatusLine;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.impl.HttpConnectionMetricsImpl;
+import org.apache.http.impl.entity.EntitySerializer;
+import org.apache.http.impl.entity.StrictContentLengthStrategy;
+import org.apache.http.impl.io.ChunkedInputStream;
+import org.apache.http.impl.io.ContentLengthInputStream;
+import org.apache.http.impl.io.HttpRequestWriter;
+import org.apache.http.impl.io.IdentityInputStream;
+import org.apache.http.impl.io.SocketInputBuffer;
+import org.apache.http.impl.io.SocketOutputBuffer;
+import org.apache.http.io.HttpMessageWriter;
+import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.ParseException;
+import org.apache.http.util.CharArrayBuffer;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * A alternate class for (@link DefaultHttpClientConnection).
+ * It has better performance than DefaultHttpClientConnection
+ *
+ * {@hide}
+ */
+public class AndroidHttpClientConnection
+ implements HttpInetConnection, HttpConnection {
+
+ private SessionInputBuffer inbuffer = null;
+ private SessionOutputBuffer outbuffer = null;
+ private int maxHeaderCount;
+ // store CoreConnectionPNames.MAX_LINE_LENGTH for performance
+ private int maxLineLength;
+
+ private final EntitySerializer entityserializer;
+
+ private HttpMessageWriter requestWriter = null;
+ private HttpConnectionMetricsImpl metrics = null;
+ private volatile boolean open;
+ private Socket socket = null;
+
+ public AndroidHttpClientConnection() {
+ this.entityserializer = new EntitySerializer(
+ new StrictContentLengthStrategy());
+ }
+
+ /**
+ * Bind socket and set HttpParams to AndroidHttpClientConnection
+ * @param socket outgoing socket
+ * @param params HttpParams
+ * @throws IOException
+ */
+ public void bind(
+ final Socket socket,
+ final HttpParams params) throws IOException {
+ if (socket == null) {
+ throw new IllegalArgumentException("Socket may not be null");
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ assertNotOpen();
+ socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params));
+ socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params));
+
+ int linger = HttpConnectionParams.getLinger(params);
+ if (linger >= 0) {
+ socket.setSoLinger(linger > 0, linger);
+ }
+ this.socket = socket;
+
+ int buffersize = HttpConnectionParams.getSocketBufferSize(params);
+ this.inbuffer = new SocketInputBuffer(socket, buffersize, params);
+ this.outbuffer = new SocketOutputBuffer(socket, buffersize, params);
+
+ maxHeaderCount = params.getIntParameter(
+ CoreConnectionPNames.MAX_HEADER_COUNT, -1);
+ maxLineLength = params.getIntParameter(
+ CoreConnectionPNames.MAX_LINE_LENGTH, -1);
+
+ this.requestWriter = new HttpRequestWriter(outbuffer, null, params);
+
+ this.metrics = new HttpConnectionMetricsImpl(
+ inbuffer.getMetrics(),
+ outbuffer.getMetrics());
+
+ this.open = true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(getClass().getSimpleName()).append("[");
+ if (isOpen()) {
+ buffer.append(getRemotePort());
+ } else {
+ buffer.append("closed");
+ }
+ buffer.append("]");
+ return buffer.toString();
+ }
+
+
+ private void assertNotOpen() {
+ if (this.open) {
+ throw new IllegalStateException("Connection is already open");
+ }
+ }
+
+ private void assertOpen() {
+ if (!this.open) {
+ throw new IllegalStateException("Connection is not open");
+ }
+ }
+
+ public boolean isOpen() {
+ // to make this method useful, we want to check if the socket is connected
+ return (this.open && this.socket != null && this.socket.isConnected());
+ }
+
+ 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(int timeout) {
+ assertOpen();
+ if (this.socket != null) {
+ try {
+ this.socket.setSoTimeout(timeout);
+ } catch (SocketException ignore) {
+ // It is not quite clear from the original 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 (SocketException ignore) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+
+ public void shutdown() throws IOException {
+ this.open = false;
+ Socket tmpsocket = this.socket;
+ if (tmpsocket != null) {
+ tmpsocket.close();
+ }
+ }
+
+ public void close() throws IOException {
+ if (!this.open) {
+ return;
+ }
+ this.open = false;
+ doFlush();
+ try {
+ try {
+ this.socket.shutdownOutput();
+ } catch (IOException ignore) {
+ }
+ try {
+ this.socket.shutdownInput();
+ } catch (IOException ignore) {
+ }
+ } catch (UnsupportedOperationException ignore) {
+ // if one isn't supported, the other one isn't either
+ }
+ this.socket.close();
+ }
+
+ /**
+ * Sends the request line and all headers over the connection.
+ * @param request the request whose headers to send.
+ * @throws HttpException
+ * @throws IOException
+ */
+ public void sendRequestHeader(final HttpRequest request)
+ throws HttpException, IOException {
+ if (request == null) {
+ throw new IllegalArgumentException("HTTP request may not be null");
+ }
+ assertOpen();
+ this.requestWriter.write(request);
+ this.metrics.incrementRequestCount();
+ }
+
+ /**
+ * Sends the request entity over the connection.
+ * @param request the request whose entity to send.
+ * @throws HttpException
+ * @throws IOException
+ */
+ public void sendRequestEntity(final HttpEntityEnclosingRequest request)
+ throws HttpException, IOException {
+ if (request == null) {
+ throw new IllegalArgumentException("HTTP request may not be null");
+ }
+ 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();
+ }
+
+ /**
+ * Parses the response headers and adds them to the
+ * given {@code headers} object, and returns the response StatusLine
+ * @param headers store parsed header to headers.
+ * @throws IOException
+ * @return StatusLine
+ * @see HttpClientConnection#receiveResponseHeader()
+ */
+ public StatusLine parseResponseHeader(Headers headers)
+ throws IOException, ParseException {
+ assertOpen();
+
+ CharArrayBuffer current = new CharArrayBuffer(64);
+
+ if (inbuffer.readLine(current) == -1) {
+ throw new NoHttpResponseException("The target server failed to respond");
+ }
+
+ // Create the status line from the status string
+ StatusLine statusline = BasicLineParser.DEFAULT.parseStatusLine(
+ current, new ParserCursor(0, current.length()));
+
+ if (HttpLog.LOGV) HttpLog.v("read: " + statusline);
+ int statusCode = statusline.getStatusCode();
+
+ // Parse header body
+ CharArrayBuffer previous = null;
+ int headerNumber = 0;
+ while(true) {
+ if (current == null) {
+ current = new CharArrayBuffer(64);
+ } else {
+ // This must be he buffer used to parse the status
+ current.clear();
+ }
+ 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
+ char first = current.charAt(0);
+ if ((first == ' ' || first == '\t') && previous != null) {
+ // we have continuation folded header
+ // so append value
+ int start = 0;
+ int length = current.length();
+ while (start < length) {
+ char ch = current.charAt(start);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ start++;
+ }
+ if (maxLineLength > 0 &&
+ previous.length() + 1 + current.length() - start >
+ maxLineLength) {
+ throw new IOException("Maximum line length limit exceeded");
+ }
+ previous.append(' ');
+ previous.append(current, start, current.length() - start);
+ } else {
+ if (previous != null) {
+ headers.parseHeader(previous);
+ }
+ headerNumber++;
+ previous = current;
+ current = null;
+ }
+ if (maxHeaderCount > 0 && headerNumber >= maxHeaderCount) {
+ throw new IOException("Maximum header count exceeded");
+ }
+ }
+
+ if (previous != null) {
+ headers.parseHeader(previous);
+ }
+
+ if (statusCode >= 200) {
+ this.metrics.incrementResponseCount();
+ }
+ return statusline;
+ }
+
+ /**
+ * Return the next response entity.
+ * @param headers contains values for parsing entity
+ * @see HttpClientConnection#receiveResponseEntity(HttpResponse response)
+ */
+ public HttpEntity receiveResponseEntity(final Headers headers) {
+ assertOpen();
+ BasicHttpEntity entity = new BasicHttpEntity();
+
+ long len = determineLength(headers);
+ 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));
+ }
+
+ String contentTypeHeader = headers.getContentType();
+ if (contentTypeHeader != null) {
+ entity.setContentType(contentTypeHeader);
+ }
+ String contentEncodingHeader = headers.getContentEncoding();
+ if (contentEncodingHeader != null) {
+ entity.setContentEncoding(contentEncodingHeader);
+ }
+
+ return entity;
+ }
+
+ private long determineLength(final Headers headers) {
+ long transferEncoding = headers.getTransferEncoding();
+ // We use Transfer-Encoding if present and ignore Content-Length.
+ // RFC2616, 4.4 item number 3
+ if (transferEncoding < Headers.NO_TRANSFER_ENCODING) {
+ return transferEncoding;
+ } else {
+ long contentlen = headers.getContentLength();
+ if (contentlen > Headers.NO_CONTENT_LENGTH) {
+ return contentlen;
+ } else {
+ return ContentLengthStrategy.IDENTITY;
+ }
+ }
+ }
+
+ /**
+ * Checks whether this connection has gone down.
+ * Network connections may get closed during some time of inactivity
+ * for several reasons. The next time a read is attempted on such a
+ * connection it will throw an IOException.
+ * This method tries to alleviate this inconvenience by trying to
+ * find out if a connection is still usable. Implementations may do
+ * that by attempting a read with a very small timeout. Thus this
+ * method may block for a small amount of time before returning a result.
+ * It is therefore an <i>expensive</i> operation.
+ *
+ * @return <code>true</code> if attempts to use this connection are
+ * likely to succeed, or <code>false</code> if they are likely
+ * to fail and this connection should be closed
+ */
+ public boolean isStale() {
+ assertOpen();
+ try {
+ this.inbuffer.isDataAvailable(1);
+ return false;
+ } catch (IOException ex) {
+ return true;
+ }
+ }
+
+ /**
+ * Returns a collection of connection metrcis
+ * @return HttpConnectionMetrics
+ */
+ public HttpConnectionMetrics getMetrics() {
+ return this.metrics;
+ }
+}
diff --git a/android/src/android/net/http/CertificateChainValidator.java b/android/src/android/net/http/CertificateChainValidator.java
new file mode 100644
index 0000000..bf3fe02
--- /dev/null
+++ b/android/src/android/net/http/CertificateChainValidator.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import com.android.org.conscrypt.SSLParametersImpl;
+import com.android.org.conscrypt.TrustManagerImpl;
+
+import android.util.Slog;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * Class responsible for all server certificate validation functionality
+ *
+ * {@hide}
+ */
+public class CertificateChainValidator {
+ private static final String TAG = "CertificateChainValidator";
+
+ private static class NoPreloadHolder {
+ /**
+ * The singleton instance of the certificate chain validator.
+ */
+ private static final CertificateChainValidator sInstance = new CertificateChainValidator();
+
+ /**
+ * The singleton instance of the hostname verifier.
+ */
+ private static final HostnameVerifier sVerifier = HttpsURLConnection
+ .getDefaultHostnameVerifier();
+ }
+
+ private X509TrustManager mTrustManager;
+
+ /**
+ * @return The singleton instance of the certificates chain validator
+ */
+ public static CertificateChainValidator getInstance() {
+ return NoPreloadHolder.sInstance;
+ }
+
+ /**
+ * Creates a new certificate chain validator. This is a private constructor.
+ * If you need a Certificate chain validator, call getInstance().
+ */
+ private CertificateChainValidator() {
+ try {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509");
+ tmf.init((KeyStore) null);
+ for (TrustManager tm : tmf.getTrustManagers()) {
+ if (tm instanceof X509TrustManager) {
+ mTrustManager = (X509TrustManager) tm;
+ }
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("X.509 TrustManagerFactory must be available", e);
+ } catch (KeyStoreException e) {
+ throw new RuntimeException("X.509 TrustManagerFactory cannot be initialized", e);
+ }
+
+ if (mTrustManager == null) {
+ throw new RuntimeException(
+ "None of the X.509 TrustManagers are X509TrustManager");
+ }
+ }
+
+ /**
+ * Performs the handshake and server certificates validation
+ * Notice a new chain will be rebuilt by tracing the issuer and subject
+ * before calling checkServerTrusted().
+ * And if the last traced certificate is self issued and it is expired, it
+ * will be dropped.
+ * @param sslSocket The secure connection socket
+ * @param domain The website domain
+ * @return An SSL error object if there is an error and null otherwise
+ */
+ public SslError doHandshakeAndValidateServerCertificates(
+ HttpsConnection connection, SSLSocket sslSocket, String domain)
+ throws IOException {
+ // get a valid SSLSession, close the socket if we fail
+ SSLSession sslSession = sslSocket.getSession();
+ if (!sslSession.isValid()) {
+ closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
+ }
+
+ // retrieve the chain of the server peer certificates
+ Certificate[] peerCertificates =
+ sslSocket.getSession().getPeerCertificates();
+
+ if (peerCertificates == null || peerCertificates.length == 0) {
+ closeSocketThrowException(
+ sslSocket, "failed to retrieve peer certificates");
+ } else {
+ // update the SSL certificate associated with the connection
+ if (connection != null) {
+ if (peerCertificates[0] != null) {
+ connection.setCertificate(
+ new SslCertificate((X509Certificate)peerCertificates[0]));
+ }
+ }
+ }
+
+ return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA");
+ }
+
+ /**
+ * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use
+ * by Chromium HTTPS stack to validate the cert chain.
+ * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format.
+ * @param domain The full website hostname and domain
+ * @param authType The authentication type for the cert chain
+ * @return An SSL error object if there is an error and null otherwise
+ */
+ public static SslError verifyServerCertificates(
+ byte[][] certChain, String domain, String authType)
+ throws IOException {
+
+ if (certChain == null || certChain.length == 0) {
+ throw new IllegalArgumentException("bad certificate chain");
+ }
+
+ X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
+
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ for (int i = 0; i < certChain.length; ++i) {
+ serverCertificates[i] = (X509Certificate) cf.generateCertificate(
+ new ByteArrayInputStream(certChain[i]));
+ }
+ } catch (CertificateException e) {
+ throw new IOException("can't read certificate", e);
+ }
+
+ return verifyServerDomainAndCertificates(serverCertificates, domain, authType);
+ }
+
+ /**
+ * Handles updates to credential storage.
+ */
+ public static void handleTrustStorageUpdate() {
+ TrustManagerFactory tmf;
+ try {
+ tmf = TrustManagerFactory.getInstance("X.509");
+ tmf.init((KeyStore) null);
+ } catch (NoSuchAlgorithmException e) {
+ Slog.w(TAG, "Couldn't find default X.509 TrustManagerFactory");
+ return;
+ } catch (KeyStoreException e) {
+ Slog.w(TAG, "Couldn't initialize default X.509 TrustManagerFactory", e);
+ return;
+ }
+
+ TrustManager[] tms = tmf.getTrustManagers();
+ boolean sentUpdate = false;
+ for (TrustManager tm : tms) {
+ try {
+ Method updateMethod = tm.getClass().getDeclaredMethod("handleTrustStorageUpdate");
+ updateMethod.setAccessible(true);
+ updateMethod.invoke(tm);
+ sentUpdate = true;
+ } catch (Exception e) {
+ }
+ }
+ if (!sentUpdate) {
+ Slog.w(TAG, "Didn't find a TrustManager to handle CA list update");
+ }
+ }
+
+ /**
+ * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates.
+ * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs.
+ * @param chain the cert chain in X509 cert format.
+ * @param domain The full website hostname and domain
+ * @param authType The authentication type for the cert chain
+ * @return An SSL error object if there is an error and null otherwise
+ */
+ private static SslError verifyServerDomainAndCertificates(
+ X509Certificate[] chain, String domain, String authType)
+ throws IOException {
+ // check if the first certificate in the chain is for this site
+ X509Certificate currCertificate = chain[0];
+ if (currCertificate == null) {
+ throw new IllegalArgumentException("certificate for this site is null");
+ }
+
+ boolean valid = domain != null
+ && !domain.isEmpty()
+ && NoPreloadHolder.sVerifier.verify(domain,
+ new DelegatingSSLSession.CertificateWrap(currCertificate));
+ if (!valid) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("certificate not for this host: " + domain);
+ }
+ return new SslError(SslError.SSL_IDMISMATCH, currCertificate);
+ }
+
+ try {
+ X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultX509TrustManager();
+ if (x509TrustManager instanceof TrustManagerImpl) {
+ TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager;
+ trustManager.checkServerTrusted(chain, authType, domain);
+ } else {
+ x509TrustManager.checkServerTrusted(chain, authType);
+ }
+ return null; // No errors.
+ } catch (GeneralSecurityException e) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("failed to validate the certificate chain, error: " +
+ e.getMessage());
+ }
+ return new SslError(SslError.SSL_UNTRUSTED, currCertificate);
+ }
+ }
+
+ /**
+ * Returns the platform default {@link X509TrustManager}.
+ */
+ private X509TrustManager getTrustManager() {
+ return mTrustManager;
+ }
+
+ private void closeSocketThrowException(
+ SSLSocket socket, String errorMessage, String defaultErrorMessage)
+ throws IOException {
+ closeSocketThrowException(
+ socket, errorMessage != null ? errorMessage : defaultErrorMessage);
+ }
+
+ private void closeSocketThrowException(SSLSocket socket,
+ String errorMessage) throws IOException {
+ if (HttpLog.LOGV) {
+ HttpLog.v("validation error: " + errorMessage);
+ }
+
+ if (socket != null) {
+ SSLSession session = socket.getSession();
+ if (session != null) {
+ session.invalidate();
+ }
+
+ socket.close();
+ }
+
+ throw new SSLHandshakeException(errorMessage);
+ }
+}
diff --git a/android/src/android/net/http/Connection.java b/android/src/android/net/http/Connection.java
new file mode 100644
index 0000000..831bd0e
--- /dev/null
+++ b/android/src/android/net/http/Connection.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.BasicHttpContext;
+
+/**
+ * {@hide}
+ */
+abstract class Connection {
+
+ /**
+ * Allow a TCP connection 60 idle seconds before erroring out
+ */
+ static final int SOCKET_TIMEOUT = 60000;
+
+ private static final int SEND = 0;
+ private static final int READ = 1;
+ private static final int DRAIN = 2;
+ private static final int DONE = 3;
+ private static final String[] states = {"SEND", "READ", "DRAIN", "DONE"};
+
+ Context mContext;
+
+ /** The low level connection */
+ protected AndroidHttpClientConnection mHttpClientConnection = null;
+
+ /**
+ * The server SSL certificate associated with this connection
+ * (null if the connection is not secure)
+ * It would be nice to store the whole certificate chain, but
+ * we want to keep things as light-weight as possible
+ */
+ protected SslCertificate mCertificate = null;
+
+ /**
+ * The host this connection is connected to. If using proxy,
+ * this is set to the proxy address
+ */
+ HttpHost mHost;
+
+ /** true if the connection can be reused for sending more requests */
+ private boolean mCanPersist;
+
+ /** context required by ConnectionReuseStrategy. */
+ private HttpContext mHttpContext;
+
+ /** set when cancelled */
+ private static int STATE_NORMAL = 0;
+ private static int STATE_CANCEL_REQUESTED = 1;
+ private int mActive = STATE_NORMAL;
+
+ /** The number of times to try to re-connect (if connect fails). */
+ private final static int RETRY_REQUEST_LIMIT = 2;
+
+ private static final int MIN_PIPE = 2;
+ private static final int MAX_PIPE = 3;
+
+ /**
+ * Doesn't seem to exist anymore in the new HTTP client, so copied here.
+ */
+ private static final String HTTP_CONNECTION = "http.connection";
+
+ RequestFeeder mRequestFeeder;
+
+ /**
+ * Buffer for feeding response blocks to webkit. One block per
+ * connection reduces memory churn.
+ */
+ private byte[] mBuf;
+
+ protected Connection(Context context, HttpHost host,
+ RequestFeeder requestFeeder) {
+ mContext = context;
+ mHost = host;
+ mRequestFeeder = requestFeeder;
+
+ mCanPersist = false;
+ mHttpContext = new BasicHttpContext(null);
+ }
+
+ HttpHost getHost() {
+ return mHost;
+ }
+
+ /**
+ * connection factory: returns an HTTP or HTTPS connection as
+ * necessary
+ */
+ static Connection getConnection(
+ Context context, HttpHost host, HttpHost proxy,
+ RequestFeeder requestFeeder) {
+
+ if (host.getSchemeName().equals("http")) {
+ return new HttpConnection(context, host, requestFeeder);
+ }
+
+ // Otherwise, default to https
+ return new HttpsConnection(context, host, proxy, requestFeeder);
+ }
+
+ /**
+ * @return The server SSL certificate associated with this
+ * connection (null if the connection is not secure)
+ */
+ /* package */ SslCertificate getCertificate() {
+ return mCertificate;
+ }
+
+ /**
+ * Close current network connection
+ * Note: this runs in non-network thread
+ */
+ void cancel() {
+ mActive = STATE_CANCEL_REQUESTED;
+ closeConnection();
+ if (HttpLog.LOGV) HttpLog.v(
+ "Connection.cancel(): connection closed " + mHost);
+ }
+
+ /**
+ * Process requests in queue
+ * pipelines requests
+ */
+ void processRequests(Request firstRequest) {
+ Request req = null;
+ boolean empty;
+ int error = EventHandler.OK;
+ Exception exception = null;
+
+ LinkedList<Request> pipe = new LinkedList<Request>();
+
+ int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
+ int state = SEND;
+
+ while (state != DONE) {
+ if (HttpLog.LOGV) HttpLog.v(
+ states[state] + " pipe " + pipe.size());
+
+ /* If a request was cancelled, give other cancel requests
+ some time to go through so we don't uselessly restart
+ connections */
+ if (mActive == STATE_CANCEL_REQUESTED) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException x) { /* ignore */ }
+ mActive = STATE_NORMAL;
+ }
+
+ switch (state) {
+ case SEND: {
+ if (pipe.size() == maxPipe) {
+ state = READ;
+ break;
+ }
+ /* get a request */
+ if (firstRequest == null) {
+ req = mRequestFeeder.getRequest(mHost);
+ } else {
+ req = firstRequest;
+ firstRequest = null;
+ }
+ if (req == null) {
+ state = DRAIN;
+ break;
+ }
+ req.setConnection(this);
+
+ /* Don't work on cancelled requests. */
+ if (req.mCancelled) {
+ if (HttpLog.LOGV) HttpLog.v(
+ "processRequests(): skipping cancelled request "
+ + req);
+ req.complete();
+ break;
+ }
+
+ if (mHttpClientConnection == null ||
+ !mHttpClientConnection.isOpen()) {
+ /* If this call fails, the address is bad or
+ the net is down. Punt for now.
+
+ FIXME: blow out entire queue here on
+ connection failure if net up? */
+
+ if (!openHttpConnection(req)) {
+ state = DONE;
+ break;
+ }
+ }
+
+ /* we have a connection, let the event handler
+ * know of any associated certificate,
+ * potentially none.
+ */
+ req.mEventHandler.certificate(mCertificate);
+
+ try {
+ /* FIXME: don't increment failure count if old
+ connection? There should not be a penalty for
+ attempting to reuse an old connection */
+ req.sendRequest(mHttpClientConnection);
+ } catch (HttpException e) {
+ exception = e;
+ error = EventHandler.ERROR;
+ } catch (IOException e) {
+ exception = e;
+ error = EventHandler.ERROR_IO;
+ } catch (IllegalStateException e) {
+ exception = e;
+ error = EventHandler.ERROR_IO;
+ }
+ if (exception != null) {
+ if (httpFailure(req, error, exception) &&
+ !req.mCancelled) {
+ /* retry request if not permanent failure
+ or cancelled */
+ pipe.addLast(req);
+ }
+ exception = null;
+ state = clearPipe(pipe) ? DONE : SEND;
+ minPipe = maxPipe = 1;
+ break;
+ }
+
+ pipe.addLast(req);
+ if (!mCanPersist) state = READ;
+ break;
+
+ }
+ case DRAIN:
+ case READ: {
+ empty = !mRequestFeeder.haveRequest(mHost);
+ int pipeSize = pipe.size();
+ if (state != DRAIN && pipeSize < minPipe &&
+ !empty && mCanPersist) {
+ state = SEND;
+ break;
+ } else if (pipeSize == 0) {
+ /* Done if no other work to do */
+ state = empty ? DONE : SEND;
+ break;
+ }
+
+ req = (Request)pipe.removeFirst();
+ if (HttpLog.LOGV) HttpLog.v(
+ "processRequests() reading " + req);
+
+ try {
+ req.readResponse(mHttpClientConnection);
+ } catch (ParseException e) {
+ exception = e;
+ error = EventHandler.ERROR_IO;
+ } catch (IOException e) {
+ exception = e;
+ error = EventHandler.ERROR_IO;
+ } catch (IllegalStateException e) {
+ exception = e;
+ error = EventHandler.ERROR_IO;
+ }
+ if (exception != null) {
+ if (httpFailure(req, error, exception) &&
+ !req.mCancelled) {
+ /* retry request if not permanent failure
+ or cancelled */
+ req.reset();
+ pipe.addFirst(req);
+ }
+ exception = null;
+ mCanPersist = false;
+ }
+ if (!mCanPersist) {
+ if (HttpLog.LOGV) HttpLog.v(
+ "processRequests(): no persist, closing " +
+ mHost);
+
+ closeConnection();
+
+ mHttpContext.removeAttribute(HTTP_CONNECTION);
+ clearPipe(pipe);
+ minPipe = maxPipe = 1;
+ state = SEND;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * After a send/receive failure, any pipelined requests must be
+ * cleared back to the mRequest queue
+ * @return true if mRequests is empty after pipe cleared
+ */
+ private boolean clearPipe(LinkedList<Request> pipe) {
+ boolean empty = true;
+ if (HttpLog.LOGV) HttpLog.v(
+ "Connection.clearPipe(): clearing pipe " + pipe.size());
+ synchronized (mRequestFeeder) {
+ Request tReq;
+ while (!pipe.isEmpty()) {
+ tReq = (Request)pipe.removeLast();
+ if (HttpLog.LOGV) HttpLog.v(
+ "clearPipe() adding back " + mHost + " " + tReq);
+ mRequestFeeder.requeueRequest(tReq);
+ empty = false;
+ }
+ if (empty) empty = !mRequestFeeder.haveRequest(mHost);
+ }
+ return empty;
+ }
+
+ /**
+ * @return true on success
+ */
+ private boolean openHttpConnection(Request req) {
+
+ long now = SystemClock.uptimeMillis();
+ int error = EventHandler.OK;
+ Exception exception = null;
+
+ try {
+ // reset the certificate to null before opening a connection
+ mCertificate = null;
+ mHttpClientConnection = openConnection(req);
+ if (mHttpClientConnection != null) {
+ mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
+ mHttpContext.setAttribute(HTTP_CONNECTION,
+ mHttpClientConnection);
+ } else {
+ // we tried to do SSL tunneling, failed,
+ // and need to drop the request;
+ // we have already informed the handler
+ req.mFailCount = RETRY_REQUEST_LIMIT;
+ return false;
+ }
+ } catch (UnknownHostException e) {
+ if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
+ error = EventHandler.ERROR_LOOKUP;
+ exception = e;
+ } catch (IllegalArgumentException e) {
+ if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
+ error = EventHandler.ERROR_CONNECT;
+ req.mFailCount = RETRY_REQUEST_LIMIT;
+ exception = e;
+ } catch (SSLConnectionClosedByUserException e) {
+ // hack: if we have an SSL connection failure,
+ // we don't want to reconnect
+ req.mFailCount = RETRY_REQUEST_LIMIT;
+ // no error message
+ return false;
+ } catch (SSLHandshakeException e) {
+ // hack: if we have an SSL connection failure,
+ // we don't want to reconnect
+ req.mFailCount = RETRY_REQUEST_LIMIT;
+ if (HttpLog.LOGV) HttpLog.v(
+ "SSL exception performing handshake");
+ error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
+ exception = e;
+ } catch (IOException e) {
+ error = EventHandler.ERROR_CONNECT;
+ exception = e;
+ }
+
+ if (HttpLog.LOGV) {
+ long now2 = SystemClock.uptimeMillis();
+ HttpLog.v("Connection.openHttpConnection() " +
+ (now2 - now) + " " + mHost);
+ }
+
+ if (error == EventHandler.OK) {
+ return true;
+ } else {
+ if (req.mFailCount < RETRY_REQUEST_LIMIT) {
+ // requeue
+ mRequestFeeder.requeueRequest(req);
+ req.mFailCount++;
+ } else {
+ httpFailure(req, error, exception);
+ }
+ return error == EventHandler.OK;
+ }
+ }
+
+ /**
+ * Helper. Calls the mEventHandler's error() method only if
+ * request failed permanently. Increments mFailcount on failure.
+ *
+ * Increments failcount only if the network is believed to be
+ * connected
+ *
+ * @return true if request can be retried (less than
+ * RETRY_REQUEST_LIMIT failures have occurred).
+ */
+ private boolean httpFailure(Request req, int errorId, Exception e) {
+ boolean ret = true;
+
+ // e.printStackTrace();
+ if (HttpLog.LOGV) HttpLog.v(
+ "httpFailure() ******* " + e + " count " + req.mFailCount +
+ " " + mHost + " " + req.getUri());
+
+ if (++req.mFailCount >= RETRY_REQUEST_LIMIT) {
+ ret = false;
+ String error;
+ if (errorId < 0) {
+ error = getEventHandlerErrorString(errorId);
+ } else {
+ Throwable cause = e.getCause();
+ error = cause != null ? cause.toString() : e.getMessage();
+ }
+ req.mEventHandler.error(errorId, error);
+ req.complete();
+ }
+
+ closeConnection();
+ mHttpContext.removeAttribute(HTTP_CONNECTION);
+
+ return ret;
+ }
+
+ private static String getEventHandlerErrorString(int errorId) {
+ switch (errorId) {
+ case EventHandler.OK:
+ return "OK";
+
+ case EventHandler.ERROR:
+ return "ERROR";
+
+ case EventHandler.ERROR_LOOKUP:
+ return "ERROR_LOOKUP";
+
+ case EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME:
+ return "ERROR_UNSUPPORTED_AUTH_SCHEME";
+
+ case EventHandler.ERROR_AUTH:
+ return "ERROR_AUTH";
+
+ case EventHandler.ERROR_PROXYAUTH:
+ return "ERROR_PROXYAUTH";
+
+ case EventHandler.ERROR_CONNECT:
+ return "ERROR_CONNECT";
+
+ case EventHandler.ERROR_IO:
+ return "ERROR_IO";
+
+ case EventHandler.ERROR_TIMEOUT:
+ return "ERROR_TIMEOUT";
+
+ case EventHandler.ERROR_REDIRECT_LOOP:
+ return "ERROR_REDIRECT_LOOP";
+
+ case EventHandler.ERROR_UNSUPPORTED_SCHEME:
+ return "ERROR_UNSUPPORTED_SCHEME";
+
+ case EventHandler.ERROR_FAILED_SSL_HANDSHAKE:
+ return "ERROR_FAILED_SSL_HANDSHAKE";
+
+ case EventHandler.ERROR_BAD_URL:
+ return "ERROR_BAD_URL";
+
+ case EventHandler.FILE_ERROR:
+ return "FILE_ERROR";
+
+ case EventHandler.FILE_NOT_FOUND_ERROR:
+ return "FILE_NOT_FOUND_ERROR";
+
+ case EventHandler.TOO_MANY_REQUESTS_ERROR:
+ return "TOO_MANY_REQUESTS_ERROR";
+
+ default:
+ return "UNKNOWN_ERROR";
+ }
+ }
+
+ HttpContext getHttpContext() {
+ return mHttpContext;
+ }
+
+ /**
+ * Use same logic as ConnectionReuseStrategy
+ * @see ConnectionReuseStrategy
+ */
+ private boolean keepAlive(HttpEntity entity,
+ ProtocolVersion ver, int connType, final HttpContext context) {
+ org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
+ context.getAttribute(ExecutionContext.HTTP_CONNECTION);
+
+ if (conn != null && !conn.isOpen())
+ return false;
+ // do NOT check for stale connection, that is an expensive operation
+
+ if (entity != null) {
+ if (entity.getContentLength() < 0) {
+ if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
+ // if the content length is not known and is not chunk
+ // encoded, the connection cannot be reused
+ return false;
+ }
+ }
+ }
+ // Check for 'Connection' directive
+ if (connType == Headers.CONN_CLOSE) {
+ return false;
+ } else if (connType == Headers.CONN_KEEP_ALIVE) {
+ return true;
+ }
+ // Resorting to protocol version default close connection policy
+ return !ver.lessEquals(HttpVersion.HTTP_1_0);
+ }
+
+ void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
+ mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
+ }
+
+ void setCanPersist(boolean canPersist) {
+ mCanPersist = canPersist;
+ }
+
+ boolean getCanPersist() {
+ return mCanPersist;
+ }
+
+ /** typically http or https... set by subclass */
+ abstract String getScheme();
+ abstract void closeConnection();
+ abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
+
+ /**
+ * Prints request queue to log, for debugging.
+ * returns request count
+ */
+ public synchronized String toString() {
+ return mHost.toString();
+ }
+
+ byte[] getBuf() {
+ if (mBuf == null) mBuf = new byte[8192];
+ return mBuf;
+ }
+
+}
diff --git a/android/src/android/net/http/ConnectionThread.java b/android/src/android/net/http/ConnectionThread.java
new file mode 100644
index 0000000..d825530
--- /dev/null
+++ b/android/src/android/net/http/ConnectionThread.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import java.lang.Thread;
+
+/**
+ * {@hide}
+ */
+class ConnectionThread extends Thread {
+
+ static final int WAIT_TIMEOUT = 5000;
+ static final int WAIT_TICK = 1000;
+
+ // Performance probe
+ long mCurrentThreadTime;
+ long mTotalThreadTime;
+
+ private boolean mWaiting;
+ private volatile boolean mRunning = true;
+ private Context mContext;
+ private RequestQueue.ConnectionManager mConnectionManager;
+ private RequestFeeder mRequestFeeder;
+
+ private int mId;
+ Connection mConnection;
+
+ ConnectionThread(Context context,
+ int id,
+ RequestQueue.ConnectionManager connectionManager,
+ RequestFeeder requestFeeder) {
+ super();
+ mContext = context;
+ setName("http" + id);
+ mId = id;
+ mConnectionManager = connectionManager;
+ mRequestFeeder = requestFeeder;
+ }
+
+ void requestStop() {
+ synchronized (mRequestFeeder) {
+ mRunning = false;
+ mRequestFeeder.notify();
+ }
+ }
+
+ /**
+ * Loop until app shutdown. Runs connections in priority
+ * order.
+ */
+ public void run() {
+ android.os.Process.setThreadPriority(
+ android.os.Process.THREAD_PRIORITY_DEFAULT +
+ android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+
+ // these are used to get performance data. When it is not in the timing,
+ // mCurrentThreadTime is 0. When it starts timing, mCurrentThreadTime is
+ // first set to -1, it will be set to the current thread time when the
+ // next request starts.
+ mCurrentThreadTime = 0;
+ mTotalThreadTime = 0;
+
+ while (mRunning) {
+ if (mCurrentThreadTime == -1) {
+ mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
+ }
+
+ Request request;
+
+ /* Get a request to process */
+ request = mRequestFeeder.getRequest();
+
+ /* wait for work */
+ if (request == null) {
+ synchronized(mRequestFeeder) {
+ if (HttpLog.LOGV) HttpLog.v("ConnectionThread: Waiting for work");
+ mWaiting = true;
+ try {
+ mRequestFeeder.wait();
+ } catch (InterruptedException e) {
+ }
+ mWaiting = false;
+ if (mCurrentThreadTime != 0) {
+ mCurrentThreadTime = SystemClock
+ .currentThreadTimeMillis();
+ }
+ }
+ } else {
+ if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
+ request.mHost + " " + request );
+
+ mConnection = mConnectionManager.getConnection(mContext,
+ request.mHost);
+ mConnection.processRequests(request);
+ if (mConnection.getCanPersist()) {
+ if (!mConnectionManager.recycleConnection(mConnection)) {
+ mConnection.closeConnection();
+ }
+ } else {
+ mConnection.closeConnection();
+ }
+ mConnection = null;
+
+ if (mCurrentThreadTime > 0) {
+ long start = mCurrentThreadTime;
+ mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
+ mTotalThreadTime += mCurrentThreadTime - start;
+ }
+ }
+
+ }
+ }
+
+ public synchronized String toString() {
+ String con = mConnection == null ? "" : mConnection.toString();
+ String active = mWaiting ? "w" : "a";
+ return "cid " + mId + " " + active + " " + con;
+ }
+
+}
diff --git a/android/src/android/net/http/DelegatingSSLSession.java b/android/src/android/net/http/DelegatingSSLSession.java
new file mode 100644
index 0000000..98fbe21
--- /dev/null
+++ b/android/src/android/net/http/DelegatingSSLSession.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * This is only used when a {@code certificate} is available but usage
+ * requires a {@link SSLSession}.
+ *
+ * @hide
+ */
+public class DelegatingSSLSession implements SSLSession {
+ protected DelegatingSSLSession() {
+ }
+
+ public static class CertificateWrap extends DelegatingSSLSession {
+ private final Certificate mCertificate;
+
+ public CertificateWrap(Certificate certificate) {
+ mCertificate = certificate;
+ }
+
+ @Override
+ public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+ return new Certificate[] { mCertificate };
+ }
+ }
+
+
+ @Override
+ public int getApplicationBufferSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getCipherSuite() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCreationTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public byte[] getId() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getLastAccessedTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Certificate[] getLocalCertificates() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Principal getLocalPrincipal() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPacketBufferSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+ throws SSLPeerUnverifiedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPeerHost() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPeerPort() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getProtocol() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SSLSessionContext getSessionContext() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getValue(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getValueNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void invalidate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isValid() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putValue(String name, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeValue(String name) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/android/src/android/net/http/EventHandler.java b/android/src/android/net/http/EventHandler.java
new file mode 100644
index 0000000..3fd471d
--- /dev/null
+++ b/android/src/android/net/http/EventHandler.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+
+/**
+ * Callbacks in this interface are made as an HTTP request is
+ * processed. The normal order of callbacks is status(), headers(),
+ * then multiple data() then endData(). handleSslErrorRequest(), if
+ * there is an SSL certificate error. error() can occur anywhere
+ * in the transaction.
+ *
+ * {@hide}
+ */
+
+public interface EventHandler {
+
+ /**
+ * Error codes used in the error() callback. Positive error codes
+ * are reserved for codes sent by http servers. Negative error
+ * codes are connection/parsing failures, etc.
+ */
+
+ /** Success */
+ public static final int OK = 0;
+ /** Generic error */
+ public static final int ERROR = -1;
+ /** Server or proxy hostname lookup failed */
+ public static final int ERROR_LOOKUP = -2;
+ /** Unsupported authentication scheme (ie, not basic or digest) */
+ public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
+ /** User authentication failed on server */
+ public static final int ERROR_AUTH = -4;
+ /** User authentication failed on proxy */
+ public static final int ERROR_PROXYAUTH = -5;
+ /** Could not connect to server */
+ public static final int ERROR_CONNECT = -6;
+ /** Failed to write to or read from server */
+ public static final int ERROR_IO = -7;
+ /** Connection timed out */
+ public static final int ERROR_TIMEOUT = -8;
+ /** Too many redirects */
+ public static final int ERROR_REDIRECT_LOOP = -9;
+ /** Unsupported URI scheme (ie, not http, https, etc) */
+ public static final int ERROR_UNSUPPORTED_SCHEME = -10;
+ /** Failed to perform SSL handshake */
+ public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
+ /** Bad URL */
+ public static final int ERROR_BAD_URL = -12;
+ /** Generic file error for file:/// loads */
+ public static final int FILE_ERROR = -13;
+ /** File not found error for file:/// loads */
+ public static final int FILE_NOT_FOUND_ERROR = -14;
+ /** Too many requests queued */
+ public static final int TOO_MANY_REQUESTS_ERROR = -15;
+
+ /**
+ * Called after status line has been sucessfully processed.
+ * @param major_version HTTP version advertised by server. major
+ * is the part before the "."
+ * @param minor_version HTTP version advertised by server. minor
+ * is the part after the "."
+ * @param code HTTP Status code. See RFC 2616.
+ * @param reason_phrase Textual explanation sent by server
+ */
+ public void status(int major_version,
+ int minor_version,
+ int code,
+ String reason_phrase);
+
+ /**
+ * Called after all headers are successfully processed.
+ */
+ public void headers(Headers headers);
+
+ /**
+ * An array containing all or part of the http body as read from
+ * the server.
+ * @param data A byte array containing the content
+ * @param len The length of valid content in data
+ *
+ * Note: chunked and compressed encodings are handled within
+ * android.net.http. Decoded data is passed through this
+ * interface.
+ */
+ public void data(byte[] data, int len);
+
+ /**
+ * Called when the document is completely read. No more data()
+ * callbacks will be made after this call
+ */
+ public void endData();
+
+ /**
+ * SSL certificate callback called before resource request is
+ * made, which will be null for insecure connection.
+ */
+ public void certificate(SslCertificate certificate);
+
+ /**
+ * There was trouble.
+ * @param id One of the error codes defined below
+ * @param description of error
+ */
+ public void error(int id, String description);
+
+ /**
+ * SSL certificate error callback. Handles SSL error(s) on the way
+ * up to the user. The callback has to make sure that restartConnection() is called,
+ * otherwise the connection will be suspended indefinitely.
+ * @return True if the callback can handle the error, which means it will
+ * call restartConnection() to unblock the thread later,
+ * otherwise return false.
+ */
+ public boolean handleSslErrorRequest(SslError error);
+
+}
diff --git a/android/src/android/net/http/Headers.java b/android/src/android/net/http/Headers.java
new file mode 100644
index 0000000..0f8b105
--- /dev/null
+++ b/android/src/android/net/http/Headers.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import org.apache.http.HeaderElement;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.message.BasicHeaderValueParser;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.CharArrayBuffer;
+
+/**
+ * Manages received headers
+ *
+ * {@hide}
+ */
+public final class Headers {
+ private static final String LOGTAG = "Http";
+
+ // header parsing constant
+ /**
+ * indicate HTTP 1.0 connection close after the response
+ */
+ public final static int CONN_CLOSE = 1;
+ /**
+ * indicate HTTP 1.1 connection keep alive
+ */
+ public final static int CONN_KEEP_ALIVE = 2;
+
+ // initial values.
+ public final static int NO_CONN_TYPE = 0;
+ public final static long NO_TRANSFER_ENCODING = 0;
+ public final static long NO_CONTENT_LENGTH = -1;
+
+ // header strings
+ public final static String TRANSFER_ENCODING = "transfer-encoding";
+ public final static String CONTENT_LEN = "content-length";
+ public final static String CONTENT_TYPE = "content-type";
+ public final static String CONTENT_ENCODING = "content-encoding";
+ public final static String CONN_DIRECTIVE = "connection";
+
+ public final static String LOCATION = "location";
+ public final static String PROXY_CONNECTION = "proxy-connection";
+
+ public final static String WWW_AUTHENTICATE = "www-authenticate";
+ public final static String PROXY_AUTHENTICATE = "proxy-authenticate";
+ public final static String CONTENT_DISPOSITION = "content-disposition";
+ public final static String ACCEPT_RANGES = "accept-ranges";
+ public final static String EXPIRES = "expires";
+ public final static String CACHE_CONTROL = "cache-control";
+ public final static String LAST_MODIFIED = "last-modified";
+ public final static String ETAG = "etag";
+ public final static String SET_COOKIE = "set-cookie";
+ public final static String PRAGMA = "pragma";
+ public final static String REFRESH = "refresh";
+ public final static String X_PERMITTED_CROSS_DOMAIN_POLICIES = "x-permitted-cross-domain-policies";
+
+ // following hash are generated by String.hashCode()
+ private final static int HASH_TRANSFER_ENCODING = 1274458357;
+ private final static int HASH_CONTENT_LEN = -1132779846;
+ private final static int HASH_CONTENT_TYPE = 785670158;
+ private final static int HASH_CONTENT_ENCODING = 2095084583;
+ private final static int HASH_CONN_DIRECTIVE = -775651618;
+ private final static int HASH_LOCATION = 1901043637;
+ private final static int HASH_PROXY_CONNECTION = 285929373;
+ private final static int HASH_WWW_AUTHENTICATE = -243037365;
+ private final static int HASH_PROXY_AUTHENTICATE = -301767724;
+ private final static int HASH_CONTENT_DISPOSITION = -1267267485;
+ private final static int HASH_ACCEPT_RANGES = 1397189435;
+ private final static int HASH_EXPIRES = -1309235404;
+ private final static int HASH_CACHE_CONTROL = -208775662;
+ private final static int HASH_LAST_MODIFIED = 150043680;
+ private final static int HASH_ETAG = 3123477;
+ private final static int HASH_SET_COOKIE = 1237214767;
+ private final static int HASH_PRAGMA = -980228804;
+ private final static int HASH_REFRESH = 1085444827;
+ private final static int HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES = -1345594014;
+
+ // keep any headers that require direct access in a presized
+ // string array
+ private final static int IDX_TRANSFER_ENCODING = 0;
+ private final static int IDX_CONTENT_LEN = 1;
+ private final static int IDX_CONTENT_TYPE = 2;
+ private final static int IDX_CONTENT_ENCODING = 3;
+ private final static int IDX_CONN_DIRECTIVE = 4;
+ private final static int IDX_LOCATION = 5;
+ private final static int IDX_PROXY_CONNECTION = 6;
+ private final static int IDX_WWW_AUTHENTICATE = 7;
+ private final static int IDX_PROXY_AUTHENTICATE = 8;
+ private final static int IDX_CONTENT_DISPOSITION = 9;
+ private final static int IDX_ACCEPT_RANGES = 10;
+ private final static int IDX_EXPIRES = 11;
+ private final static int IDX_CACHE_CONTROL = 12;
+ private final static int IDX_LAST_MODIFIED = 13;
+ private final static int IDX_ETAG = 14;
+ private final static int IDX_SET_COOKIE = 15;
+ private final static int IDX_PRAGMA = 16;
+ private final static int IDX_REFRESH = 17;
+ private final static int IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES = 18;
+
+ private final static int HEADER_COUNT = 19;
+
+ /* parsed values */
+ private long transferEncoding;
+ private long contentLength; // Content length of the incoming data
+ private int connectionType;
+ private ArrayList<String> cookies = new ArrayList<String>(2);
+
+ private String[] mHeaders = new String[HEADER_COUNT];
+ private final static String[] sHeaderNames = {
+ TRANSFER_ENCODING,
+ CONTENT_LEN,
+ CONTENT_TYPE,
+ CONTENT_ENCODING,
+ CONN_DIRECTIVE,
+ LOCATION,
+ PROXY_CONNECTION,
+ WWW_AUTHENTICATE,
+ PROXY_AUTHENTICATE,
+ CONTENT_DISPOSITION,
+ ACCEPT_RANGES,
+ EXPIRES,
+ CACHE_CONTROL,
+ LAST_MODIFIED,
+ ETAG,
+ SET_COOKIE,
+ PRAGMA,
+ REFRESH,
+ X_PERMITTED_CROSS_DOMAIN_POLICIES
+ };
+
+ // Catch-all for headers not explicitly handled
+ private ArrayList<String> mExtraHeaderNames = new ArrayList<String>(4);
+ private ArrayList<String> mExtraHeaderValues = new ArrayList<String>(4);
+
+ public Headers() {
+ transferEncoding = NO_TRANSFER_ENCODING;
+ contentLength = NO_CONTENT_LENGTH;
+ connectionType = NO_CONN_TYPE;
+ }
+
+ public void parseHeader(CharArrayBuffer buffer) {
+ int pos = setLowercaseIndexOf(buffer, ':');
+ if (pos == -1) {
+ return;
+ }
+ String name = buffer.substringTrimmed(0, pos);
+ if (name.length() == 0) {
+ return;
+ }
+ pos++;
+
+ String val = buffer.substringTrimmed(pos, buffer.length());
+ if (HttpLog.LOGV) {
+ HttpLog.v("hdr " + buffer.length() + " " + buffer);
+ }
+
+ switch (name.hashCode()) {
+ case HASH_TRANSFER_ENCODING:
+ if (name.equals(TRANSFER_ENCODING)) {
+ mHeaders[IDX_TRANSFER_ENCODING] = val;
+ HeaderElement[] encodings = BasicHeaderValueParser.DEFAULT
+ .parseElements(buffer, new ParserCursor(pos,
+ buffer.length()));
+ // The chunked encoding must be the last one applied RFC2616,
+ // 14.41
+ int len = encodings.length;
+ if (HTTP.IDENTITY_CODING.equalsIgnoreCase(val)) {
+ transferEncoding = ContentLengthStrategy.IDENTITY;
+ } else if ((len > 0)
+ && (HTTP.CHUNK_CODING
+ .equalsIgnoreCase(encodings[len - 1].getName()))) {
+ transferEncoding = ContentLengthStrategy.CHUNKED;
+ } else {
+ transferEncoding = ContentLengthStrategy.IDENTITY;
+ }
+ }
+ break;
+ case HASH_CONTENT_LEN:
+ if (name.equals(CONTENT_LEN)) {
+ mHeaders[IDX_CONTENT_LEN] = val;
+ try {
+ contentLength = Long.parseLong(val);
+ } catch (NumberFormatException e) {
+ if (false) {
+ Log.v(LOGTAG, "Headers.headers(): error parsing"
+ + " content length: " + buffer.toString());
+ }
+ }
+ }
+ break;
+ case HASH_CONTENT_TYPE:
+ if (name.equals(CONTENT_TYPE)) {
+ mHeaders[IDX_CONTENT_TYPE] = val;
+ }
+ break;
+ case HASH_CONTENT_ENCODING:
+ if (name.equals(CONTENT_ENCODING)) {
+ mHeaders[IDX_CONTENT_ENCODING] = val;
+ }
+ break;
+ case HASH_CONN_DIRECTIVE:
+ if (name.equals(CONN_DIRECTIVE)) {
+ mHeaders[IDX_CONN_DIRECTIVE] = val;
+ setConnectionType(buffer, pos);
+ }
+ break;
+ case HASH_LOCATION:
+ if (name.equals(LOCATION)) {
+ mHeaders[IDX_LOCATION] = val;
+ }
+ break;
+ case HASH_PROXY_CONNECTION:
+ if (name.equals(PROXY_CONNECTION)) {
+ mHeaders[IDX_PROXY_CONNECTION] = val;
+ setConnectionType(buffer, pos);
+ }
+ break;
+ case HASH_WWW_AUTHENTICATE:
+ if (name.equals(WWW_AUTHENTICATE)) {
+ mHeaders[IDX_WWW_AUTHENTICATE] = val;
+ }
+ break;
+ case HASH_PROXY_AUTHENTICATE:
+ if (name.equals(PROXY_AUTHENTICATE)) {
+ mHeaders[IDX_PROXY_AUTHENTICATE] = val;
+ }
+ break;
+ case HASH_CONTENT_DISPOSITION:
+ if (name.equals(CONTENT_DISPOSITION)) {
+ mHeaders[IDX_CONTENT_DISPOSITION] = val;
+ }
+ break;
+ case HASH_ACCEPT_RANGES:
+ if (name.equals(ACCEPT_RANGES)) {
+ mHeaders[IDX_ACCEPT_RANGES] = val;
+ }
+ break;
+ case HASH_EXPIRES:
+ if (name.equals(EXPIRES)) {
+ mHeaders[IDX_EXPIRES] = val;
+ }
+ break;
+ case HASH_CACHE_CONTROL:
+ if (name.equals(CACHE_CONTROL)) {
+ // In case where we receive more than one header, create a ',' separated list.
+ // This should be ok, according to RFC 2616 chapter 4.2
+ if (mHeaders[IDX_CACHE_CONTROL] != null &&
+ mHeaders[IDX_CACHE_CONTROL].length() > 0) {
+ mHeaders[IDX_CACHE_CONTROL] += (',' + val);
+ } else {
+ mHeaders[IDX_CACHE_CONTROL] = val;
+ }
+ }
+ break;
+ case HASH_LAST_MODIFIED:
+ if (name.equals(LAST_MODIFIED)) {
+ mHeaders[IDX_LAST_MODIFIED] = val;
+ }
+ break;
+ case HASH_ETAG:
+ if (name.equals(ETAG)) {
+ mHeaders[IDX_ETAG] = val;
+ }
+ break;
+ case HASH_SET_COOKIE:
+ if (name.equals(SET_COOKIE)) {
+ mHeaders[IDX_SET_COOKIE] = val;
+ cookies.add(val);
+ }
+ break;
+ case HASH_PRAGMA:
+ if (name.equals(PRAGMA)) {
+ mHeaders[IDX_PRAGMA] = val;
+ }
+ break;
+ case HASH_REFRESH:
+ if (name.equals(REFRESH)) {
+ mHeaders[IDX_REFRESH] = val;
+ }
+ break;
+ case HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES:
+ if (name.equals(X_PERMITTED_CROSS_DOMAIN_POLICIES)) {
+ mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = val;
+ }
+ break;
+ default:
+ mExtraHeaderNames.add(name);
+ mExtraHeaderValues.add(val);
+ }
+ }
+
+ public long getTransferEncoding() {
+ return transferEncoding;
+ }
+
+ public long getContentLength() {
+ return contentLength;
+ }
+
+ public int getConnectionType() {
+ return connectionType;
+ }
+
+ public String getContentType() {
+ return mHeaders[IDX_CONTENT_TYPE];
+ }
+
+ public String getContentEncoding() {
+ return mHeaders[IDX_CONTENT_ENCODING];
+ }
+
+ public String getLocation() {
+ return mHeaders[IDX_LOCATION];
+ }
+
+ public String getWwwAuthenticate() {
+ return mHeaders[IDX_WWW_AUTHENTICATE];
+ }
+
+ public String getProxyAuthenticate() {
+ return mHeaders[IDX_PROXY_AUTHENTICATE];
+ }
+
+ public String getContentDisposition() {
+ return mHeaders[IDX_CONTENT_DISPOSITION];
+ }
+
+ public String getAcceptRanges() {
+ return mHeaders[IDX_ACCEPT_RANGES];
+ }
+
+ public String getExpires() {
+ return mHeaders[IDX_EXPIRES];
+ }
+
+ public String getCacheControl() {
+ return mHeaders[IDX_CACHE_CONTROL];
+ }
+
+ public String getLastModified() {
+ return mHeaders[IDX_LAST_MODIFIED];
+ }
+
+ public String getEtag() {
+ return mHeaders[IDX_ETAG];
+ }
+
+ public ArrayList<String> getSetCookie() {
+ return this.cookies;
+ }
+
+ public String getPragma() {
+ return mHeaders[IDX_PRAGMA];
+ }
+
+ public String getRefresh() {
+ return mHeaders[IDX_REFRESH];
+ }
+
+ public String getXPermittedCrossDomainPolicies() {
+ return mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES];
+ }
+
+ public void setContentLength(long value) {
+ this.contentLength = value;
+ }
+
+ public void setContentType(String value) {
+ mHeaders[IDX_CONTENT_TYPE] = value;
+ }
+
+ public void setContentEncoding(String value) {
+ mHeaders[IDX_CONTENT_ENCODING] = value;
+ }
+
+ public void setLocation(String value) {
+ mHeaders[IDX_LOCATION] = value;
+ }
+
+ public void setWwwAuthenticate(String value) {
+ mHeaders[IDX_WWW_AUTHENTICATE] = value;
+ }
+
+ public void setProxyAuthenticate(String value) {
+ mHeaders[IDX_PROXY_AUTHENTICATE] = value;
+ }
+
+ public void setContentDisposition(String value) {
+ mHeaders[IDX_CONTENT_DISPOSITION] = value;
+ }
+
+ public void setAcceptRanges(String value) {
+ mHeaders[IDX_ACCEPT_RANGES] = value;
+ }
+
+ public void setExpires(String value) {
+ mHeaders[IDX_EXPIRES] = value;
+ }
+
+ public void setCacheControl(String value) {
+ mHeaders[IDX_CACHE_CONTROL] = value;
+ }
+
+ public void setLastModified(String value) {
+ mHeaders[IDX_LAST_MODIFIED] = value;
+ }
+
+ public void setEtag(String value) {
+ mHeaders[IDX_ETAG] = value;
+ }
+
+ public void setXPermittedCrossDomainPolicies(String value) {
+ mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = value;
+ }
+
+ public interface HeaderCallback {
+ public void header(String name, String value);
+ }
+
+ /**
+ * Reports all non-null headers to the callback
+ */
+ public void getHeaders(HeaderCallback hcb) {
+ for (int i = 0; i < HEADER_COUNT; i++) {
+ String h = mHeaders[i];
+ if (h != null) {
+ hcb.header(sHeaderNames[i], h);
+ }
+ }
+ int extraLen = mExtraHeaderNames.size();
+ for (int i = 0; i < extraLen; i++) {
+ if (false) {
+ HttpLog.v("Headers.getHeaders() extra: " + i + " " +
+ mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
+ }
+ hcb.header(mExtraHeaderNames.get(i),
+ mExtraHeaderValues.get(i));
+ }
+
+ }
+
+ private void setConnectionType(CharArrayBuffer buffer, int pos) {
+ if (containsIgnoreCaseTrimmed(buffer, pos, HTTP.CONN_CLOSE)) {
+ connectionType = CONN_CLOSE;
+ } else if (containsIgnoreCaseTrimmed(
+ buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
+ connectionType = CONN_KEEP_ALIVE;
+ }
+ }
+
+
+ /**
+ * Returns true if the buffer contains the given string. Ignores leading
+ * whitespace and case.
+ *
+ * @param buffer to search
+ * @param beginIndex index at which we should start
+ * @param str to search for
+ */
+ static boolean containsIgnoreCaseTrimmed(CharArrayBuffer buffer,
+ int beginIndex, final String str) {
+ int len = buffer.length();
+ char[] chars = buffer.buffer();
+ while (beginIndex < len && HTTP.isWhitespace(chars[beginIndex])) {
+ beginIndex++;
+ }
+ int size = str.length();
+ boolean ok = len >= (beginIndex + size);
+ for (int j=0; ok && (j < size); j++) {
+ char a = chars[beginIndex + j];
+ char b = str.charAt(j);
+ if (a != b) {
+ a = Character.toLowerCase(a);
+ b = Character.toLowerCase(b);
+ ok = a == b;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns index of first occurence ch. Lower cases characters leading up
+ * to first occurrence of ch.
+ */
+ static int setLowercaseIndexOf(CharArrayBuffer buffer, final int ch) {
+
+ int beginIndex = 0;
+ int endIndex = buffer.length();
+ char[] chars = buffer.buffer();
+
+ for (int i = beginIndex; i < endIndex; i++) {
+ char current = chars[i];
+ if (current == ch) {
+ return i;
+ } else {
+ chars[i] = Character.toLowerCase(current);
+ }
+ }
+ return -1;
+ }
+}
diff --git a/android/src/android/net/http/HttpAuthHeader.java b/android/src/android/net/http/HttpAuthHeader.java
new file mode 100644
index 0000000..3abac23
--- /dev/null
+++ b/android/src/android/net/http/HttpAuthHeader.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import java.util.Locale;
+
+/**
+ * HttpAuthHeader: a class to store HTTP authentication-header parameters.
+ * For more information, see: RFC 2617: HTTP Authentication.
+ *
+ * {@hide}
+ */
+public class HttpAuthHeader {
+ /**
+ * Possible HTTP-authentication header tokens to search for:
+ */
+ public final static String BASIC_TOKEN = "Basic";
+ public final static String DIGEST_TOKEN = "Digest";
+
+ private final static String REALM_TOKEN = "realm";
+ private final static String NONCE_TOKEN = "nonce";
+ private final static String STALE_TOKEN = "stale";
+ private final static String OPAQUE_TOKEN = "opaque";
+ private final static String QOP_TOKEN = "qop";
+ private final static String ALGORITHM_TOKEN = "algorithm";
+
+ /**
+ * An authentication scheme. We currently support two different schemes:
+ * HttpAuthHeader.BASIC - basic, and
+ * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
+ */
+ private int mScheme;
+
+ public static final int UNKNOWN = 0;
+ public static final int BASIC = 1;
+ public static final int DIGEST = 2;
+
+ /**
+ * A flag, indicating that the previous request from the client was
+ * rejected because the nonce value was stale. If stale is TRUE
+ * (case-insensitive), the client may wish to simply retry the request
+ * with a new encrypted response, without reprompting the user for a
+ * new username and password.
+ */
+ private boolean mStale;
+
+ /**
+ * A string to be displayed to users so they know which username and
+ * password to use.
+ */
+ private String mRealm;
+
+ /**
+ * A server-specified data string which should be uniquely generated
+ * each time a 401 response is made.
+ */
+ private String mNonce;
+
+ /**
+ * A string of data, specified by the server, which should be returned
+ * by the client unchanged in the Authorization header of subsequent
+ * requests with URIs in the same protection space.
+ */
+ private String mOpaque;
+
+ /**
+ * This directive is optional, but is made so only for backward
+ * compatibility with RFC 2069 [6]; it SHOULD be used by all
+ * implementations compliant with this version of the Digest scheme.
+ * If present, it is a quoted string of one or more tokens indicating
+ * the "quality of protection" values supported by the server. The
+ * value "auth" indicates authentication; the value "auth-int"
+ * indicates authentication with integrity protection.
+ */
+ private String mQop;
+
+ /**
+ * A string indicating a pair of algorithms used to produce the digest
+ * and a checksum. If this is not present it is assumed to be "MD5".
+ */
+ private String mAlgorithm;
+
+ /**
+ * Is this authentication request a proxy authentication request?
+ */
+ private boolean mIsProxy;
+
+ /**
+ * Username string we get from the user.
+ */
+ private String mUsername;
+
+ /**
+ * Password string we get from the user.
+ */
+ private String mPassword;
+
+ /**
+ * Creates a new HTTP-authentication header object from the
+ * input header string.
+ * The header string is assumed to contain parameters of at
+ * most one authentication-scheme (ensured by the caller).
+ */
+ public HttpAuthHeader(String header) {
+ if (header != null) {
+ parseHeader(header);
+ }
+ }
+
+ /**
+ * @return True iff this is a proxy authentication header.
+ */
+ public boolean isProxy() {
+ return mIsProxy;
+ }
+
+ /**
+ * Marks this header as a proxy authentication header.
+ */
+ public void setProxy() {
+ mIsProxy = true;
+ }
+
+ /**
+ * @return The username string.
+ */
+ public String getUsername() {
+ return mUsername;
+ }
+
+ /**
+ * Sets the username string.
+ */
+ public void setUsername(String username) {
+ mUsername = username;
+ }
+
+ /**
+ * @return The password string.
+ */
+ public String getPassword() {
+ return mPassword;
+ }
+
+ /**
+ * Sets the password string.
+ */
+ public void setPassword(String password) {
+ mPassword = password;
+ }
+
+ /**
+ * @return True iff this is the BASIC-authentication request.
+ */
+ public boolean isBasic () {
+ return mScheme == BASIC;
+ }
+
+ /**
+ * @return True iff this is the DIGEST-authentication request.
+ */
+ public boolean isDigest() {
+ return mScheme == DIGEST;
+ }
+
+ /**
+ * @return The authentication scheme requested. We currently
+ * support two schemes:
+ * HttpAuthHeader.BASIC - basic, and
+ * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
+ */
+ public int getScheme() {
+ return mScheme;
+ }
+
+ /**
+ * @return True if indicating that the previous request from
+ * the client was rejected because the nonce value was stale.
+ */
+ public boolean getStale() {
+ return mStale;
+ }
+
+ /**
+ * @return The realm value or null if there is none.
+ */
+ public String getRealm() {
+ return mRealm;
+ }
+
+ /**
+ * @return The nonce value or null if there is none.
+ */
+ public String getNonce() {
+ return mNonce;
+ }
+
+ /**
+ * @return The opaque value or null if there is none.
+ */
+ public String getOpaque() {
+ return mOpaque;
+ }
+
+ /**
+ * @return The QOP ("quality-of_protection") value or null if
+ * there is none. The QOP value is always lower-case.
+ */
+ public String getQop() {
+ return mQop;
+ }
+
+ /**
+ * @return The name of the algorithm used or null if there is
+ * none. By default, MD5 is used.
+ */
+ public String getAlgorithm() {
+ return mAlgorithm;
+ }
+
+ /**
+ * @return True iff the authentication scheme requested by the
+ * server is supported; currently supported schemes:
+ * BASIC,
+ * DIGEST (only algorithm="md5", no qop or qop="auth).
+ */
+ public boolean isSupportedScheme() {
+ // it is a good idea to enforce non-null realms!
+ if (mRealm != null) {
+ if (mScheme == BASIC) {
+ return true;
+ } else {
+ if (mScheme == DIGEST) {
+ return
+ mAlgorithm.equals("md5") &&
+ (mQop == null || mQop.equals("auth"));
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses the header scheme name and then scheme parameters if
+ * the scheme is supported.
+ */
+ private void parseHeader(String header) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpAuthHeader.parseHeader(): header: " + header);
+ }
+
+ if (header != null) {
+ String parameters = parseScheme(header);
+ if (parameters != null) {
+ // if we have a supported scheme
+ if (mScheme != UNKNOWN) {
+ parseParameters(parameters);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses the authentication scheme name. If we have a Digest
+ * scheme, sets the algorithm value to the default of MD5.
+ * @return The authentication scheme parameters string to be
+ * parsed later (if the scheme is supported) or null if failed
+ * to parse the scheme (the header value is null?).
+ */
+ private String parseScheme(String header) {
+ if (header != null) {
+ int i = header.indexOf(' ');
+ if (i >= 0) {
+ String scheme = header.substring(0, i).trim();
+ if (scheme.equalsIgnoreCase(DIGEST_TOKEN)) {
+ mScheme = DIGEST;
+
+ // md5 is the default algorithm!!!
+ mAlgorithm = "md5";
+ } else {
+ if (scheme.equalsIgnoreCase(BASIC_TOKEN)) {
+ mScheme = BASIC;
+ }
+ }
+
+ return header.substring(i + 1);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses a comma-separated list of authentification scheme
+ * parameters.
+ */
+ private void parseParameters(String parameters) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpAuthHeader.parseParameters():" +
+ " parameters: " + parameters);
+ }
+
+ if (parameters != null) {
+ int i;
+ do {
+ i = parameters.indexOf(',');
+ if (i < 0) {
+ // have only one parameter
+ parseParameter(parameters);
+ } else {
+ parseParameter(parameters.substring(0, i));
+ parameters = parameters.substring(i + 1);
+ }
+ } while (i >= 0);
+ }
+ }
+
+ /**
+ * Parses a single authentication scheme parameter. The parameter
+ * string is expected to follow the format: PARAMETER=VALUE.
+ */
+ private void parseParameter(String parameter) {
+ if (parameter != null) {
+ // here, we are looking for the 1st occurence of '=' only!!!
+ int i = parameter.indexOf('=');
+ if (i >= 0) {
+ String token = parameter.substring(0, i).trim();
+ String value =
+ trimDoubleQuotesIfAny(parameter.substring(i + 1).trim());
+
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpAuthHeader.parseParameter():" +
+ " token: " + token +
+ " value: " + value);
+ }
+
+ if (token.equalsIgnoreCase(REALM_TOKEN)) {
+ mRealm = value;
+ } else {
+ if (mScheme == DIGEST) {
+ parseParameter(token, value);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * If the token is a known parameter name, parses and initializes
+ * the token value.
+ */
+ private void parseParameter(String token, String value) {
+ if (token != null && value != null) {
+ if (token.equalsIgnoreCase(NONCE_TOKEN)) {
+ mNonce = value;
+ return;
+ }
+
+ if (token.equalsIgnoreCase(STALE_TOKEN)) {
+ parseStale(value);
+ return;
+ }
+
+ if (token.equalsIgnoreCase(OPAQUE_TOKEN)) {
+ mOpaque = value;
+ return;
+ }
+
+ if (token.equalsIgnoreCase(QOP_TOKEN)) {
+ mQop = value.toLowerCase(Locale.ROOT);
+ return;
+ }
+
+ if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) {
+ mAlgorithm = value.toLowerCase(Locale.ROOT);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Parses and initializes the 'stale' paramer value. Any value
+ * different from case-insensitive "true" is considered "false".
+ */
+ private void parseStale(String value) {
+ if (value != null) {
+ if (value.equalsIgnoreCase("true")) {
+ mStale = true;
+ }
+ }
+ }
+
+ /**
+ * Trims double-quotes around a parameter value if there are any.
+ * @return The string value without the outermost pair of double-
+ * quotes or null if the original value is null.
+ */
+ static private String trimDoubleQuotesIfAny(String value) {
+ if (value != null) {
+ int len = value.length();
+ if (len > 2 &&
+ value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') {
+ return value.substring(1, len - 1);
+ }
+ }
+
+ return value;
+ }
+}
diff --git a/android/src/android/net/http/HttpConnection.java b/android/src/android/net/http/HttpConnection.java
new file mode 100644
index 0000000..edf8fed
--- /dev/null
+++ b/android/src/android/net/http/HttpConnection.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.content.Context;
+
+import java.net.Socket;
+import java.io.IOException;
+
+import org.apache.http.HttpHost;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+
+/**
+ * A requestConnection connecting to a normal (non secure) http server
+ *
+ * {@hide}
+ */
+class HttpConnection extends Connection {
+
+ HttpConnection(Context context, HttpHost host,
+ RequestFeeder requestFeeder) {
+ super(context, host, requestFeeder);
+ }
+
+ /**
+ * Opens the connection to a http server
+ *
+ * @return the opened low level connection
+ * @throws IOException if the connection fails for any reason.
+ */
+ @Override
+ AndroidHttpClientConnection openConnection(Request req) throws IOException {
+
+ // Update the certificate info (connection not secure - set to null)
+ EventHandler eventHandler = req.getEventHandler();
+ mCertificate = null;
+ eventHandler.certificate(mCertificate);
+
+ AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
+ BasicHttpParams params = new BasicHttpParams();
+ Socket sock = new Socket(mHost.getHostName(), mHost.getPort());
+ params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
+ conn.bind(sock, params);
+ return conn;
+ }
+
+ /**
+ * Closes the low level connection.
+ *
+ * If an exception is thrown then it is assumed that the
+ * connection will have been closed (to the extent possible)
+ * anyway and the caller does not need to take any further action.
+ *
+ */
+ void closeConnection() {
+ try {
+ if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
+ mHttpClientConnection.close();
+ }
+ } catch (IOException e) {
+ if (HttpLog.LOGV) HttpLog.v(
+ "closeConnection(): failed closing connection " +
+ mHost);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Restart a secure connection suspended waiting for user interaction.
+ */
+ void restartConnection(boolean abort) {
+ // not required for plain http connections
+ }
+
+ String getScheme() {
+ return "http";
+ }
+}
diff --git a/android/src/android/net/http/HttpLog.java b/android/src/android/net/http/HttpLog.java
new file mode 100644
index 0000000..0934664
--- /dev/null
+++ b/android/src/android/net/http/HttpLog.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * package-level logging flag
+ */
+
+package android.net.http;
+
+import android.os.SystemClock;
+
+import android.util.Log;
+
+/**
+ * {@hide}
+ */
+class HttpLog {
+ private final static String LOGTAG = "http";
+
+ private static final boolean DEBUG = false;
+ static final boolean LOGV = false;
+
+ static void v(String logMe) {
+ Log.v(LOGTAG, SystemClock.uptimeMillis() + " " + Thread.currentThread().getName() + " " + logMe);
+ }
+
+ static void e(String logMe) {
+ Log.e(LOGTAG, logMe);
+ }
+}
diff --git a/android/src/android/net/http/HttpsConnection.java b/android/src/android/net/http/HttpsConnection.java
new file mode 100644
index 0000000..a8674de
--- /dev/null
+++ b/android/src/android/net/http/HttpsConnection.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.content.Context;
+import android.util.Log;
+import com.android.org.conscrypt.FileClientSessionCache;
+import com.android.org.conscrypt.OpenSSLContextImpl;
+import com.android.org.conscrypt.SSLClientSessionCache;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpStatus;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.security.KeyManagementException;
+import java.security.cert.X509Certificate;
+import java.util.Locale;
+
+/**
+ * A Connection connecting to a secure http server or tunneling through
+ * a http proxy server to a https server.
+ *
+ * @hide
+ */
+public class HttpsConnection extends Connection {
+
+ /**
+ * SSL socket factory
+ */
+ private static SSLSocketFactory mSslSocketFactory = null;
+
+ static {
+ // This initialization happens in the zygote. It triggers some
+ // lazy initialization that can will benefit later invocations of
+ // initializeEngine().
+ initializeEngine(null);
+ }
+
+ /**
+ * @hide
+ *
+ * @param sessionDir directory to cache SSL sessions
+ */
+ public static void initializeEngine(File sessionDir) {
+ try {
+ SSLClientSessionCache cache = null;
+ if (sessionDir != null) {
+ Log.d("HttpsConnection", "Caching SSL sessions in "
+ + sessionDir + ".");
+ cache = FileClientSessionCache.usingDirectory(sessionDir);
+ }
+
+ OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
+
+ // here, trust managers is a single trust-all manager
+ TrustManager[] trustManagers = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ public void checkClientTrusted(
+ X509Certificate[] certs, String authType) {
+ }
+
+ public void checkServerTrusted(
+ X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+
+ sslContext.engineInit(null, trustManagers, null);
+ sslContext.engineGetClientSessionContext().setPersistentCache(cache);
+
+ synchronized (HttpsConnection.class) {
+ mSslSocketFactory = sslContext.engineGetSocketFactory();
+ }
+ } catch (KeyManagementException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private synchronized static SSLSocketFactory getSocketFactory() {
+ return mSslSocketFactory;
+ }
+
+ /**
+ * Object to wait on when suspending the SSL connection
+ */
+ private Object mSuspendLock = new Object();
+
+ /**
+ * True if the connection is suspended pending the result of asking the
+ * user about an error.
+ */
+ private boolean mSuspended = false;
+
+ /**
+ * True if the connection attempt should be aborted due to an ssl
+ * error.
+ */
+ private boolean mAborted = false;
+
+ // Used when connecting through a proxy.
+ private HttpHost mProxyHost;
+
+ /**
+ * Contructor for a https connection.
+ */
+ HttpsConnection(Context context, HttpHost host, HttpHost proxy,
+ RequestFeeder requestFeeder) {
+ super(context, host, requestFeeder);
+ mProxyHost = proxy;
+ }
+
+ /**
+ * Sets the server SSL certificate associated with this
+ * connection.
+ * @param certificate The SSL certificate
+ */
+ /* package */ void setCertificate(SslCertificate certificate) {
+ mCertificate = certificate;
+ }
+
+ /**
+ * Opens the connection to a http server or proxy.
+ *
+ * @return the opened low level connection
+ * @throws IOException if the connection fails for any reason.
+ */
+ @Override
+ AndroidHttpClientConnection openConnection(Request req) throws IOException {
+ SSLSocket sslSock = null;
+
+ if (mProxyHost != null) {
+ // If we have a proxy set, we first send a CONNECT request
+ // to the proxy; if the proxy returns 200 OK, we negotiate
+ // a secure connection to the target server via the proxy.
+ // If the request fails, we drop it, but provide the event
+ // handler with the response status and headers. The event
+ // handler is then responsible for cancelling the load or
+ // issueing a new request.
+ AndroidHttpClientConnection proxyConnection = null;
+ Socket proxySock = null;
+ try {
+ proxySock = new Socket
+ (mProxyHost.getHostName(), mProxyHost.getPort());
+
+ proxySock.setSoTimeout(60 * 1000);
+
+ proxyConnection = new AndroidHttpClientConnection();
+ HttpParams params = new BasicHttpParams();
+ HttpConnectionParams.setSocketBufferSize(params, 8192);
+
+ proxyConnection.bind(proxySock, params);
+ } catch(IOException e) {
+ if (proxyConnection != null) {
+ proxyConnection.close();
+ }
+
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to establish a connection to the proxy";
+ }
+
+ throw new IOException(errorMessage);
+ }
+
+ StatusLine statusLine = null;
+ int statusCode = 0;
+ Headers headers = new Headers();
+ try {
+ BasicHttpRequest proxyReq = new BasicHttpRequest
+ ("CONNECT", mHost.toHostString());
+
+ // add all 'proxy' headers from the original request, we also need
+ // to add 'host' header unless we want proxy to answer us with a
+ // 400 Bad Request
+ for (Header h : req.mHttpRequest.getAllHeaders()) {
+ String headerName = h.getName().toLowerCase(Locale.ROOT);
+ if (headerName.startsWith("proxy") || headerName.equals("keep-alive")
+ || headerName.equals("host")) {
+ proxyReq.addHeader(h);
+ }
+ }
+
+ proxyConnection.sendRequestHeader(proxyReq);
+ proxyConnection.flush();
+
+ // it is possible to receive informational status
+ // codes prior to receiving actual headers;
+ // all those status codes are smaller than OK 200
+ // a loop is a standard way of dealing with them
+ do {
+ statusLine = proxyConnection.parseResponseHeader(headers);
+ statusCode = statusLine.getStatusCode();
+ } while (statusCode < HttpStatus.SC_OK);
+ } catch (ParseException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to send a CONNECT request";
+ }
+
+ throw new IOException(errorMessage);
+ } catch (HttpException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to send a CONNECT request";
+ }
+
+ throw new IOException(errorMessage);
+ } catch (IOException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to send a CONNECT request";
+ }
+
+ throw new IOException(errorMessage);
+ }
+
+ if (statusCode == HttpStatus.SC_OK) {
+ try {
+ sslSock = (SSLSocket) getSocketFactory().createSocket(
+ proxySock, mHost.getHostName(), mHost.getPort(), true);
+ } catch(IOException e) {
+ if (sslSock != null) {
+ sslSock.close();
+ }
+
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to create an SSL socket";
+ }
+ throw new IOException(errorMessage);
+ }
+ } else {
+ // if the code is not OK, inform the event handler
+ ProtocolVersion version = statusLine.getProtocolVersion();
+
+ req.mEventHandler.status(version.getMajor(),
+ version.getMinor(),
+ statusCode,
+ statusLine.getReasonPhrase());
+ req.mEventHandler.headers(headers);
+ req.mEventHandler.endData();
+
+ proxyConnection.close();
+
+ // here, we return null to indicate that the original
+ // request needs to be dropped
+ return null;
+ }
+ } else {
+ // if we do not have a proxy, we simply connect to the host
+ try {
+ sslSock = (SSLSocket) getSocketFactory().createSocket(
+ mHost.getHostName(), mHost.getPort());
+ sslSock.setSoTimeout(SOCKET_TIMEOUT);
+ } catch(IOException e) {
+ if (sslSock != null) {
+ sslSock.close();
+ }
+
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage = "failed to create an SSL socket";
+ }
+
+ throw new IOException(errorMessage);
+ }
+ }
+
+ // do handshake and validate server certificates
+ SslError error = CertificateChainValidator.getInstance().
+ doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
+
+ // Inform the user if there is a problem
+ if (error != null) {
+ // handleSslErrorRequest may immediately unsuspend if it wants to
+ // allow the certificate anyway.
+ // So we mark the connection as suspended, call handleSslErrorRequest
+ // then check if we're still suspended and only wait if we actually
+ // need to.
+ synchronized (mSuspendLock) {
+ mSuspended = true;
+ }
+ // don't hold the lock while calling out to the event handler
+ boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
+ if(!canHandle) {
+ throw new IOException("failed to handle "+ error);
+ }
+ synchronized (mSuspendLock) {
+ if (mSuspended) {
+ try {
+ // Put a limit on how long we are waiting; if the timeout
+ // expires (which should never happen unless you choose
+ // to ignore the SSL error dialog for a very long time),
+ // we wake up the thread and abort the request. This is
+ // to prevent us from stalling the network if things go
+ // very bad.
+ mSuspendLock.wait(10 * 60 * 1000);
+ if (mSuspended) {
+ // mSuspended is true if we have not had a chance to
+ // restart the connection yet (ie, the wait timeout
+ // has expired)
+ mSuspended = false;
+ mAborted = true;
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpsConnection.openConnection():" +
+ " SSL timeout expired and request was cancelled!!!");
+ }
+ }
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ if (mAborted) {
+ // The user decided not to use this unverified connection
+ // so close it immediately.
+ sslSock.close();
+ throw new SSLConnectionClosedByUserException("connection closed by the user");
+ }
+ }
+ }
+
+ // All went well, we have an open, verified connection.
+ AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
+ BasicHttpParams params = new BasicHttpParams();
+ params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
+ conn.bind(sslSock, params);
+
+ return conn;
+ }
+
+ /**
+ * Closes the low level connection.
+ *
+ * If an exception is thrown then it is assumed that the connection will
+ * have been closed (to the extent possible) anyway and the caller does not
+ * need to take any further action.
+ *
+ */
+ @Override
+ void closeConnection() {
+ // if the connection has been suspended due to an SSL error
+ if (mSuspended) {
+ // wake up the network thread
+ restartConnection(false);
+ }
+
+ try {
+ if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
+ mHttpClientConnection.close();
+ }
+ } catch (IOException e) {
+ if (HttpLog.LOGV)
+ HttpLog.v("HttpsConnection.closeConnection():" +
+ " failed closing connection " + mHost);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Restart a secure connection suspended waiting for user interaction.
+ */
+ void restartConnection(boolean proceed) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpsConnection.restartConnection():" +
+ " proceed: " + proceed);
+ }
+
+ synchronized (mSuspendLock) {
+ if (mSuspended) {
+ mSuspended = false;
+ mAborted = !proceed;
+ mSuspendLock.notify();
+ }
+ }
+ }
+
+ @Override
+ String getScheme() {
+ return "https";
+ }
+}
+
+/**
+ * Simple exception we throw if the SSL connection is closed by the user.
+ *
+ * {@hide}
+ */
+class SSLConnectionClosedByUserException extends SSLException {
+
+ public SSLConnectionClosedByUserException(String reason) {
+ super(reason);
+ }
+}
diff --git a/android/src/android/net/http/IdleCache.java b/android/src/android/net/http/IdleCache.java
new file mode 100644
index 0000000..fda6009
--- /dev/null
+++ b/android/src/android/net/http/IdleCache.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Hangs onto idle live connections for a little while
+ */
+
+package android.net.http;
+
+import org.apache.http.HttpHost;
+
+import android.os.SystemClock;
+
+/**
+ * {@hide}
+ */
+class IdleCache {
+
+ class Entry {
+ HttpHost mHost;
+ Connection mConnection;
+ long mTimeout;
+ };
+
+ private final static int IDLE_CACHE_MAX = 8;
+
+ /* Allow five consecutive empty queue checks before shutdown */
+ private final static int EMPTY_CHECK_MAX = 5;
+
+ /* six second timeout for connections */
+ private final static int TIMEOUT = 6 * 1000;
+ private final static int CHECK_INTERVAL = 2 * 1000;
+ private Entry[] mEntries = new Entry[IDLE_CACHE_MAX];
+
+ private int mCount = 0;
+
+ private IdleReaper mThread = null;
+
+ /* stats */
+ private int mCached = 0;
+ private int mReused = 0;
+
+ IdleCache() {
+ for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+ mEntries[i] = new Entry();
+ }
+ }
+
+ /**
+ * Caches connection, if there is room.
+ * @return true if connection cached
+ */
+ synchronized boolean cacheConnection(
+ HttpHost host, Connection connection) {
+
+ boolean ret = false;
+
+ if (HttpLog.LOGV) {
+ HttpLog.v("IdleCache size " + mCount + " host " + host);
+ }
+
+ if (mCount < IDLE_CACHE_MAX) {
+ long time = SystemClock.uptimeMillis();
+ for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+ Entry entry = mEntries[i];
+ if (entry.mHost == null) {
+ entry.mHost = host;
+ entry.mConnection = connection;
+ entry.mTimeout = time + TIMEOUT;
+ mCount++;
+ if (HttpLog.LOGV) mCached++;
+ ret = true;
+ if (mThread == null) {
+ mThread = new IdleReaper();
+ mThread.start();
+ }
+ break;
+ }
+ }
+ }
+ return ret;
+ }
+
+ synchronized Connection getConnection(HttpHost host) {
+ Connection ret = null;
+
+ if (mCount > 0) {
+ for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+ Entry entry = mEntries[i];
+ HttpHost eHost = entry.mHost;
+ if (eHost != null && eHost.equals(host)) {
+ ret = entry.mConnection;
+ entry.mHost = null;
+ entry.mConnection = null;
+ mCount--;
+ if (HttpLog.LOGV) mReused++;
+ break;
+ }
+ }
+ }
+ return ret;
+ }
+
+ synchronized void clear() {
+ for (int i = 0; mCount > 0 && i < IDLE_CACHE_MAX; i++) {
+ Entry entry = mEntries[i];
+ if (entry.mHost != null) {
+ entry.mHost = null;
+ entry.mConnection.closeConnection();
+ entry.mConnection = null;
+ mCount--;
+ }
+ }
+ }
+
+ private synchronized void clearIdle() {
+ if (mCount > 0) {
+ long time = SystemClock.uptimeMillis();
+ for (int i = 0; i < IDLE_CACHE_MAX; i++) {
+ Entry entry = mEntries[i];
+ if (entry.mHost != null && time > entry.mTimeout) {
+ entry.mHost = null;
+ entry.mConnection.closeConnection();
+ entry.mConnection = null;
+ mCount--;
+ }
+ }
+ }
+ }
+
+ private class IdleReaper extends Thread {
+
+ public void run() {
+ int check = 0;
+
+ setName("IdleReaper");
+ android.os.Process.setThreadPriority(
+ android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ synchronized (IdleCache.this) {
+ while (check < EMPTY_CHECK_MAX) {
+ try {
+ IdleCache.this.wait(CHECK_INTERVAL);
+ } catch (InterruptedException ex) {
+ }
+ if (mCount == 0) {
+ check++;
+ } else {
+ check = 0;
+ clearIdle();
+ }
+ }
+ mThread = null;
+ }
+ if (HttpLog.LOGV) {
+ HttpLog.v("IdleCache IdleReaper shutdown: cached " + mCached +
+ " reused " + mReused);
+ mCached = 0;
+ mReused = 0;
+ }
+ }
+ }
+}
diff --git a/android/src/android/net/http/LoggingEventHandler.java b/android/src/android/net/http/LoggingEventHandler.java
new file mode 100644
index 0000000..bdafa0b
--- /dev/null
+++ b/android/src/android/net/http/LoggingEventHandler.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A test EventHandler: Logs everything received
+ */
+
+package android.net.http;
+
+import android.net.http.Headers;
+
+/**
+ * {@hide}
+ */
+public class LoggingEventHandler implements EventHandler {
+
+ public void requestSent() {
+ HttpLog.v("LoggingEventHandler:requestSent()");
+ }
+
+ public void status(int major_version,
+ int minor_version,
+ int code, /* Status-Code value */
+ String reason_phrase) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler:status() major: " + major_version +
+ " minor: " + minor_version +
+ " code: " + code +
+ " reason: " + reason_phrase);
+ }
+ }
+
+ public void headers(Headers headers) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler:headers()");
+ HttpLog.v(headers.toString());
+ }
+ }
+
+ public void locationChanged(String newLocation, boolean permanent) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler: locationChanged() " + newLocation +
+ " permanent " + permanent);
+ }
+ }
+
+ public void data(byte[] data, int len) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler: data() " + len + " bytes");
+ }
+ // HttpLog.v(new String(data, 0, len));
+ }
+ public void endData() {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler: endData() called");
+ }
+ }
+
+ public void certificate(SslCertificate certificate) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler: certificate(): " + certificate);
+ }
+ }
+
+ public void error(int id, String description) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler: error() called Id:" + id +
+ " description " + description);
+ }
+ }
+
+ public boolean handleSslErrorRequest(SslError error) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("LoggingEventHandler: handleSslErrorRequest():" + error);
+ }
+ // return false so that the caller thread won't wait forever
+ return false;
+ }
+}
diff --git a/android/src/android/net/http/Request.java b/android/src/android/net/http/Request.java
new file mode 100644
index 0000000..76d7bb9
--- /dev/null
+++ b/android/src/android/net/http/Request.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpStatus;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+
+import org.apache.http.StatusLine;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.protocol.RequestContent;
+
+/**
+ * Represents an HTTP request for a given host.
+ *
+ * {@hide}
+ */
+
+class Request {
+
+ /** The eventhandler to call as the request progresses */
+ EventHandler mEventHandler;
+
+ private Connection mConnection;
+
+ /** The Apache http request */
+ BasicHttpRequest mHttpRequest;
+
+ /** The path component of this request */
+ String mPath;
+
+ /** Host serving this request */
+ HttpHost mHost;
+
+ /** Set if I'm using a proxy server */
+ HttpHost mProxyHost;
+
+ /** True if request has been cancelled */
+ volatile boolean mCancelled = false;
+
+ int mFailCount = 0;
+
+ // This will be used to set the Range field if we retry a connection. This
+ // is http/1.1 feature.
+ private int mReceivedBytes = 0;
+
+ private InputStream mBodyProvider;
+ private int mBodyLength;
+
+ private final static String HOST_HEADER = "Host";
+ private final static String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
+ private final static String CONTENT_LENGTH_HEADER = "content-length";
+
+ /* Used to synchronize waitUntilComplete() requests */
+ private final Object mClientResource = new Object();
+
+ /** True if loading should be paused **/
+ private boolean mLoadingPaused = false;
+
+ /**
+ * Processor used to set content-length and transfer-encoding
+ * headers.
+ */
+ private static RequestContent requestContentProcessor =
+ new RequestContent();
+
+ /**
+ * Instantiates a new Request.
+ * @param method GET/POST/PUT
+ * @param host The server that will handle this request
+ * @param path path part of URI
+ * @param bodyProvider InputStream providing HTTP body, null if none
+ * @param bodyLength length of body, must be 0 if bodyProvider is null
+ * @param eventHandler request will make progress callbacks on
+ * this interface
+ * @param headers reqeust headers
+ */
+ Request(String method, HttpHost host, HttpHost proxyHost, String path,
+ InputStream bodyProvider, int bodyLength,
+ EventHandler eventHandler,
+ Map<String, String> headers) {
+ mEventHandler = eventHandler;
+ mHost = host;
+ mProxyHost = proxyHost;
+ mPath = path;
+ mBodyProvider = bodyProvider;
+ mBodyLength = bodyLength;
+
+ if (bodyProvider == null && !"POST".equalsIgnoreCase(method)) {
+ mHttpRequest = new BasicHttpRequest(method, getUri());
+ } else {
+ mHttpRequest = new BasicHttpEntityEnclosingRequest(
+ method, getUri());
+ // it is ok to have null entity for BasicHttpEntityEnclosingRequest.
+ // By using BasicHttpEntityEnclosingRequest, it will set up the
+ // correct content-length, content-type and content-encoding.
+ if (bodyProvider != null) {
+ setBodyProvider(bodyProvider, bodyLength);
+ }
+ }
+ addHeader(HOST_HEADER, getHostPort());
+
+ /* FIXME: if webcore will make the root document a
+ high-priority request, we can ask for gzip encoding only on
+ high priority reqs (saving the trouble for images, etc) */
+ addHeader(ACCEPT_ENCODING_HEADER, "gzip");
+ addHeaders(headers);
+ }
+
+ /**
+ * @param pause True if the load should be paused.
+ */
+ synchronized void setLoadingPaused(boolean pause) {
+ mLoadingPaused = pause;
+
+ // Wake up the paused thread if we're unpausing the load.
+ if (!mLoadingPaused) {
+ notify();
+ }
+ }
+
+ /**
+ * @param connection Request served by this connection
+ */
+ void setConnection(Connection connection) {
+ mConnection = connection;
+ }
+
+ /* package */ EventHandler getEventHandler() {
+ return mEventHandler;
+ }
+
+ /**
+ * Add header represented by given pair to request. Header will
+ * be formatted in request as "name: value\r\n".
+ * @param name of header
+ * @param value of header
+ */
+ void addHeader(String name, String value) {
+ if (name == null) {
+ String damage = "Null http header name";
+ HttpLog.e(damage);
+ throw new NullPointerException(damage);
+ }
+ if (value == null || value.length() == 0) {
+ String damage = "Null or empty value for header \"" + name + "\"";
+ HttpLog.e(damage);
+ throw new RuntimeException(damage);
+ }
+ mHttpRequest.addHeader(name, value);
+ }
+
+ /**
+ * Add all headers in given map to this request. This is a helper
+ * method: it calls addHeader for each pair in the map.
+ */
+ void addHeaders(Map<String, String> headers) {
+ if (headers == null) {
+ return;
+ }
+
+ Entry<String, String> entry;
+ Iterator<Entry<String, String>> i = headers.entrySet().iterator();
+ while (i.hasNext()) {
+ entry = i.next();
+ addHeader(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Send the request line and headers
+ */
+ void sendRequest(AndroidHttpClientConnection httpClientConnection)
+ throws HttpException, IOException {
+
+ if (mCancelled) return; // don't send cancelled requests
+
+ if (HttpLog.LOGV) {
+ HttpLog.v("Request.sendRequest() " + mHost.getSchemeName() + "://" + getHostPort());
+ // HttpLog.v(mHttpRequest.getRequestLine().toString());
+ if (false) {
+ Iterator i = mHttpRequest.headerIterator();
+ while (i.hasNext()) {
+ Header header = (Header)i.next();
+ HttpLog.v(header.getName() + ": " + header.getValue());
+ }
+ }
+ }
+
+ requestContentProcessor.process(mHttpRequest,
+ mConnection.getHttpContext());
+ httpClientConnection.sendRequestHeader(mHttpRequest);
+ if (mHttpRequest instanceof HttpEntityEnclosingRequest) {
+ httpClientConnection.sendRequestEntity(
+ (HttpEntityEnclosingRequest) mHttpRequest);
+ }
+
+ if (HttpLog.LOGV) {
+ HttpLog.v("Request.requestSent() " + mHost.getSchemeName() + "://" + getHostPort() + mPath);
+ }
+ }
+
+
+ /**
+ * Receive a single http response.
+ *
+ * @param httpClientConnection the request to receive the response for.
+ */
+ void readResponse(AndroidHttpClientConnection httpClientConnection)
+ throws IOException, ParseException {
+
+ if (mCancelled) return; // don't send cancelled requests
+
+ StatusLine statusLine = null;
+ boolean hasBody = false;
+ httpClientConnection.flush();
+ int statusCode = 0;
+
+ Headers header = new Headers();
+ do {
+ statusLine = httpClientConnection.parseResponseHeader(header);
+ statusCode = statusLine.getStatusCode();
+ } while (statusCode < HttpStatus.SC_OK);
+ if (HttpLog.LOGV) HttpLog.v(
+ "Request.readResponseStatus() " +
+ statusLine.toString().length() + " " + statusLine);
+
+ ProtocolVersion v = statusLine.getProtocolVersion();
+ mEventHandler.status(v.getMajor(), v.getMinor(),
+ statusCode, statusLine.getReasonPhrase());
+ mEventHandler.headers(header);
+ HttpEntity entity = null;
+ hasBody = canResponseHaveBody(mHttpRequest, statusCode);
+
+ if (hasBody)
+ entity = httpClientConnection.receiveResponseEntity(header);
+
+ // restrict the range request to the servers claiming that they are
+ // accepting ranges in bytes
+ boolean supportPartialContent = "bytes".equalsIgnoreCase(header
+ .getAcceptRanges());
+
+ if (entity != null) {
+ InputStream is = entity.getContent();
+
+ // process gzip content encoding
+ Header contentEncoding = entity.getContentEncoding();
+ InputStream nis = null;
+ byte[] buf = null;
+ int count = 0;
+ try {
+ if (contentEncoding != null &&
+ contentEncoding.getValue().equals("gzip")) {
+ nis = new GZIPInputStream(is);
+ } else {
+ nis = is;
+ }
+
+ /* accumulate enough data to make it worth pushing it
+ * up the stack */
+ buf = mConnection.getBuf();
+ int len = 0;
+ int lowWater = buf.length / 2;
+ while (len != -1) {
+ synchronized(this) {
+ while (mLoadingPaused) {
+ // Put this (network loading) thread to sleep if WebCore
+ // has asked us to. This can happen with plugins for
+ // example, if we are streaming data but the plugin has
+ // filled its internal buffers.
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ HttpLog.e("Interrupted exception whilst "
+ + "network thread paused at WebCore's request."
+ + " " + e.getMessage());
+ }
+ }
+ }
+
+ len = nis.read(buf, count, buf.length - count);
+
+ if (len != -1) {
+ count += len;
+ if (supportPartialContent) mReceivedBytes += len;
+ }
+ if (len == -1 || count >= lowWater) {
+ if (HttpLog.LOGV) HttpLog.v("Request.readResponse() " + count);
+ mEventHandler.data(buf, count);
+ count = 0;
+ }
+ }
+ } catch (EOFException e) {
+ /* InflaterInputStream throws an EOFException when the
+ server truncates gzipped content. Handle this case
+ as we do truncated non-gzipped content: no error */
+ if (count > 0) {
+ // if there is uncommited content, we should commit them
+ mEventHandler.data(buf, count);
+ }
+ if (HttpLog.LOGV) HttpLog.v( "readResponse() handling " + e);
+ } catch(IOException e) {
+ // don't throw if we have a non-OK status code
+ if (statusCode == HttpStatus.SC_OK
+ || statusCode == HttpStatus.SC_PARTIAL_CONTENT) {
+ if (supportPartialContent && count > 0) {
+ // if there is uncommited content, we should commit them
+ // as we will continue the request
+ mEventHandler.data(buf, count);
+ }
+ throw e;
+ }
+ } finally {
+ if (nis != null) {
+ nis.close();
+ }
+ }
+ }
+ mConnection.setCanPersist(entity, statusLine.getProtocolVersion(),
+ header.getConnectionType());
+ mEventHandler.endData();
+ complete();
+
+ if (HttpLog.LOGV) HttpLog.v("Request.readResponse(): done " +
+ mHost.getSchemeName() + "://" + getHostPort() + mPath);
+ }
+
+ /**
+ * Data will not be sent to or received from server after cancel()
+ * call. Does not close connection--use close() below for that.
+ *
+ * Called by RequestHandle from non-network thread
+ */
+ synchronized void cancel() {
+ if (HttpLog.LOGV) {
+ HttpLog.v("Request.cancel(): " + getUri());
+ }
+
+ // Ensure that the network thread is not blocked by a hanging request from WebCore to
+ // pause the load.
+ mLoadingPaused = false;
+ notify();
+
+ mCancelled = true;
+ if (mConnection != null) {
+ mConnection.cancel();
+ }
+ }
+
+ String getHostPort() {
+ String myScheme = mHost.getSchemeName();
+ int myPort = mHost.getPort();
+
+ // Only send port when we must... many servers can't deal with it
+ if (myPort != 80 && myScheme.equals("http") ||
+ myPort != 443 && myScheme.equals("https")) {
+ return mHost.toHostString();
+ } else {
+ return mHost.getHostName();
+ }
+ }
+
+ String getUri() {
+ if (mProxyHost == null ||
+ mHost.getSchemeName().equals("https")) {
+ return mPath;
+ }
+ return mHost.getSchemeName() + "://" + getHostPort() + mPath;
+ }
+
+ /**
+ * for debugging
+ */
+ public String toString() {
+ return mPath;
+ }
+
+
+ /**
+ * If this request has been sent once and failed, it must be reset
+ * before it can be sent again.
+ */
+ void reset() {
+ /* clear content-length header */
+ mHttpRequest.removeHeaders(CONTENT_LENGTH_HEADER);
+
+ if (mBodyProvider != null) {
+ try {
+ mBodyProvider.reset();
+ } catch (IOException ex) {
+ if (HttpLog.LOGV) HttpLog.v(
+ "failed to reset body provider " +
+ getUri());
+ }
+ setBodyProvider(mBodyProvider, mBodyLength);
+ }
+
+ if (mReceivedBytes > 0) {
+ // reset the fail count as we continue the request
+ mFailCount = 0;
+ // set the "Range" header to indicate that the retry will continue
+ // instead of restarting the request
+ HttpLog.v("*** Request.reset() to range:" + mReceivedBytes);
+ mHttpRequest.setHeader("Range", "bytes=" + mReceivedBytes + "-");
+ }
+ }
+
+ /**
+ * Pause thread request completes. Used for synchronous requests,
+ * and testing
+ */
+ void waitUntilComplete() {
+ synchronized (mClientResource) {
+ try {
+ if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete()");
+ mClientResource.wait();
+ if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete() done waiting");
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ void complete() {
+ synchronized (mClientResource) {
+ mClientResource.notifyAll();
+ }
+ }
+
+ /**
+ * Decide whether a response comes with an entity.
+ * The implementation in this class is based on RFC 2616.
+ * Unknown methods and response codes are supposed to
+ * indicate responses with an entity.
+ * <br/>
+ * Derived executors can override this method to handle
+ * methods and response codes not specified in RFC 2616.
+ *
+ * @param request the request, to obtain the executed method
+ * @param response the response, to obtain the status code
+ */
+
+ private static boolean canResponseHaveBody(final HttpRequest request,
+ final int status) {
+
+ if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
+ return false;
+ }
+ return status >= HttpStatus.SC_OK
+ && status != HttpStatus.SC_NO_CONTENT
+ && status != HttpStatus.SC_NOT_MODIFIED;
+ }
+
+ /**
+ * Supply an InputStream that provides the body of a request. It's
+ * not great that the caller must also provide the length of the data
+ * returned by that InputStream, but the client needs to know up
+ * front, and I'm not sure how to get this out of the InputStream
+ * itself without a costly readthrough. I'm not sure skip() would
+ * do what we want. If you know a better way, please let me know.
+ */
+ private void setBodyProvider(InputStream bodyProvider, int bodyLength) {
+ if (!bodyProvider.markSupported()) {
+ throw new IllegalArgumentException(
+ "bodyProvider must support mark()");
+ }
+ // Mark beginning of stream
+ bodyProvider.mark(Integer.MAX_VALUE);
+
+ ((BasicHttpEntityEnclosingRequest)mHttpRequest).setEntity(
+ new InputStreamEntity(bodyProvider, bodyLength));
+ }
+
+
+ /**
+ * Handles SSL error(s) on the way down from the user (the user
+ * has already provided their feedback).
+ */
+ public void handleSslErrorResponse(boolean proceed) {
+ HttpsConnection connection = (HttpsConnection)(mConnection);
+ if (connection != null) {
+ connection.restartConnection(proceed);
+ }
+ }
+
+ /**
+ * Helper: calls error() on eventhandler with appropriate message
+ * This should not be called before the mConnection is set.
+ */
+ void error(int errorId, int resourceId) {
+ mEventHandler.error(
+ errorId,
+ mConnection.mContext.getText(
+ resourceId).toString());
+ }
+
+}
diff --git a/placeholder/org/apache/http/PlaceHolder.java b/android/src/android/net/http/RequestFeeder.java
index 20ebfc0..34ca267 100644
--- a/placeholder/org/apache/http/PlaceHolder.java
+++ b/android/src/android/net/http/RequestFeeder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,29 @@
* limitations under the License.
*/
-package org.apache.http;
+/**
+ * Supplies Requests to a Connection
+ */
+
+package android.net.http;
+
+import org.apache.http.HttpHost;
+
+/**
+ * {@hide}
+ */
+interface RequestFeeder {
+
+ Request getRequest();
+ Request getRequest(HttpHost host);
+
+ /**
+ * @return true if a request for this host is available
+ */
+ boolean haveRequest(HttpHost host);
-public class PlaceHolder {
+ /**
+ * Put request back on head of queue
+ */
+ void requeueRequest(Request request);
}
diff --git a/android/src/android/net/http/RequestHandle.java b/android/src/android/net/http/RequestHandle.java
new file mode 100644
index 0000000..46c3869
--- /dev/null
+++ b/android/src/android/net/http/RequestHandle.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http;
+
+import android.net.ParseException;
+import android.net.WebAddress;
+import android.webkit.CookieManager;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.InputStream;
+import java.lang.Math;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * RequestHandle: handles a request session that may include multiple
+ * redirects, HTTP authentication requests, etc.
+ *
+ * {@hide}
+ */
+public class RequestHandle {
+
+ private String mUrl;
+ private WebAddress mUri;
+ private String mMethod;
+ private Map<String, String> mHeaders;
+ private RequestQueue mRequestQueue;
+ private Request mRequest;
+ private InputStream mBodyProvider;
+ private int mBodyLength;
+ private int mRedirectCount = 0;
+ // Used only with synchronous requests.
+ private Connection mConnection;
+
+ private final static String AUTHORIZATION_HEADER = "Authorization";
+ private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
+
+ public final static int MAX_REDIRECT_COUNT = 16;
+
+ /**
+ * Creates a new request session.
+ */
+ public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ InputStream bodyProvider, int bodyLength, Request request) {
+
+ if (headers == null) {
+ headers = new HashMap<String, String>();
+ }
+ mHeaders = headers;
+ mBodyProvider = bodyProvider;
+ mBodyLength = bodyLength;
+ mMethod = method == null? "GET" : method;
+
+ mUrl = url;
+ mUri = uri;
+
+ mRequestQueue = requestQueue;
+
+ mRequest = request;
+ }
+
+ /**
+ * Creates a new request session with a given Connection. This connection
+ * is used during a synchronous load to handle this request.
+ */
+ public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ InputStream bodyProvider, int bodyLength, Request request,
+ Connection conn) {
+ this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
+ request);
+ mConnection = conn;
+ }
+
+ /**
+ * Cancels this request
+ */
+ public void cancel() {
+ if (mRequest != null) {
+ mRequest.cancel();
+ }
+ }
+
+ /**
+ * Pauses the loading of this request. For example, called from the WebCore thread
+ * when the plugin can take no more data.
+ */
+ public void pauseRequest(boolean pause) {
+ if (mRequest != null) {
+ mRequest.setLoadingPaused(pause);
+ }
+ }
+
+ /**
+ * Handles SSL error(s) on the way down from the user (the user
+ * has already provided their feedback).
+ */
+ public void handleSslErrorResponse(boolean proceed) {
+ if (mRequest != null) {
+ mRequest.handleSslErrorResponse(proceed);
+ }
+ }
+
+ /**
+ * @return true if we've hit the max redirect count
+ */
+ public boolean isRedirectMax() {
+ return mRedirectCount >= MAX_REDIRECT_COUNT;
+ }
+
+ public int getRedirectCount() {
+ return mRedirectCount;
+ }
+
+ public void setRedirectCount(int count) {
+ mRedirectCount = count;
+ }
+
+ /**
+ * Create and queue a redirect request.
+ *
+ * @param redirectTo URL to redirect to
+ * @param statusCode HTTP status code returned from original request
+ * @param cacheHeaders Cache header for redirect URL
+ * @return true if setup succeeds, false otherwise (redirect loop
+ * count exceeded, body provider unable to rewind on 307 redirect)
+ */
+ public boolean setupRedirect(String redirectTo, int statusCode,
+ Map<String, String> cacheHeaders) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestHandle.setupRedirect(): redirectCount " +
+ mRedirectCount);
+ }
+
+ // be careful and remove authentication headers, if any
+ mHeaders.remove(AUTHORIZATION_HEADER);
+ mHeaders.remove(PROXY_AUTHORIZATION_HEADER);
+
+ if (++mRedirectCount == MAX_REDIRECT_COUNT) {
+ // Way too many redirects -- fail out
+ if (HttpLog.LOGV) HttpLog.v(
+ "RequestHandle.setupRedirect(): too many redirects " +
+ mRequest);
+ mRequest.error(EventHandler.ERROR_REDIRECT_LOOP,
+ com.android.internal.R.string.httpErrorRedirectLoop);
+ return false;
+ }
+
+ if (mUrl.startsWith("https:") && redirectTo.startsWith("http:")) {
+ // implement http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3
+ if (HttpLog.LOGV) {
+ HttpLog.v("blowing away the referer on an https -> http redirect");
+ }
+ mHeaders.remove("Referer");
+ }
+
+ mUrl = redirectTo;
+ try {
+ mUri = new WebAddress(mUrl);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+
+ // update the "Cookie" header based on the redirected url
+ mHeaders.remove("Cookie");
+ String cookie = CookieManager.getInstance().getCookie(mUri);
+ if (cookie != null && cookie.length() > 0) {
+ mHeaders.put("Cookie", cookie);
+ }
+
+ if ((statusCode == 302 || statusCode == 303) && mMethod.equals("POST")) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("replacing POST with GET on redirect to " + redirectTo);
+ }
+ mMethod = "GET";
+ }
+ /* Only repost content on a 307. If 307, reset the body
+ provider so we can replay the body */
+ if (statusCode == 307) {
+ try {
+ if (mBodyProvider != null) mBodyProvider.reset();
+ } catch (java.io.IOException ex) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("setupRedirect() failed to reset body provider");
+ }
+ return false;
+ }
+
+ } else {
+ mHeaders.remove("Content-Type");
+ mBodyProvider = null;
+ }
+
+ // Update the cache headers for this URL
+ mHeaders.putAll(cacheHeaders);
+
+ createAndQueueNewRequest();
+ return true;
+ }
+
+ /**
+ * Create and queue an HTTP authentication-response (basic) request.
+ */
+ public void setupBasicAuthResponse(boolean isProxy, String username, String password) {
+ String response = computeBasicAuthResponse(username, password);
+ if (HttpLog.LOGV) {
+ HttpLog.v("setupBasicAuthResponse(): response: " + response);
+ }
+ mHeaders.put(authorizationHeader(isProxy), "Basic " + response);
+ setupAuthResponse();
+ }
+
+ /**
+ * Create and queue an HTTP authentication-response (digest) request.
+ */
+ public void setupDigestAuthResponse(boolean isProxy,
+ String username,
+ String password,
+ String realm,
+ String nonce,
+ String QOP,
+ String algorithm,
+ String opaque) {
+
+ String response = computeDigestAuthResponse(
+ username, password, realm, nonce, QOP, algorithm, opaque);
+ if (HttpLog.LOGV) {
+ HttpLog.v("setupDigestAuthResponse(): response: " + response);
+ }
+ mHeaders.put(authorizationHeader(isProxy), "Digest " + response);
+ setupAuthResponse();
+ }
+
+ private void setupAuthResponse() {
+ try {
+ if (mBodyProvider != null) mBodyProvider.reset();
+ } catch (java.io.IOException ex) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("setupAuthResponse() failed to reset body provider");
+ }
+ }
+ createAndQueueNewRequest();
+ }
+
+ /**
+ * @return HTTP request method (GET, PUT, etc).
+ */
+ public String getMethod() {
+ return mMethod;
+ }
+
+ /**
+ * @return Basic-scheme authentication response: BASE64(username:password).
+ */
+ public static String computeBasicAuthResponse(String username, String password) {
+ if (username == null) {
+ throw new NullPointerException("username == null");
+ }
+
+ if (password == null) {
+ throw new NullPointerException("password == null");
+ }
+
+ // encode username:password to base64
+ return new String(Base64.encodeBase64((username + ':' + password).getBytes()));
+ }
+
+ public void waitUntilComplete() {
+ mRequest.waitUntilComplete();
+ }
+
+ public void processRequest() {
+ if (mConnection != null) {
+ mConnection.processRequests(mRequest);
+ }
+ }
+
+ /**
+ * @return Digest-scheme authentication response.
+ */
+ private String computeDigestAuthResponse(String username,
+ String password,
+ String realm,
+ String nonce,
+ String QOP,
+ String algorithm,
+ String opaque) {
+
+ if (username == null) {
+ throw new NullPointerException("username == null");
+ }
+
+ if (password == null) {
+ throw new NullPointerException("password == null");
+ }
+
+ if (realm == null) {
+ throw new NullPointerException("realm == null");
+ }
+
+ String A1 = username + ":" + realm + ":" + password;
+ String A2 = mMethod + ":" + mUrl;
+
+ // because we do not preemptively send authorization headers, nc is always 1
+ String nc = "00000001";
+ String cnonce = computeCnonce();
+ String digest = computeDigest(A1, A2, nonce, QOP, nc, cnonce);
+
+ String response = "";
+ response += "username=" + doubleQuote(username) + ", ";
+ response += "realm=" + doubleQuote(realm) + ", ";
+ response += "nonce=" + doubleQuote(nonce) + ", ";
+ response += "uri=" + doubleQuote(mUrl) + ", ";
+ response += "response=" + doubleQuote(digest) ;
+
+ if (opaque != null) {
+ response += ", opaque=" + doubleQuote(opaque);
+ }
+
+ if (algorithm != null) {
+ response += ", algorithm=" + algorithm;
+ }
+
+ if (QOP != null) {
+ response += ", qop=" + QOP + ", nc=" + nc + ", cnonce=" + doubleQuote(cnonce);
+ }
+
+ return response;
+ }
+
+ /**
+ * @return The right authorization header (dependeing on whether it is a proxy or not).
+ */
+ public static String authorizationHeader(boolean isProxy) {
+ if (!isProxy) {
+ return AUTHORIZATION_HEADER;
+ } else {
+ return PROXY_AUTHORIZATION_HEADER;
+ }
+ }
+
+ /**
+ * @return Double-quoted MD5 digest.
+ */
+ private String computeDigest(
+ String A1, String A2, String nonce, String QOP, String nc, String cnonce) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("computeDigest(): QOP: " + QOP);
+ }
+
+ if (QOP == null) {
+ return KD(H(A1), nonce + ":" + H(A2));
+ } else {
+ if (QOP.equalsIgnoreCase("auth")) {
+ return KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + QOP + ":" + H(A2));
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return MD5 hash of concat(secret, ":", data).
+ */
+ private String KD(String secret, String data) {
+ return H(secret + ":" + data);
+ }
+
+ /**
+ * @return MD5 hash of param.
+ */
+ private String H(String param) {
+ if (param != null) {
+ try {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+ byte[] d = md5.digest(param.getBytes());
+ if (d != null) {
+ return bufferToHex(d);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return HEX buffer representation.
+ */
+ private String bufferToHex(byte[] buffer) {
+ final char hexChars[] =
+ { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
+
+ if (buffer != null) {
+ int length = buffer.length;
+ if (length > 0) {
+ StringBuilder hex = new StringBuilder(2 * length);
+
+ for (int i = 0; i < length; ++i) {
+ byte l = (byte) (buffer[i] & 0x0F);
+ byte h = (byte)((buffer[i] & 0xF0) >> 4);
+
+ hex.append(hexChars[h]);
+ hex.append(hexChars[l]);
+ }
+
+ return hex.toString();
+ } else {
+ return "";
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Computes a random cnonce value based on the current time.
+ */
+ private String computeCnonce() {
+ Random rand = new Random();
+ int nextInt = rand.nextInt();
+ nextInt = (nextInt == Integer.MIN_VALUE) ?
+ Integer.MAX_VALUE : Math.abs(nextInt);
+ return Integer.toString(nextInt, 16);
+ }
+
+ /**
+ * "Double-quotes" the argument.
+ */
+ private String doubleQuote(String param) {
+ if (param != null) {
+ return "\"" + param + "\"";
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates and queues new request.
+ */
+ private void createAndQueueNewRequest() {
+ // mConnection is non-null if and only if the requests are synchronous.
+ if (mConnection != null) {
+ RequestHandle newHandle = mRequestQueue.queueSynchronousRequest(
+ mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
+ mBodyProvider, mBodyLength);
+ mRequest = newHandle.mRequest;
+ mConnection = newHandle.mConnection;
+ newHandle.processRequest();
+ return;
+ }
+ mRequest = mRequestQueue.queueRequest(
+ mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
+ mBodyProvider,
+ mBodyLength).mRequest;
+ }
+}
diff --git a/android/src/android/net/http/RequestQueue.java b/android/src/android/net/http/RequestQueue.java
new file mode 100644
index 0000000..7d2da1b
--- /dev/null
+++ b/android/src/android/net/http/RequestQueue.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * High level HTTP Interface
+ * Queues requests as necessary
+ */
+
+package android.net.http;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Proxy;
+import android.net.WebAddress;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.http.HttpHost;
+
+/**
+ * {@hide}
+ */
+public class RequestQueue implements RequestFeeder {
+
+
+ /**
+ * Requests, indexed by HttpHost (scheme, host, port)
+ */
+ private final LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
+ private final Context mContext;
+ private final ActivePool mActivePool;
+ private final ConnectivityManager mConnectivityManager;
+
+ private HttpHost mProxyHost = null;
+ private BroadcastReceiver mProxyChangeReceiver;
+
+ /* default simultaneous connection count */
+ private static final int CONNECTION_COUNT = 4;
+
+ /**
+ * This class maintains active connection threads
+ */
+ class ActivePool implements ConnectionManager {
+ /** Threads used to process requests */
+ ConnectionThread[] mThreads;
+
+ IdleCache mIdleCache;
+
+ private int mTotalRequest;
+ private int mTotalConnection;
+ private int mConnectionCount;
+
+ ActivePool(int connectionCount) {
+ mIdleCache = new IdleCache();
+ mConnectionCount = connectionCount;
+ mThreads = new ConnectionThread[mConnectionCount];
+
+ for (int i = 0; i < mConnectionCount; i++) {
+ mThreads[i] = new ConnectionThread(
+ mContext, i, this, RequestQueue.this);
+ }
+ }
+
+ void startup() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ mThreads[i].start();
+ }
+ }
+
+ void shutdown() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ mThreads[i].requestStop();
+ }
+ }
+
+ void startConnectionThread() {
+ synchronized (RequestQueue.this) {
+ RequestQueue.this.notify();
+ }
+ }
+
+ public void startTiming() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ ConnectionThread rt = mThreads[i];
+ rt.mCurrentThreadTime = -1;
+ rt.mTotalThreadTime = 0;
+ }
+ mTotalRequest = 0;
+ mTotalConnection = 0;
+ }
+
+ public void stopTiming() {
+ int totalTime = 0;
+ for (int i = 0; i < mConnectionCount; i++) {
+ ConnectionThread rt = mThreads[i];
+ if (rt.mCurrentThreadTime != -1) {
+ totalTime += rt.mTotalThreadTime;
+ }
+ rt.mCurrentThreadTime = 0;
+ }
+ Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
+ + mTotalRequest + " requests and " + mTotalConnection
+ + " new connections");
+ }
+
+ void logState() {
+ StringBuilder dump = new StringBuilder();
+ for (int i = 0; i < mConnectionCount; i++) {
+ dump.append(mThreads[i] + "\n");
+ }
+ HttpLog.v(dump.toString());
+ }
+
+
+ public HttpHost getProxyHost() {
+ return mProxyHost;
+ }
+
+ /**
+ * Turns off persistence on all live connections
+ */
+ void disablePersistence() {
+ for (int i = 0; i < mConnectionCount; i++) {
+ Connection connection = mThreads[i].mConnection;
+ if (connection != null) connection.setCanPersist(false);
+ }
+ mIdleCache.clear();
+ }
+
+ /* Linear lookup -- okay for small thread counts. Might use
+ private HashMap<HttpHost, LinkedList<ConnectionThread>> mActiveMap;
+ if this turns out to be a hotspot */
+ ConnectionThread getThread(HttpHost host) {
+ synchronized(RequestQueue.this) {
+ for (int i = 0; i < mThreads.length; i++) {
+ ConnectionThread ct = mThreads[i];
+ Connection connection = ct.mConnection;
+ if (connection != null && connection.mHost.equals(host)) {
+ return ct;
+ }
+ }
+ }
+ return null;
+ }
+
+ public Connection getConnection(Context context, HttpHost host) {
+ host = RequestQueue.this.determineHost(host);
+ Connection con = mIdleCache.getConnection(host);
+ if (con == null) {
+ mTotalConnection++;
+ con = Connection.getConnection(mContext, host, mProxyHost,
+ RequestQueue.this);
+ }
+ return con;
+ }
+ public boolean recycleConnection(Connection connection) {
+ return mIdleCache.cacheConnection(connection.getHost(), connection);
+ }
+
+ }
+
+ /**
+ * A RequestQueue class instance maintains a set of queued
+ * requests. It orders them, makes the requests against HTTP
+ * servers, and makes callbacks to supplied eventHandlers as data
+ * is read. It supports request prioritization, connection reuse
+ * and pipelining.
+ *
+ * @param context application context
+ */
+ public RequestQueue(Context context) {
+ this(context, CONNECTION_COUNT);
+ }
+
+ /**
+ * A RequestQueue class instance maintains a set of queued
+ * requests. It orders them, makes the requests against HTTP
+ * servers, and makes callbacks to supplied eventHandlers as data
+ * is read. It supports request prioritization, connection reuse
+ * and pipelining.
+ *
+ * @param context application context
+ * @param connectionCount The number of simultaneous connections
+ */
+ public RequestQueue(Context context, int connectionCount) {
+ mContext = context;
+
+ mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
+
+ mActivePool = new ActivePool(connectionCount);
+ mActivePool.startup();
+
+ mConnectivityManager = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ /**
+ * Enables data state and proxy tracking
+ */
+ public synchronized void enablePlatformNotifications() {
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
+
+ if (mProxyChangeReceiver == null) {
+ mProxyChangeReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context ctx, Intent intent) {
+ setProxyConfig();
+ }
+ };
+ mContext.registerReceiver(mProxyChangeReceiver,
+ new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
+ }
+ // we need to resample the current proxy setup
+ setProxyConfig();
+ }
+
+ /**
+ * If platform notifications have been enabled, call this method
+ * to disable before destroying RequestQueue
+ */
+ public synchronized void disablePlatformNotifications() {
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
+
+ if (mProxyChangeReceiver != null) {
+ mContext.unregisterReceiver(mProxyChangeReceiver);
+ mProxyChangeReceiver = null;
+ }
+ }
+
+ /**
+ * Because our IntentReceiver can run within a different thread,
+ * synchronize setting the proxy
+ */
+ private synchronized void setProxyConfig() {
+ NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+ if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
+ mProxyHost = null;
+ } else {
+ String host = Proxy.getHost(mContext);
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
+ if (host == null) {
+ mProxyHost = null;
+ } else {
+ mActivePool.disablePersistence();
+ mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
+ }
+ }
+ }
+
+ /**
+ * used by webkit
+ * @return proxy host if set, null otherwise
+ */
+ public HttpHost getProxyHost() {
+ return mProxyHost;
+ }
+
+ /**
+ * Queues an HTTP request
+ * @param url The url to load.
+ * @param method "GET" or "POST."
+ * @param headers A hashmap of http headers.
+ * @param eventHandler The event handler for handling returned
+ * data. Callbacks will be made on the supplied instance.
+ * @param bodyProvider InputStream providing HTTP body, null if none
+ * @param bodyLength length of body, must be 0 if bodyProvider is null
+ */
+ public RequestHandle queueRequest(
+ String url, String method,
+ Map<String, String> headers, EventHandler eventHandler,
+ InputStream bodyProvider, int bodyLength) {
+ WebAddress uri = new WebAddress(url);
+ return queueRequest(url, uri, method, headers, eventHandler,
+ bodyProvider, bodyLength);
+ }
+
+ /**
+ * Queues an HTTP request
+ * @param url The url to load.
+ * @param uri The uri of the url to load.
+ * @param method "GET" or "POST."
+ * @param headers A hashmap of http headers.
+ * @param eventHandler The event handler for handling returned
+ * data. Callbacks will be made on the supplied instance.
+ * @param bodyProvider InputStream providing HTTP body, null if none
+ * @param bodyLength length of body, must be 0 if bodyProvider is null
+ */
+ public RequestHandle queueRequest(
+ String url, WebAddress uri, String method, Map<String, String> headers,
+ EventHandler eventHandler,
+ InputStream bodyProvider, int bodyLength) {
+
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
+
+ // Ensure there is an eventHandler set
+ if (eventHandler == null) {
+ eventHandler = new LoggingEventHandler();
+ }
+
+ /* Create and queue request */
+ Request req;
+ HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+
+ // set up request
+ req = new Request(method, httpHost, mProxyHost, uri.getPath(), bodyProvider,
+ bodyLength, eventHandler, headers);
+
+ queueRequest(req, false);
+
+ mActivePool.mTotalRequest++;
+
+ // dump();
+ mActivePool.startConnectionThread();
+
+ return new RequestHandle(
+ this, url, uri, method, headers, bodyProvider, bodyLength,
+ req);
+ }
+
+ private static class SyncFeeder implements RequestFeeder {
+ // This is used in the case where the request fails and needs to be
+ // requeued into the RequestFeeder.
+ private Request mRequest;
+ SyncFeeder() {
+ }
+ public Request getRequest() {
+ Request r = mRequest;
+ mRequest = null;
+ return r;
+ }
+ public Request getRequest(HttpHost host) {
+ return getRequest();
+ }
+ public boolean haveRequest(HttpHost host) {
+ return mRequest != null;
+ }
+ public void requeueRequest(Request r) {
+ mRequest = r;
+ }
+ }
+
+ public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ EventHandler eventHandler, InputStream bodyProvider,
+ int bodyLength) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+ }
+
+ HttpHost host = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+
+ Request req = new Request(method, host, mProxyHost, uri.getPath(),
+ bodyProvider, bodyLength, eventHandler, headers);
+
+ // Open a new connection that uses our special RequestFeeder
+ // implementation.
+ host = determineHost(host);
+ Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+ new SyncFeeder());
+
+ // TODO: I would like to process the request here but LoadListener
+ // needs a RequestHandle to process some messages.
+ return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+ bodyLength, req, conn);
+
+ }
+
+ // Chooses between the proxy and the request's host.
+ private HttpHost determineHost(HttpHost host) {
+ // There used to be a comment in ConnectionThread about t-mob's proxy
+ // being really bad about https. But, HttpsConnection actually looks
+ // for a proxy and connects through it anyway. I think that this check
+ // is still valid because if a site is https, we will use
+ // HttpsConnection rather than HttpConnection if the proxy address is
+ // not secure.
+ return (mProxyHost == null || "https".equals(host.getSchemeName()))
+ ? host : mProxyHost;
+ }
+
+ /**
+ * @return true iff there are any non-active requests pending
+ */
+ synchronized boolean requestsPending() {
+ return !mPending.isEmpty();
+ }
+
+
+ /**
+ * debug tool: prints request queue to log
+ */
+ synchronized void dump() {
+ HttpLog.v("dump()");
+ StringBuilder dump = new StringBuilder();
+ int count = 0;
+ Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
+
+ // mActivePool.log(dump);
+
+ if (!mPending.isEmpty()) {
+ iter = mPending.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
+ String hostName = entry.getKey().getHostName();
+ StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
+
+ LinkedList<Request> reqList = entry.getValue();
+ ListIterator reqIter = reqList.listIterator(0);
+ while (iter.hasNext()) {
+ Request request = (Request)iter.next();
+ line.append(request + " ");
+ }
+ dump.append(line);
+ dump.append("\n");
+ }
+ }
+ HttpLog.v(dump.toString());
+ }
+
+ /*
+ * RequestFeeder implementation
+ */
+ public synchronized Request getRequest() {
+ Request ret = null;
+
+ if (!mPending.isEmpty()) {
+ ret = removeFirst(mPending);
+ }
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
+ return ret;
+ }
+
+ /**
+ * @return a request for given host if possible
+ */
+ public synchronized Request getRequest(HttpHost host) {
+ Request ret = null;
+
+ if (mPending.containsKey(host)) {
+ LinkedList<Request> reqList = mPending.get(host);
+ ret = reqList.removeFirst();
+ if (reqList.isEmpty()) {
+ mPending.remove(host);
+ }
+ }
+ if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
+ return ret;
+ }
+
+ /**
+ * @return true if a request for this host is available
+ */
+ public synchronized boolean haveRequest(HttpHost host) {
+ return mPending.containsKey(host);
+ }
+
+ /**
+ * Put request back on head of queue
+ */
+ public void requeueRequest(Request request) {
+ queueRequest(request, true);
+ }
+
+ /**
+ * This must be called to cleanly shutdown RequestQueue
+ */
+ public void shutdown() {
+ mActivePool.shutdown();
+ }
+
+ protected synchronized void queueRequest(Request request, boolean head) {
+ HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
+ LinkedList<Request> reqList;
+ if (mPending.containsKey(host)) {
+ reqList = mPending.get(host);
+ } else {
+ reqList = new LinkedList<Request>();
+ mPending.put(host, reqList);
+ }
+ if (head) {
+ reqList.addFirst(request);
+ } else {
+ reqList.add(request);
+ }
+ }
+
+
+ public void startTiming() {
+ mActivePool.startTiming();
+ }
+
+ public void stopTiming() {
+ mActivePool.stopTiming();
+ }
+
+ /* helper */
+ private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
+ Request ret = null;
+ Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
+ if (iter.hasNext()) {
+ Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
+ LinkedList<Request> reqList = entry.getValue();
+ ret = reqList.removeFirst();
+ if (reqList.isEmpty()) {
+ requestQueue.remove(entry.getKey());
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * This interface is exposed to each connection
+ */
+ interface ConnectionManager {
+ HttpHost getProxyHost();
+ Connection getConnection(Context context, HttpHost host);
+ boolean recycleConnection(Connection connection);
+ }
+}
diff --git a/android/src/com/android/internal/http/multipart/ByteArrayPartSource.java b/android/src/com/android/internal/http/multipart/ByteArrayPartSource.java
new file mode 100644
index 0000000..faaac7f
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/ByteArrayPartSource.java
@@ -0,0 +1,86 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/ByteArrayPartSource.java,v 1.7 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * A PartSource that reads from a byte array. This class should be used when
+ * the data to post is already loaded into memory.
+ *
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ *
+ * @since 2.0
+ */
+public class ByteArrayPartSource implements PartSource {
+
+ /** Name of the source file. */
+ private String fileName;
+
+ /** Byte array of the source file. */
+ private byte[] bytes;
+
+ /**
+ * Constructor for ByteArrayPartSource.
+ *
+ * @param fileName the name of the file these bytes represent
+ * @param bytes the content of this part
+ */
+ public ByteArrayPartSource(String fileName, byte[] bytes) {
+
+ this.fileName = fileName;
+ this.bytes = bytes;
+
+ }
+
+ /**
+ * @see PartSource#getLength()
+ */
+ public long getLength() {
+ return bytes.length;
+ }
+
+ /**
+ * @see PartSource#getFileName()
+ */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /**
+ * @see PartSource#createInputStream()
+ */
+ public InputStream createInputStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+}
diff --git a/android/src/com/android/internal/http/multipart/FilePart.java b/android/src/com/android/internal/http/multipart/FilePart.java
new file mode 100644
index 0000000..bfcda00
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/FilePart.java
@@ -0,0 +1,254 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v 1.19 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class implements a part of a Multipart post object that
+ * consists of a file.
+ *
+ * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
+ * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
+ * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ *
+ * @since 2.0
+ *
+ */
+public class FilePart extends PartBase {
+
+ /** Default content encoding of file attachments. */
+ public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
+
+ /** Default charset of file attachments. */
+ public static final String DEFAULT_CHARSET = "ISO-8859-1";
+
+ /** Default transfer encoding of file attachments. */
+ public static final String DEFAULT_TRANSFER_ENCODING = "binary";
+
+ /** Log object for this class. */
+ private static final Log LOG = LogFactory.getLog(FilePart.class);
+
+ /** Attachment's file name */
+ protected static final String FILE_NAME = "; filename=";
+
+ /** Attachment's file name as a byte array */
+ private static final byte[] FILE_NAME_BYTES =
+ EncodingUtils.getAsciiBytes(FILE_NAME);
+
+ /** Source of the file part. */
+ private PartSource source;
+
+ /**
+ * FilePart Constructor.
+ *
+ * @param name the name for this part
+ * @param partSource the source for this part
+ * @param contentType the content type for this part, if <code>null</code> the
+ * {@link #DEFAULT_CONTENT_TYPE default} is used
+ * @param charset the charset encoding for this part, if <code>null</code> the
+ * {@link #DEFAULT_CHARSET default} is used
+ */
+ public FilePart(String name, PartSource partSource, String contentType, String charset) {
+
+ super(
+ name,
+ contentType == null ? DEFAULT_CONTENT_TYPE : contentType,
+ charset == null ? "ISO-8859-1" : charset,
+ DEFAULT_TRANSFER_ENCODING
+ );
+
+ if (partSource == null) {
+ throw new IllegalArgumentException("Source may not be null");
+ }
+ this.source = partSource;
+ }
+
+ /**
+ * FilePart Constructor.
+ *
+ * @param name the name for this part
+ * @param partSource the source for this part
+ */
+ public FilePart(String name, PartSource partSource) {
+ this(name, partSource, null, null);
+ }
+
+ /**
+ * FilePart Constructor.
+ *
+ * @param name the name of the file part
+ * @param file the file to post
+ *
+ * @throws FileNotFoundException if the <i>file</i> is not a normal
+ * file or if it is not readable.
+ */
+ public FilePart(String name, File file)
+ throws FileNotFoundException {
+ this(name, new FilePartSource(file), null, null);
+ }
+
+ /**
+ * FilePart Constructor.
+ *
+ * @param name the name of the file part
+ * @param file the file to post
+ * @param contentType the content type for this part, if <code>null</code> the
+ * {@link #DEFAULT_CONTENT_TYPE default} is used
+ * @param charset the charset encoding for this part, if <code>null</code> the
+ * {@link #DEFAULT_CHARSET default} is used
+ *
+ * @throws FileNotFoundException if the <i>file</i> is not a normal
+ * file or if it is not readable.
+ */
+ public FilePart(String name, File file, String contentType, String charset)
+ throws FileNotFoundException {
+ this(name, new FilePartSource(file), contentType, charset);
+ }
+
+ /**
+ * FilePart Constructor.
+ *
+ * @param name the name of the file part
+ * @param fileName the file name
+ * @param file the file to post
+ *
+ * @throws FileNotFoundException if the <i>file</i> is not a normal
+ * file or if it is not readable.
+ */
+ public FilePart(String name, String fileName, File file)
+ throws FileNotFoundException {
+ this(name, new FilePartSource(fileName, file), null, null);
+ }
+
+ /**
+ * FilePart Constructor.
+ *
+ * @param name the name of the file part
+ * @param fileName the file name
+ * @param file the file to post
+ * @param contentType the content type for this part, if <code>null</code> the
+ * {@link #DEFAULT_CONTENT_TYPE default} is used
+ * @param charset the charset encoding for this part, if <code>null</code> the
+ * {@link #DEFAULT_CHARSET default} is used
+ *
+ * @throws FileNotFoundException if the <i>file</i> is not a normal
+ * file or if it is not readable.
+ */
+ public FilePart(String name, String fileName, File file, String contentType, String charset)
+ throws FileNotFoundException {
+ this(name, new FilePartSource(fileName, file), contentType, charset);
+ }
+
+ /**
+ * Write the disposition header to the output stream
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs
+ * @see Part#sendDispositionHeader(OutputStream)
+ */
+ @Override
+ protected void sendDispositionHeader(OutputStream out)
+ throws IOException {
+ LOG.trace("enter sendDispositionHeader(OutputStream out)");
+ super.sendDispositionHeader(out);
+ String filename = this.source.getFileName();
+ if (filename != null) {
+ out.write(FILE_NAME_BYTES);
+ out.write(QUOTE_BYTES);
+ out.write(EncodingUtils.getAsciiBytes(filename));
+ out.write(QUOTE_BYTES);
+ }
+ }
+
+ /**
+ * Write the data in "source" to the specified stream.
+ * @param out The output stream.
+ * @throws IOException if an IO problem occurs.
+ * @see Part#sendData(OutputStream)
+ */
+ @Override
+ protected void sendData(OutputStream out) throws IOException {
+ LOG.trace("enter sendData(OutputStream out)");
+ if (lengthOfData() == 0) {
+
+ // this file contains no data, so there is nothing to send.
+ // we don't want to create a zero length buffer as this will
+ // cause an infinite loop when reading.
+ LOG.debug("No data to send.");
+ return;
+ }
+
+ byte[] tmp = new byte[4096];
+ InputStream instream = source.createInputStream();
+ try {
+ int len;
+ while ((len = instream.read(tmp)) >= 0) {
+ out.write(tmp, 0, len);
+ }
+ } finally {
+ // we're done with the stream, close it
+ instream.close();
+ }
+ }
+
+ /**
+ * Returns the source of the file part.
+ *
+ * @return The source.
+ */
+ protected PartSource getSource() {
+ LOG.trace("enter getSource()");
+ return this.source;
+ }
+
+ /**
+ * Return the length of the data.
+ * @return The length.
+ * @see Part#lengthOfData()
+ */
+ @Override
+ protected long lengthOfData() {
+ LOG.trace("enter lengthOfData()");
+ return source.getLength();
+ }
+
+}
diff --git a/android/src/com/android/internal/http/multipart/FilePartSource.java b/android/src/com/android/internal/http/multipart/FilePartSource.java
new file mode 100644
index 0000000..eb5cc0f
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/FilePartSource.java
@@ -0,0 +1,131 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePartSource.java,v 1.10 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A PartSource that reads from a File.
+ *
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ *
+ * @since 2.0
+ */
+public class FilePartSource implements PartSource {
+
+ /** File part file. */
+ private File file = null;
+
+ /** File part file name. */
+ private String fileName = null;
+
+ /**
+ * Constructor for FilePartSource.
+ *
+ * @param file the FilePart source File.
+ *
+ * @throws FileNotFoundException if the file does not exist or
+ * cannot be read
+ */
+ public FilePartSource(File file) throws FileNotFoundException {
+ this.file = file;
+ if (file != null) {
+ if (!file.isFile()) {
+ throw new FileNotFoundException("File is not a normal file.");
+ }
+ if (!file.canRead()) {
+ throw new FileNotFoundException("File is not readable.");
+ }
+ this.fileName = file.getName();
+ }
+ }
+
+ /**
+ * Constructor for FilePartSource.
+ *
+ * @param fileName the file name of the FilePart
+ * @param file the source File for the FilePart
+ *
+ * @throws FileNotFoundException if the file does not exist or
+ * cannot be read
+ */
+ public FilePartSource(String fileName, File file)
+ throws FileNotFoundException {
+ this(file);
+ if (fileName != null) {
+ this.fileName = fileName;
+ }
+ }
+
+ /**
+ * Return the length of the file
+ * @return the length of the file.
+ * @see PartSource#getLength()
+ */
+ public long getLength() {
+ if (this.file != null) {
+ return this.file.length();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Return the current filename
+ * @return the filename.
+ * @see PartSource#getFileName()
+ */
+ public String getFileName() {
+ return (fileName == null) ? "noname" : fileName;
+ }
+
+ /**
+ * Return a new {@link FileInputStream} for the current filename.
+ * @return the new input stream.
+ * @throws IOException If an IO problem occurs.
+ * @see PartSource#createInputStream()
+ */
+ public InputStream createInputStream() throws IOException {
+ if (this.file != null) {
+ return new FileInputStream(this.file);
+ } else {
+ return new ByteArrayInputStream(new byte[] {});
+ }
+ }
+
+}
diff --git a/android/src/com/android/internal/http/multipart/MultipartEntity.java b/android/src/com/android/internal/http/multipart/MultipartEntity.java
new file mode 100644
index 0000000..2c5e7f6
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/MultipartEntity.java
@@ -0,0 +1,230 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java,v 1.1 2004/10/06 03:39:59 mbecke Exp $
+ * $Revision: 502647 $
+ * $Date: 2007-02-02 17:22:54 +0100 (Fri, 02 Feb 2007) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Random;
+
+import org.apache.http.Header;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implements a request entity suitable for an HTTP multipart POST method.
+ * <p>
+ * The HTTP multipart POST method is defined in section 3.3 of
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC1867</a>:
+ * <blockquote>
+ * The media-type multipart/form-data follows the rules of all multipart
+ * MIME data streams as outlined in RFC 1521. The multipart/form-data contains
+ * a series of parts. Each part is expected to contain a content-disposition
+ * header where the value is "form-data" and a name attribute specifies
+ * the field name within the form, e.g., 'content-disposition: form-data;
+ * name="xxxxx"', where xxxxx is the field name corresponding to that field.
+ * Field names originally in non-ASCII character sets may be encoded using
+ * the method outlined in RFC 1522.
+ * </blockquote>
+ * </p>
+ * <p>This entity is designed to be used in conjunction with the
+ * {@link org.apache.http.HttpRequest} to provide
+ * multipart posts. Example usage:</p>
+ * <pre>
+ * File f = new File("/path/fileToUpload.txt");
+ * HttpRequest request = new HttpRequest("http://host/some_path");
+ * Part[] parts = {
+ * new StringPart("param_name", "value"),
+ * new FilePart(f.getName(), f)
+ * };
+ * filePost.setEntity(
+ * new MultipartRequestEntity(parts, filePost.getParams())
+ * );
+ * HttpClient client = new HttpClient();
+ * int status = client.executeMethod(filePost);
+ * </pre>
+ *
+ * @since 3.0
+ */
+public class MultipartEntity extends AbstractHttpEntity {
+
+ private static final Log log = LogFactory.getLog(MultipartEntity.class);
+
+ /** The Content-Type for multipart/form-data. */
+ private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data";
+
+ /**
+ * Sets the value to use as the multipart boundary.
+ * <p>
+ * This parameter expects a value if type {@link String}.
+ * </p>
+ */
+ public static final String MULTIPART_BOUNDARY = "http.method.multipart.boundary";
+
+ /**
+ * The pool of ASCII chars to be used for generating a multipart boundary.
+ */
+ private static byte[] MULTIPART_CHARS = EncodingUtils.getAsciiBytes(
+ "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+ /**
+ * Generates a random multipart boundary string.
+ */
+ private static byte[] generateMultipartBoundary() {
+ Random rand = new Random();
+ byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
+ }
+ return bytes;
+ }
+
+ /** The MIME parts as set by the constructor */
+ protected Part[] parts;
+
+ private byte[] multipartBoundary;
+
+ private HttpParams params;
+
+ private boolean contentConsumed = false;
+
+ /**
+ * Creates a new multipart entity containing the given parts.
+ * @param parts The parts to include.
+ * @param params The params of the HttpMethod using this entity.
+ */
+ public MultipartEntity(Part[] parts, HttpParams params) {
+ if (parts == null) {
+ throw new IllegalArgumentException("parts cannot be null");
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("params cannot be null");
+ }
+ this.parts = parts;
+ this.params = params;
+ }
+
+ public MultipartEntity(Part[] parts) {
+ setContentType(MULTIPART_FORM_CONTENT_TYPE);
+ if (parts == null) {
+ throw new IllegalArgumentException("parts cannot be null");
+ }
+ this.parts = parts;
+ this.params = null;
+ }
+
+ /**
+ * Returns the MIME boundary string that is used to demarcate boundaries of
+ * this part. The first call to this method will implicitly create a new
+ * boundary string. To create a boundary string first the
+ * HttpMethodParams.MULTIPART_BOUNDARY parameter is considered. Otherwise
+ * a random one is generated.
+ *
+ * @return The boundary string of this entity in ASCII encoding.
+ */
+ protected byte[] getMultipartBoundary() {
+ if (multipartBoundary == null) {
+ String temp = null;
+ if (params != null) {
+ temp = (String) params.getParameter(MULTIPART_BOUNDARY);
+ }
+ if (temp != null) {
+ multipartBoundary = EncodingUtils.getAsciiBytes(temp);
+ } else {
+ multipartBoundary = generateMultipartBoundary();
+ }
+ }
+ return multipartBoundary;
+ }
+
+ /**
+ * Returns <code>true</code> if all parts are repeatable, <code>false</code> otherwise.
+ */
+ public boolean isRepeatable() {
+ for (int i = 0; i < parts.length; i++) {
+ if (!parts[i].isRepeatable()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ */
+ public void writeTo(OutputStream out) throws IOException {
+ Part.sendParts(out, parts, getMultipartBoundary());
+ }
+ /* (non-Javadoc)
+ * @see org.apache.commons.http.AbstractHttpEntity.#getContentType()
+ */
+ @Override
+ public Header getContentType() {
+ StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
+ buffer.append("; boundary=");
+ buffer.append(EncodingUtils.getAsciiString(getMultipartBoundary()));
+ return new BasicHeader(HTTP.CONTENT_TYPE, buffer.toString());
+
+ }
+
+ /* (non-Javadoc)
+ */
+ public long getContentLength() {
+ try {
+ return Part.getLengthOfParts(parts, getMultipartBoundary());
+ } catch (Exception e) {
+ log.error("An exception occurred while getting the length of the parts", e);
+ return 0;
+ }
+ }
+
+ public InputStream getContent() throws IOException, IllegalStateException {
+ if(!isRepeatable() && this.contentConsumed ) {
+ throw new IllegalStateException("Content has been consumed");
+ }
+ this.contentConsumed = true;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Part.sendParts(baos, this.parts, this.multipartBoundary);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ return bais;
+ }
+
+ public boolean isStreaming() {
+ return false;
+ }
+}
diff --git a/android/src/com/android/internal/http/multipart/Part.java b/android/src/com/android/internal/http/multipart/Part.java
new file mode 100644
index 0000000..cb1b546
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/Part.java
@@ -0,0 +1,439 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v 1.16 2005/01/14 21:16:40 olegk Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Abstract class for one Part of a multipart post object.
+ *
+ * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
+ * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
+ * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ *
+ * @since 2.0
+ */
+public abstract class Part {
+
+ /** Log object for this class. */
+ private static final Log LOG = LogFactory.getLog(Part.class);
+
+ /**
+ * The boundary
+ * @deprecated use {@link org.apache.http.client.methods.multipart#MULTIPART_BOUNDARY}
+ */
+ protected static final String BOUNDARY = "----------------314159265358979323846";
+
+ /**
+ * The boundary as a byte array.
+ * @deprecated
+ */
+ protected static final byte[] BOUNDARY_BYTES = EncodingUtils.getAsciiBytes(BOUNDARY);
+
+ /**
+ * The default boundary to be used if {@link #setPartBoundary(byte[])} has not
+ * been called.
+ */
+ private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
+
+ /** Carriage return/linefeed */
+ protected static final String CRLF = "\r\n";
+
+ /** Carriage return/linefeed as a byte array */
+ protected static final byte[] CRLF_BYTES = EncodingUtils.getAsciiBytes(CRLF);
+
+ /** Content dispostion characters */
+ protected static final String QUOTE = "\"";
+
+ /** Content dispostion as a byte array */
+ protected static final byte[] QUOTE_BYTES =
+ EncodingUtils.getAsciiBytes(QUOTE);
+
+ /** Extra characters */
+ protected static final String EXTRA = "--";
+
+ /** Extra characters as a byte array */
+ protected static final byte[] EXTRA_BYTES =
+ EncodingUtils.getAsciiBytes(EXTRA);
+
+ /** Content dispostion characters */
+ protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
+
+ /** Content dispostion as a byte array */
+ protected static final byte[] CONTENT_DISPOSITION_BYTES =
+ EncodingUtils.getAsciiBytes(CONTENT_DISPOSITION);
+
+ /** Content type header */
+ protected static final String CONTENT_TYPE = "Content-Type: ";
+
+ /** Content type header as a byte array */
+ protected static final byte[] CONTENT_TYPE_BYTES =
+ EncodingUtils.getAsciiBytes(CONTENT_TYPE);
+
+ /** Content charset */
+ protected static final String CHARSET = "; charset=";
+
+ /** Content charset as a byte array */
+ protected static final byte[] CHARSET_BYTES =
+ EncodingUtils.getAsciiBytes(CHARSET);
+
+ /** Content type header */
+ protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
+
+ /** Content type header as a byte array */
+ protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES =
+ EncodingUtils.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
+
+ /**
+ * Return the boundary string.
+ * @return the boundary string
+ * @deprecated uses a constant string. Rather use {@link #getPartBoundary}
+ */
+ public static String getBoundary() {
+ return BOUNDARY;
+ }
+
+ /**
+ * The ASCII bytes to use as the multipart boundary.
+ */
+ private byte[] boundaryBytes;
+
+ /**
+ * Return the name of this part.
+ * @return The name.
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the content type of this part.
+ * @return the content type, or <code>null</code> to exclude the content type header
+ */
+ public abstract String getContentType();
+
+ /**
+ * Return the character encoding of this part.
+ * @return the character encoding, or <code>null</code> to exclude the character
+ * encoding header
+ */
+ public abstract String getCharSet();
+
+ /**
+ * Return the transfer encoding of this part.
+ * @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
+ */
+ public abstract String getTransferEncoding();
+
+ /**
+ * Gets the part boundary to be used.
+ * @return the part boundary as an array of bytes.
+ *
+ * @since 3.0
+ */
+ protected byte[] getPartBoundary() {
+ if (boundaryBytes == null) {
+ // custom boundary bytes have not been set, use the default.
+ return DEFAULT_BOUNDARY_BYTES;
+ } else {
+ return boundaryBytes;
+ }
+ }
+
+ /**
+ * Sets the part boundary. Only meant to be used by
+ * {@link Part#sendParts(OutputStream, Part[], byte[])}
+ * and {@link Part#getLengthOfParts(Part[], byte[])}
+ * @param boundaryBytes An array of ASCII bytes.
+ * @since 3.0
+ */
+ void setPartBoundary(byte[] boundaryBytes) {
+ this.boundaryBytes = boundaryBytes;
+ }
+
+ /**
+ * Tests if this part can be sent more than once.
+ * @return <code>true</code> if {@link #sendData(OutputStream)} can be successfully called
+ * more than once.
+ * @since 3.0
+ */
+ public boolean isRepeatable() {
+ return true;
+ }
+
+ /**
+ * Write the start to the specified output stream
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected void sendStart(OutputStream out) throws IOException {
+ LOG.trace("enter sendStart(OutputStream out)");
+ out.write(EXTRA_BYTES);
+ out.write(getPartBoundary());
+ out.write(CRLF_BYTES);
+ }
+
+ /**
+ * Write the content disposition header to the specified output stream
+ *
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected void sendDispositionHeader(OutputStream out) throws IOException {
+ LOG.trace("enter sendDispositionHeader(OutputStream out)");
+ out.write(CONTENT_DISPOSITION_BYTES);
+ out.write(QUOTE_BYTES);
+ out.write(EncodingUtils.getAsciiBytes(getName()));
+ out.write(QUOTE_BYTES);
+ }
+
+ /**
+ * Write the content type header to the specified output stream
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected void sendContentTypeHeader(OutputStream out) throws IOException {
+ LOG.trace("enter sendContentTypeHeader(OutputStream out)");
+ String contentType = getContentType();
+ if (contentType != null) {
+ out.write(CRLF_BYTES);
+ out.write(CONTENT_TYPE_BYTES);
+ out.write(EncodingUtils.getAsciiBytes(contentType));
+ String charSet = getCharSet();
+ if (charSet != null) {
+ out.write(CHARSET_BYTES);
+ out.write(EncodingUtils.getAsciiBytes(charSet));
+ }
+ }
+ }
+
+ /**
+ * Write the content transfer encoding header to the specified
+ * output stream
+ *
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
+ LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
+ String transferEncoding = getTransferEncoding();
+ if (transferEncoding != null) {
+ out.write(CRLF_BYTES);
+ out.write(CONTENT_TRANSFER_ENCODING_BYTES);
+ out.write(EncodingUtils.getAsciiBytes(transferEncoding));
+ }
+ }
+
+ /**
+ * Write the end of the header to the output stream
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected void sendEndOfHeader(OutputStream out) throws IOException {
+ LOG.trace("enter sendEndOfHeader(OutputStream out)");
+ out.write(CRLF_BYTES);
+ out.write(CRLF_BYTES);
+ }
+
+ /**
+ * Write the data to the specified output stream
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected abstract void sendData(OutputStream out) throws IOException;
+
+ /**
+ * Return the length of the main content
+ *
+ * @return long The length.
+ * @throws IOException If an IO problem occurs
+ */
+ protected abstract long lengthOfData() throws IOException;
+
+ /**
+ * Write the end data to the output stream.
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ protected void sendEnd(OutputStream out) throws IOException {
+ LOG.trace("enter sendEnd(OutputStream out)");
+ out.write(CRLF_BYTES);
+ }
+
+ /**
+ * Write all the data to the output stream.
+ * If you override this method make sure to override
+ * #length() as well
+ *
+ * @param out The output stream
+ * @throws IOException If an IO problem occurs.
+ */
+ public void send(OutputStream out) throws IOException {
+ LOG.trace("enter send(OutputStream out)");
+ sendStart(out);
+ sendDispositionHeader(out);
+ sendContentTypeHeader(out);
+ sendTransferEncodingHeader(out);
+ sendEndOfHeader(out);
+ sendData(out);
+ sendEnd(out);
+ }
+
+
+ /**
+ * Return the full length of all the data.
+ * If you override this method make sure to override
+ * #send(OutputStream) as well
+ *
+ * @return long The length.
+ * @throws IOException If an IO problem occurs
+ */
+ public long length() throws IOException {
+ LOG.trace("enter length()");
+ if (lengthOfData() < 0) {
+ return -1;
+ }
+ ByteArrayOutputStream overhead = new ByteArrayOutputStream();
+ sendStart(overhead);
+ sendDispositionHeader(overhead);
+ sendContentTypeHeader(overhead);
+ sendTransferEncodingHeader(overhead);
+ sendEndOfHeader(overhead);
+ sendEnd(overhead);
+ return overhead.size() + lengthOfData();
+ }
+
+ /**
+ * Return a string representation of this object.
+ * @return A string representation of this object.
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.getName();
+ }
+
+ /**
+ * Write all parts and the last boundary to the specified output stream.
+ *
+ * @param out The stream to write to.
+ * @param parts The parts to write.
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ */
+ public static void sendParts(OutputStream out, final Part[] parts)
+ throws IOException {
+ sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
+ }
+
+ /**
+ * Write all parts and the last boundary to the specified output stream.
+ *
+ * @param out The stream to write to.
+ * @param parts The parts to write.
+ * @param partBoundary The ASCII bytes to use as the part boundary.
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ *
+ * @since 3.0
+ */
+ public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
+ throws IOException {
+
+ if (parts == null) {
+ throw new IllegalArgumentException("Parts may not be null");
+ }
+ if (partBoundary == null || partBoundary.length == 0) {
+ throw new IllegalArgumentException("partBoundary may not be empty");
+ }
+ for (int i = 0; i < parts.length; i++) {
+ // set the part boundary before the part is sent
+ parts[i].setPartBoundary(partBoundary);
+ parts[i].send(out);
+ }
+ out.write(EXTRA_BYTES);
+ out.write(partBoundary);
+ out.write(EXTRA_BYTES);
+ out.write(CRLF_BYTES);
+ }
+
+ /**
+ * Return the total sum of all parts and that of the last boundary
+ *
+ * @param parts The parts.
+ * @return The total length
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ */
+ public static long getLengthOfParts(Part[] parts)
+ throws IOException {
+ return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
+ }
+
+ /**
+ * Gets the length of the multipart message including the given parts.
+ *
+ * @param parts The parts.
+ * @param partBoundary The ASCII bytes to use as the part boundary.
+ * @return The total length
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ *
+ * @since 3.0
+ */
+ public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
+ LOG.trace("getLengthOfParts(Parts[])");
+ if (parts == null) {
+ throw new IllegalArgumentException("Parts may not be null");
+ }
+ long total = 0;
+ for (int i = 0; i < parts.length; i++) {
+ // set the part boundary before we calculate the part's length
+ parts[i].setPartBoundary(partBoundary);
+ long l = parts[i].length();
+ if (l < 0) {
+ return -1;
+ }
+ total += l;
+ }
+ total += EXTRA_BYTES.length;
+ total += partBoundary.length;
+ total += EXTRA_BYTES.length;
+ total += CRLF_BYTES.length;
+ return total;
+ }
+}
diff --git a/android/src/com/android/internal/http/multipart/PartBase.java b/android/src/com/android/internal/http/multipart/PartBase.java
new file mode 100644
index 0000000..876d15d
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/PartBase.java
@@ -0,0 +1,150 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartBase.java,v 1.5 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+
+/**
+ * Provides setters and getters for the basic Part properties.
+ *
+ * @author Michael Becke
+ */
+public abstract class PartBase extends Part {
+
+ /** Name of the file part. */
+ private String name;
+
+ /** Content type of the file part. */
+ private String contentType;
+
+ /** Content encoding of the file part. */
+ private String charSet;
+
+ /** The transfer encoding. */
+ private String transferEncoding;
+
+ /**
+ * Constructor.
+ *
+ * @param name The name of the part
+ * @param contentType The content type, or <code>null</code>
+ * @param charSet The character encoding, or <code>null</code>
+ * @param transferEncoding The transfer encoding, or <code>null</code>
+ */
+ public PartBase(String name, String contentType, String charSet, String transferEncoding) {
+
+ if (name == null) {
+ throw new IllegalArgumentException("Name must not be null");
+ }
+ this.name = name;
+ this.contentType = contentType;
+ this.charSet = charSet;
+ this.transferEncoding = transferEncoding;
+ }
+
+ /**
+ * Returns the name.
+ * @return The name.
+ * @see Part#getName()
+ */
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns the content type of this part.
+ * @return String The name.
+ */
+ @Override
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ /**
+ * Return the character encoding of this part.
+ * @return String The name.
+ */
+ @Override
+ public String getCharSet() {
+ return this.charSet;
+ }
+
+ /**
+ * Returns the transfer encoding of this part.
+ * @return String The name.
+ */
+ @Override
+ public String getTransferEncoding() {
+ return transferEncoding;
+ }
+
+ /**
+ * Sets the character encoding.
+ *
+ * @param charSet the character encoding, or <code>null</code> to exclude the character
+ * encoding header
+ */
+ public void setCharSet(String charSet) {
+ this.charSet = charSet;
+ }
+
+ /**
+ * Sets the content type.
+ *
+ * @param contentType the content type, or <code>null</code> to exclude the content type header
+ */
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ /**
+ * Sets the part name.
+ *
+ * @param name
+ */
+ public void setName(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Name must not be null");
+ }
+ this.name = name;
+ }
+
+ /**
+ * Sets the transfer encoding.
+ *
+ * @param transferEncoding the transfer encoding, or <code>null</code> to exclude the
+ * transfer encoding header
+ */
+ public void setTransferEncoding(String transferEncoding) {
+ this.transferEncoding = transferEncoding;
+ }
+
+}
diff --git a/android/src/com/android/internal/http/multipart/PartSource.java b/android/src/com/android/internal/http/multipart/PartSource.java
new file mode 100644
index 0000000..3740696
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/PartSource.java
@@ -0,0 +1,72 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartSource.java,v 1.6 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An interface for providing access to data when posting MultiPart messages.
+ *
+ * @see FilePart
+ *
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ *
+ * @since 2.0
+ */
+public interface PartSource {
+
+ /**
+ * Gets the number of bytes contained in this source.
+ *
+ * @return a value >= 0
+ */
+ long getLength();
+
+ /**
+ * Gets the name of the file this source represents.
+ *
+ * @return the fileName used for posting a MultiPart file part
+ */
+ String getFileName();
+
+ /**
+ * Gets a new InputStream for reading this source. This method can be
+ * called more than once and should therefore return a new stream every
+ * time.
+ *
+ * @return a new InputStream
+ *
+ * @throws IOException if an error occurs when creating the InputStream
+ */
+ InputStream createInputStream() throws IOException;
+
+}
diff --git a/android/src/com/android/internal/http/multipart/StringPart.java b/android/src/com/android/internal/http/multipart/StringPart.java
new file mode 100644
index 0000000..c98257e
--- /dev/null
+++ b/android/src/com/android/internal/http/multipart/StringPart.java
@@ -0,0 +1,150 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/StringPart.java,v 1.11 2004/04/18 23:51:37 jsdever Exp $
+ * $Revision: 480424 $
+ * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
+ *
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.android.internal.http.multipart;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+import org.apache.http.util.EncodingUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Simple string parameter for a multipart post
+ *
+ * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
+ * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ *
+ * @since 2.0
+ */
+public class StringPart extends PartBase {
+
+ /** Log object for this class. */
+ private static final Log LOG = LogFactory.getLog(StringPart.class);
+
+ /** Default content encoding of string parameters. */
+ public static final String DEFAULT_CONTENT_TYPE = "text/plain";
+
+ /** Default charset of string parameters*/
+ public static final String DEFAULT_CHARSET = "US-ASCII";
+
+ /** Default transfer encoding of string parameters*/
+ public static final String DEFAULT_TRANSFER_ENCODING = "8bit";
+
+ /** Contents of this StringPart. */
+ private byte[] content;
+
+ /** The String value of this part. */
+ private String value;
+
+ /**
+ * Constructor.
+ *
+ * @param name The name of the part
+ * @param value the string to post
+ * @param charset the charset to be used to encode the string, if <code>null</code>
+ * the {@link #DEFAULT_CHARSET default} is used
+ */
+ public StringPart(String name, String value, String charset) {
+
+ super(
+ name,
+ DEFAULT_CONTENT_TYPE,
+ charset == null ? DEFAULT_CHARSET : charset,
+ DEFAULT_TRANSFER_ENCODING
+ );
+ if (value == null) {
+ throw new IllegalArgumentException("Value may not be null");
+ }
+ if (value.indexOf(0) != -1) {
+ // See RFC 2048, 2.8. "8bit Data"
+ throw new IllegalArgumentException("NULs may not be present in string parts");
+ }
+ this.value = value;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param name The name of the part
+ * @param value the string to post
+ */
+ public StringPart(String name, String value) {
+ this(name, value, null);
+ }
+
+ /**
+ * Gets the content in bytes. Bytes are lazily created to allow the charset to be changed
+ * after the part is created.
+ *
+ * @return the content in bytes
+ */
+ private byte[] getContent() {
+ if (content == null) {
+ content = EncodingUtils.getBytes(value, getCharSet());
+ }
+ return content;
+ }
+
+ /**
+ * Writes the data to the given OutputStream.
+ * @param out the OutputStream to write to
+ * @throws IOException if there is a write error
+ */
+ @Override
+ protected void sendData(OutputStream out) throws IOException {
+ LOG.trace("enter sendData(OutputStream)");
+ out.write(getContent());
+ }
+
+ /**
+ * Return the length of the data.
+ * @return The length of the data.
+ * @see Part#lengthOfData()
+ */
+ @Override
+ protected long lengthOfData() {
+ LOG.trace("enter lengthOfData()");
+ return getContent().length;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.commons.httpclient.methods.multipart.BasePart#setCharSet(java.lang.String)
+ */
+ @Override
+ public void setCharSet(String charSet) {
+ super.setCharSet(charSet);
+ this.content = null;
+ }
+
+}
diff --git a/src/org/apache/http/conn/ConnectTimeoutException.java b/src/org/apache/http/conn/ConnectTimeoutException.java
deleted file mode 100644
index 6cc6922..0000000
--- a/src/org/apache/http/conn/ConnectTimeoutException.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ConnectTimeoutException.java $
- * $Revision: 617645 $
- * $Date: 2008-02-01 13:05:31 -0800 (Fri, 01 Feb 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn;
-
-import java.io.InterruptedIOException;
-
-/**
- * A timeout while connecting to an HTTP server or waiting for an
- * available connection from an HttpConnectionManager.
- *
- * @author <a href="mailto:laura@lwerner.org">Laura Werner</a>
- *
- * @since 4.0
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public class ConnectTimeoutException extends InterruptedIOException {
-
- private static final long serialVersionUID = -4816682903149535989L;
-
- /**
- * Creates a ConnectTimeoutException with a <tt>null</tt> detail message.
- */
- public ConnectTimeoutException() {
- super();
- }
-
- /**
- * Creates a ConnectTimeoutException with the specified detail message.
- *
- * @param message The exception detail message
- */
- public ConnectTimeoutException(final String message) {
- super(message);
- }
-
-}
diff --git a/src/org/apache/http/conn/scheme/HostNameResolver.java b/src/org/apache/http/conn/scheme/HostNameResolver.java
deleted file mode 100644
index d488a4b..0000000
--- a/src/org/apache/http/conn/scheme/HostNameResolver.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * $HeadURL:$
- * $Revision:$
- * $Date:$
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.scheme;
-
-import java.io.IOException;
-import java.net.InetAddress;
-/**
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
-*/
-
-@Deprecated
-public interface HostNameResolver {
-
- InetAddress resolve (String hostname) throws IOException;
-
-}
diff --git a/src/org/apache/http/conn/scheme/LayeredSocketFactory.java b/src/org/apache/http/conn/scheme/LayeredSocketFactory.java
deleted file mode 100644
index b9f5348..0000000
--- a/src/org/apache/http/conn/scheme/LayeredSocketFactory.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java $
- * $Revision: 645850 $
- * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.scheme;
-
-import java.io.IOException;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
-/**
- * A {@link SocketFactory SocketFactory} for layered sockets (SSL/TLS).
- * See there for things to consider when implementing a socket factory.
- *
- * @author Michael Becke
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- * @since 4.0
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public interface LayeredSocketFactory extends SocketFactory {
-
- /**
- * Returns a socket connected to the given host that is layered over an
- * existing socket. Used primarily for creating secure sockets through
- * proxies.
- *
- * @param socket the existing socket
- * @param host the host name/IP
- * @param port the port on the host
- * @param autoClose a flag for closing the underling socket when the created
- * socket is closed
- *
- * @return Socket a new socket
- *
- * @throws IOException if an I/O error occurs while creating the socket
- * @throws UnknownHostException if the IP address of the host cannot be
- * determined
- */
- Socket createSocket(
- Socket socket,
- String host,
- int port,
- boolean autoClose
- ) throws IOException, UnknownHostException;
-
-}
diff --git a/src/org/apache/http/conn/scheme/SocketFactory.java b/src/org/apache/http/conn/scheme/SocketFactory.java
deleted file mode 100644
index c6bc03c..0000000
--- a/src/org/apache/http/conn/scheme/SocketFactory.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/SocketFactory.java $
- * $Revision: 645850 $
- * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.scheme;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
-import org.apache.http.conn.ConnectTimeoutException;
-import org.apache.http.params.HttpParams;
-
-/**
- * A factory for creating and connecting sockets.
- * The factory encapsulates the logic for establishing a socket connection.
- * <br/>
- * Both {@link java.lang.Object#equals(java.lang.Object) Object.equals()}
- * and {@link java.lang.Object#hashCode() Object.hashCode()}
- * must be overridden for the correct operation of some connection managers.
- *
- * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
- * @author Michael Becke
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public interface SocketFactory {
-
- /**
- * Creates a new, unconnected socket.
- * The socket should subsequently be passed to
- * {@link #connectSocket connectSocket}.
- *
- * @return a new socket
- *
- * @throws IOException if an I/O error occurs while creating the socket
- */
- Socket createSocket()
- throws IOException
- ;
-
-
- /**
- * Connects a socket to the given host.
- *
- * @param sock the socket to connect, as obtained from
- * {@link #createSocket createSocket}.
- * <code>null</code> indicates that a new socket
- * should be created and connected.
- * @param host the host to connect to
- * @param port the port to connect to on the host
- * @param localAddress the local address to bind the socket to, or
- * <code>null</code> for any
- * @param localPort the port on the local machine,
- * 0 or a negative number for any
- * @param params additional {@link HttpParams parameters} for connecting
- *
- * @return the connected socket. The returned object may be different
- * from the <code>sock</code> argument if this factory supports
- * a layered protocol.
- *
- * @throws IOException if an I/O error occurs
- * @throws UnknownHostException if the IP address of the target host
- * can not be determined
- * @throws ConnectTimeoutException if the socket cannot be connected
- * within the time limit defined in the <code>params</code>
- */
- Socket connectSocket(
- Socket sock,
- String host,
- int port,
- InetAddress localAddress,
- int localPort,
- HttpParams params
- ) throws IOException, UnknownHostException, ConnectTimeoutException;
-
-
- /**
- * Checks whether a socket provides a secure connection.
- * The socket must be {@link #connectSocket connected}
- * by this factory.
- * The factory will <i>not</i> perform I/O operations
- * in this method.
- * <br/>
- * As a rule of thumb, plain sockets are not secure and
- * TLS/SSL sockets are secure. However, there may be
- * application specific deviations. For example, a plain
- * socket to a host in the same intranet ("trusted zone")
- * could be considered secure. On the other hand, a
- * TLS/SSL socket could be considered insecure based on
- * the cypher suite chosen for the connection.
- *
- * @param sock the connected socket to check
- *
- * @return <code>true</code> if the connection of the socket
- * should be considered secure, or
- * <code>false</code> if it should not
- *
- * @throws IllegalArgumentException
- * if the argument is invalid, for example because it is
- * not a connected socket or was created by a different
- * socket factory.
- * Note that socket factories are <i>not</i> required to
- * check these conditions, they may simply return a default
- * value when called with an invalid socket argument.
- */
- boolean isSecure(Socket sock)
- throws IllegalArgumentException
- ;
-
-}
diff --git a/src/org/apache/http/conn/ssl/AbstractVerifier.java b/src/org/apache/http/conn/ssl/AbstractVerifier.java
deleted file mode 100644
index a56a6d4..0000000
--- a/src/org/apache/http/conn/ssl/AbstractVerifier.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java $
- * $Revision: 653041 $
- * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.ssl;
-
-import org.apache.http.conn.util.InetAddressUtils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.logging.Logger;
-import java.util.logging.Level;
-
-import javax.net.ssl.DistinguishedNameParser;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-/**
- * Abstract base class for all standard {@link X509HostnameVerifier}
- * implementations.
- *
- * @author Julius Davies
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public abstract class AbstractVerifier implements X509HostnameVerifier {
-
- /**
- * This contains a list of 2nd-level domains that aren't allowed to
- * have wildcards when combined with country-codes.
- * For example: [*.co.uk].
- * <p/>
- * The [*.co.uk] problem is an interesting one. Should we just hope
- * that CA's would never foolishly allow such a certificate to happen?
- * Looks like we're the only implementation guarding against this.
- * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
- */
- private final static String[] BAD_COUNTRY_2LDS =
- { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
- "lg", "ne", "net", "or", "org" };
-
- static {
- // Just in case developer forgot to manually sort the array. :-)
- Arrays.sort(BAD_COUNTRY_2LDS);
- }
-
- public AbstractVerifier() {
- super();
- }
-
- public final void verify(String host, SSLSocket ssl)
- throws IOException {
- if(host == null) {
- throw new NullPointerException("host to verify is null");
- }
-
- SSLSession session = ssl.getSession();
- Certificate[] certs = session.getPeerCertificates();
- X509Certificate x509 = (X509Certificate) certs[0];
- verify(host, x509);
- }
-
- public final boolean verify(String host, SSLSession session) {
- try {
- Certificate[] certs = session.getPeerCertificates();
- X509Certificate x509 = (X509Certificate) certs[0];
- verify(host, x509);
- return true;
- }
- catch(SSLException e) {
- return false;
- }
- }
-
- public final void verify(String host, X509Certificate cert)
- throws SSLException {
- String[] cns = getCNs(cert);
- String[] subjectAlts = getDNSSubjectAlts(cert);
- verify(host, cns, subjectAlts);
- }
-
- public final void verify(final String host, final String[] cns,
- final String[] subjectAlts,
- final boolean strictWithSubDomains)
- throws SSLException {
-
- // Build the list of names we're going to check. Our DEFAULT and
- // STRICT implementations of the HostnameVerifier only use the
- // first CN provided. All other CNs are ignored.
- // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
- LinkedList<String> names = new LinkedList<String>();
- if(cns != null && cns.length > 0 && cns[0] != null) {
- names.add(cns[0]);
- }
- if(subjectAlts != null) {
- for (String subjectAlt : subjectAlts) {
- if (subjectAlt != null) {
- names.add(subjectAlt);
- }
- }
- }
-
- if(names.isEmpty()) {
- String msg = "Certificate for <" + host + "> doesn't contain CN or DNS subjectAlt";
- throw new SSLException(msg);
- }
-
- // StringBuffer for building the error message.
- StringBuffer buf = new StringBuffer();
-
- // We're can be case-insensitive when comparing the host we used to
- // establish the socket to the hostname in the certificate.
- String hostName = host.trim().toLowerCase(Locale.ENGLISH);
- boolean match = false;
- for(Iterator<String> it = names.iterator(); it.hasNext();) {
- // Don't trim the CN, though!
- String cn = it.next();
- cn = cn.toLowerCase(Locale.ENGLISH);
- // Store CN in StringBuffer in case we need to report an error.
- buf.append(" <");
- buf.append(cn);
- buf.append('>');
- if(it.hasNext()) {
- buf.append(" OR");
- }
-
- // The CN better have at least two dots if it wants wildcard
- // action. It also can't be [*.co.uk] or [*.co.jp] or
- // [*.org.uk], etc...
- boolean doWildcard = cn.startsWith("*.") &&
- cn.indexOf('.', 2) != -1 &&
- acceptableCountryWildcard(cn) &&
- !InetAddressUtils.isIPv4Address(host);
-
- if(doWildcard) {
- match = hostName.endsWith(cn.substring(1));
- if(match && strictWithSubDomains) {
- // If we're in strict mode, then [*.foo.com] is not
- // allowed to match [a.b.foo.com]
- match = countDots(hostName) == countDots(cn);
- }
- } else {
- match = hostName.equals(cn);
- }
- if(match) {
- break;
- }
- }
- if(!match) {
- throw new SSLException("hostname in certificate didn't match: <" + host + "> !=" + buf);
- }
- }
-
- public static boolean acceptableCountryWildcard(String cn) {
- int cnLen = cn.length();
- if(cnLen >= 7 && cnLen <= 9) {
- // Look for the '.' in the 3rd-last position:
- if(cn.charAt(cnLen - 3) == '.') {
- // Trim off the [*.] and the [.XX].
- String s = cn.substring(2, cnLen - 3);
- // And test against the sorted array of bad 2lds:
- int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
- return x < 0;
- }
- }
- return true;
- }
-
- public static String[] getCNs(X509Certificate cert) {
- DistinguishedNameParser dnParser =
- new DistinguishedNameParser(cert.getSubjectX500Principal());
- List<String> cnList = dnParser.getAllMostSpecificFirst("cn");
-
- if(!cnList.isEmpty()) {
- String[] cns = new String[cnList.size()];
- cnList.toArray(cns);
- return cns;
- } else {
- return null;
- }
- }
-
-
- /**
- * Extracts the array of SubjectAlt DNS names from an X509Certificate.
- * Returns null if there aren't any.
- * <p/>
- * Note: Java doesn't appear able to extract international characters
- * from the SubjectAlts. It can only extract international characters
- * from the CN field.
- * <p/>
- * (Or maybe the version of OpenSSL I'm using to test isn't storing the
- * international characters correctly in the SubjectAlts?).
- *
- * @param cert X509Certificate
- * @return Array of SubjectALT DNS names stored in the certificate.
- */
- public static String[] getDNSSubjectAlts(X509Certificate cert) {
- LinkedList<String> subjectAltList = new LinkedList<String>();
- Collection<List<?>> c = null;
- try {
- c = cert.getSubjectAlternativeNames();
- }
- catch(CertificateParsingException cpe) {
- Logger.getLogger(AbstractVerifier.class.getName())
- .log(Level.FINE, "Error parsing certificate.", cpe);
- }
- if(c != null) {
- for (List<?> aC : c) {
- List<?> list = aC;
- int type = ((Integer) list.get(0)).intValue();
- // If type is 2, then we've got a dNSName
- if (type == 2) {
- String s = (String) list.get(1);
- subjectAltList.add(s);
- }
- }
- }
- if(!subjectAltList.isEmpty()) {
- String[] subjectAlts = new String[subjectAltList.size()];
- subjectAltList.toArray(subjectAlts);
- return subjectAlts;
- } else {
- return null;
- }
- }
-
- /**
- * Counts the number of dots "." in a string.
- * @param s string to count dots from
- * @return number of dots
- */
- public static int countDots(final String s) {
- int count = 0;
- for(int i = 0; i < s.length(); i++) {
- if(s.charAt(i) == '.') {
- count++;
- }
- }
- return count;
- }
-
-}
diff --git a/src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java b/src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java
deleted file mode 100644
index c2bf4c4..0000000
--- a/src/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java $
- * $Revision: 617642 $
- * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.ssl;
-
-/**
- * The ALLOW_ALL HostnameVerifier essentially turns hostname verification
- * off. This implementation is a no-op, and never throws the SSLException.
- *
- * @author Julius Davies
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public class AllowAllHostnameVerifier extends AbstractVerifier {
-
- public final void verify(
- final String host,
- final String[] cns,
- final String[] subjectAlts) {
- // Allow everything - so never blowup.
- }
-
- @Override
- public final String toString() {
- return "ALLOW_ALL";
- }
-
-}
diff --git a/src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java b/src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java
deleted file mode 100644
index 48a7bf9..0000000
--- a/src/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java $
- * $Revision: 617642 $
- * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.ssl;
-
-import javax.net.ssl.SSLException;
-
-/**
- * The HostnameVerifier that works the same way as Curl and Firefox.
- * <p/>
- * The hostname must match either the first CN, or any of the subject-alts.
- * A wildcard can occur in the CN, and in any of the subject-alts.
- * <p/>
- * The only difference between BROWSER_COMPATIBLE and STRICT is that a wildcard
- * (such as "*.foo.com") with BROWSER_COMPATIBLE matches all subdomains,
- * including "a.b.foo.com".
- *
- * @author Julius Davies
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public class BrowserCompatHostnameVerifier extends AbstractVerifier {
-
- public final void verify(
- final String host,
- final String[] cns,
- final String[] subjectAlts) throws SSLException {
- verify(host, cns, subjectAlts, false);
- }
-
- @Override
- public final String toString() {
- return "BROWSER_COMPATIBLE";
- }
-
-}
diff --git a/src/org/apache/http/conn/ssl/SSLSocketFactory.java b/src/org/apache/http/conn/ssl/SSLSocketFactory.java
deleted file mode 100644
index 1e46fee..0000000
--- a/src/org/apache/http/conn/ssl/SSLSocketFactory.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java $
- * $Revision: 659194 $
- * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.ssl;
-
-import org.apache.http.conn.scheme.HostNameResolver;
-import org.apache.http.conn.scheme.LayeredSocketFactory;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-
-/**
- * Layered socket factory for TLS/SSL connections, based on JSSE.
- *.
- * <p>
- * SSLSocketFactory can be used to validate the identity of the HTTPS
- * server against a list of trusted certificates and to authenticate to
- * the HTTPS server using a private key.
- * </p>
- *
- * <p>
- * SSLSocketFactory will enable server authentication when supplied with
- * a {@link KeyStore truststore} file containg one or several trusted
- * certificates. The client secure socket will reject the connection during
- * the SSL session handshake if the target HTTPS server attempts to
- * authenticate itself with a non-trusted certificate.
- * </p>
- *
- * <p>
- * Use JDK keytool utility to import a trusted certificate and generate a truststore file:
- * <pre>
- * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
- * </pre>
- * </p>
- *
- * <p>
- * SSLSocketFactory will enable client authentication when supplied with
- * a {@link KeyStore keystore} file containg a private key/public certificate
- * pair. The client secure socket will use the private key to authenticate
- * itself to the target HTTPS server during the SSL session handshake if
- * requested to do so by the server.
- * The target HTTPS server will in its turn verify the certificate presented
- * by the client in order to establish client's authenticity
- * </p>
- *
- * <p>
- * Use the following sequence of actions to generate a keystore file
- * </p>
- * <ul>
- * <li>
- * <p>
- * Use JDK keytool utility to generate a new key
- * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
- * For simplicity use the same password for the key as that of the keystore
- * </p>
- * </li>
- * <li>
- * <p>
- * Issue a certificate signing request (CSR)
- * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
- * </p>
- * </li>
- * <li>
- * <p>
- * Send the certificate request to the trusted Certificate Authority for signature.
- * One may choose to act as her own CA and sign the certificate request using a PKI
- * tool, such as OpenSSL.
- * </p>
- * </li>
- * <li>
- * <p>
- * Import the trusted CA root certificate
- * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
- * </p>
- * </li>
- * <li>
- * <p>
- * Import the PKCS#7 file containg the complete certificate chain
- * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
- * </p>
- * </li>
- * <li>
- * <p>
- * Verify the content the resultant keystore file
- * <pre>keytool -list -v -keystore my.keystore</pre>
- * </p>
- * </li>
- * </ul>
- * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
- * @author Julius Davies
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-
-@Deprecated
-public class SSLSocketFactory implements LayeredSocketFactory {
-
- public static final String TLS = "TLS";
- public static final String SSL = "SSL";
- public static final String SSLV2 = "SSLv2";
-
- public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
- = new AllowAllHostnameVerifier();
-
- public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
- = new BrowserCompatHostnameVerifier();
-
- public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
- = new StrictHostnameVerifier();
-
- /*
- * Put defaults into holder class to avoid class preloading creating an
- * instance of the classes referenced.
- */
- private static class NoPreloadHolder {
- /**
- * The factory using the default JVM settings for secure connections.
- */
- private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory();
- }
-
- /**
- * Gets an singleton instance of the SSLProtocolSocketFactory.
- * @return a SSLProtocolSocketFactory
- */
- public static SSLSocketFactory getSocketFactory() {
- return NoPreloadHolder.DEFAULT_FACTORY;
- }
-
- private final SSLContext sslcontext;
- private final javax.net.ssl.SSLSocketFactory socketfactory;
- private final HostNameResolver nameResolver;
- private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
-
- public SSLSocketFactory(
- String algorithm,
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore,
- final SecureRandom random,
- final HostNameResolver nameResolver)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
- {
- super();
- if (algorithm == null) {
- algorithm = TLS;
- }
- KeyManager[] keymanagers = null;
- if (keystore != null) {
- keymanagers = createKeyManagers(keystore, keystorePassword);
- }
- TrustManager[] trustmanagers = null;
- if (truststore != null) {
- trustmanagers = createTrustManagers(truststore);
- }
- this.sslcontext = SSLContext.getInstance(algorithm);
- this.sslcontext.init(keymanagers, trustmanagers, random);
- this.socketfactory = this.sslcontext.getSocketFactory();
- this.nameResolver = nameResolver;
- }
-
- public SSLSocketFactory(
- final KeyStore keystore,
- final String keystorePassword,
- final KeyStore truststore)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
- {
- this(TLS, keystore, keystorePassword, truststore, null, null);
- }
-
- public SSLSocketFactory(final KeyStore keystore, final String keystorePassword)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
- {
- this(TLS, keystore, keystorePassword, null, null, null);
- }
-
- public SSLSocketFactory(final KeyStore truststore)
- throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
- {
- this(TLS, null, null, truststore, null, null);
- }
-
- /**
- * Constructs an HttpClient SSLSocketFactory backed by the given JSSE
- * SSLSocketFactory.
- *
- * @hide
- */
- public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) {
- super();
- this.sslcontext = null;
- this.socketfactory = socketfactory;
- this.nameResolver = null;
- }
-
- /**
- * Creates the default SSL socket factory.
- * This constructor is used exclusively to instantiate the factory for
- * {@link #getSocketFactory getSocketFactory}.
- */
- private SSLSocketFactory() {
- super();
- this.sslcontext = null;
- this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory();
- this.nameResolver = null;
- }
-
- private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password)
- throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
- if (keystore == null) {
- throw new IllegalArgumentException("Keystore may not be null");
- }
- KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
- KeyManagerFactory.getDefaultAlgorithm());
- kmfactory.init(keystore, password != null ? password.toCharArray(): null);
- return kmfactory.getKeyManagers();
- }
-
- private static TrustManager[] createTrustManagers(final KeyStore keystore)
- throws KeyStoreException, NoSuchAlgorithmException {
- if (keystore == null) {
- throw new IllegalArgumentException("Keystore may not be null");
- }
- TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- tmfactory.init(keystore);
- return tmfactory.getTrustManagers();
- }
-
-
- // non-javadoc, see interface org.apache.http.conn.SocketFactory
- public Socket createSocket()
- throws IOException {
-
- // the cast makes sure that the factory is working as expected
- return (SSLSocket) this.socketfactory.createSocket();
- }
-
-
- // non-javadoc, see interface org.apache.http.conn.SocketFactory
- public Socket connectSocket(
- final Socket sock,
- final String host,
- final int port,
- final InetAddress localAddress,
- int localPort,
- final HttpParams params
- ) throws IOException {
-
- if (host == null) {
- throw new IllegalArgumentException("Target host may not be null.");
- }
- if (params == null) {
- throw new IllegalArgumentException("Parameters may not be null.");
- }
-
- SSLSocket sslsock = (SSLSocket)
- ((sock != null) ? sock : createSocket());
-
- if ((localAddress != null) || (localPort > 0)) {
-
- // we need to bind explicitly
- if (localPort < 0)
- localPort = 0; // indicates "any"
-
- InetSocketAddress isa =
- new InetSocketAddress(localAddress, localPort);
- sslsock.bind(isa);
- }
-
- int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
- int soTimeout = HttpConnectionParams.getSoTimeout(params);
-
- InetSocketAddress remoteAddress;
- if (this.nameResolver != null) {
- remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port);
- } else {
- remoteAddress = new InetSocketAddress(host, port);
- }
-
- sslsock.connect(remoteAddress, connTimeout);
-
- sslsock.setSoTimeout(soTimeout);
- try {
- hostnameVerifier.verify(host, sslsock);
- // verifyHostName() didn't blowup - good!
- } catch (IOException iox) {
- // close the socket before re-throwing the exception
- try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
- throw iox;
- }
-
- return sslsock;
- }
-
-
- /**
- * Checks whether a socket connection is secure.
- * This factory creates TLS/SSL socket connections
- * which, by default, are considered secure.
- * <br/>
- * Derived classes may override this method to perform
- * runtime checks, for example based on the cypher suite.
- *
- * @param sock the connected socket
- *
- * @return <code>true</code>
- *
- * @throws IllegalArgumentException if the argument is invalid
- */
- public boolean isSecure(Socket sock)
- throws IllegalArgumentException {
-
- if (sock == null) {
- throw new IllegalArgumentException("Socket may not be null.");
- }
- // This instanceof check is in line with createSocket() above.
- if (!(sock instanceof SSLSocket)) {
- throw new IllegalArgumentException
- ("Socket not created by this factory.");
- }
- // This check is performed last since it calls the argument object.
- if (sock.isClosed()) {
- throw new IllegalArgumentException("Socket is closed.");
- }
-
- return true;
-
- } // isSecure
-
-
- // non-javadoc, see interface LayeredSocketFactory
- public Socket createSocket(
- final Socket socket,
- final String host,
- final int port,
- final boolean autoClose
- ) throws IOException, UnknownHostException {
- SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
- socket,
- host,
- port,
- autoClose
- );
- hostnameVerifier.verify(host, sslSocket);
- // verifyHostName() didn't blowup - good!
- return sslSocket;
- }
-
- public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
- if ( hostnameVerifier == null ) {
- throw new IllegalArgumentException("Hostname verifier may not be null");
- }
- this.hostnameVerifier = hostnameVerifier;
- }
-
- public X509HostnameVerifier getHostnameVerifier() {
- return hostnameVerifier;
- }
-
-}
diff --git a/src/org/apache/http/conn/ssl/StrictHostnameVerifier.java b/src/org/apache/http/conn/ssl/StrictHostnameVerifier.java
deleted file mode 100644
index bd9e70d..0000000
--- a/src/org/apache/http/conn/ssl/StrictHostnameVerifier.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java $
- * $Revision: 617642 $
- * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.ssl;
-
-import javax.net.ssl.SSLException;
-
-/**
- * The Strict HostnameVerifier works the same way as Sun Java 1.4, Sun
- * Java 5, Sun Java 6-rc. It's also pretty close to IE6. This
- * implementation appears to be compliant with RFC 2818 for dealing with
- * wildcards.
- * <p/>
- * The hostname must match either the first CN, or any of the subject-alts.
- * A wildcard can occur in the CN, and in any of the subject-alts. The
- * one divergence from IE6 is how we only check the first CN. IE6 allows
- * a match against any of the CNs present. We decided to follow in
- * Sun Java 1.4's footsteps and only check the first CN. (If you need
- * to check all the CN's, feel free to write your own implementation!).
- * <p/>
- * A wildcard such as "*.foo.com" matches only subdomains in the same
- * level, for example "a.foo.com". It does not match deeper subdomains
- * such as "a.b.foo.com".
- *
- * @author Julius Davies
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public class StrictHostnameVerifier extends AbstractVerifier {
-
- public final void verify(
- final String host,
- final String[] cns,
- final String[] subjectAlts) throws SSLException {
- verify(host, cns, subjectAlts, true);
- }
-
- @Override
- public final String toString() {
- return "STRICT";
- }
-
-}
diff --git a/src/org/apache/http/conn/ssl/X509HostnameVerifier.java b/src/org/apache/http/conn/ssl/X509HostnameVerifier.java
deleted file mode 100644
index e38db5f..0000000
--- a/src/org/apache/http/conn/ssl/X509HostnameVerifier.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java $
- * $Revision: 618365 $
- * $Date: 2008-02-04 10:20:08 -0800 (Mon, 04 Feb 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.conn.ssl;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-import java.io.IOException;
-import java.security.cert.X509Certificate;
-
-/**
- * Interface for checking if a hostname matches the names stored inside the
- * server's X.509 certificate. Implements javax.net.ssl.HostnameVerifier, but
- * we don't actually use that interface. Instead we added some methods that
- * take String parameters (instead of javax.net.ssl.HostnameVerifier's
- * SSLSession). JUnit is a lot easier this way! :-)
- * <p/>
- * We provide the HostnameVerifier.DEFAULT, HostnameVerifier.STRICT, and
- * HostnameVerifier.ALLOW_ALL implementations. But feel free to define
- * your own implementation!
- * <p/>
- * Inspired by Sebastian Hauer's original StrictSSLProtocolSocketFactory in the
- * HttpClient "contrib" repository.
- *
- * @author Julius Davies
- * @author <a href="mailto:hauer@psicode.com">Sebastian Hauer</a>
- *
- * @since 4.0 (8-Dec-2006)
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public interface X509HostnameVerifier extends HostnameVerifier {
-
- boolean verify(String host, SSLSession session);
-
- void verify(String host, SSLSocket ssl) throws IOException;
-
- void verify(String host, X509Certificate cert) throws SSLException;
-
- /**
- * Checks to see if the supplied hostname matches any of the supplied CNs
- * or "DNS" Subject-Alts. Most implementations only look at the first CN,
- * and ignore any additional CNs. Most implementations do look at all of
- * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
- * according to RFC 2818.
- *
- * @param cns CN fields, in order, as extracted from the X.509
- * certificate.
- * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
- * from the X.509 certificate.
- * @param host The hostname to verify.
- * @throws SSLException If verification failed.
- */
- void verify(String host, String[] cns, String[] subjectAlts)
- throws SSLException;
-
-
-}
diff --git a/src/org/apache/http/conn/ssl/package.html b/src/org/apache/http/conn/ssl/package.html
deleted file mode 100644
index a5c737f..0000000
--- a/src/org/apache/http/conn/ssl/package.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<html>
-<head>
-<!--
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/package.html $
- * $Revision: 555193 $
- * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made 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>
-TLS/SSL specific parts of the <i>HttpConn</i> API.
-
-</body>
-</html>
diff --git a/src/org/apache/http/params/CoreConnectionPNames.java b/src/org/apache/http/params/CoreConnectionPNames.java
deleted file mode 100644
index 9479db1..0000000
--- a/src/org/apache/http/params/CoreConnectionPNames.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/CoreConnectionPNames.java $
- * $Revision: 576077 $
- * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.params;
-
-
-/**
- * Defines parameter names for connections in HttpCore.
- *
- * @version $Revision: 576077 $
- *
- * @since 4.0
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public interface CoreConnectionPNames {
-
- /**
- * Defines the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
- * timeout for waiting for data. A timeout value of zero is interpreted as an infinite
- * timeout. This value is used when no socket timeout is set in the
- * method parameters.
- * <p>
- * This parameter expects a value of type {@link Integer}.
- * </p>
- * @see java.net.SocketOptions#SO_TIMEOUT
- */
- public static final String SO_TIMEOUT = "http.socket.timeout";
-
- /**
- * Determines whether Nagle's algorithm is to be used. The Nagle's algorithm
- * tries to conserve bandwidth by minimizing the number of segments that are
- * sent. When applications wish to decrease network latency and increase
- * performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY).
- * Data will be sent earlier, at the cost of an increase in bandwidth consumption.
- * <p>
- * This parameter expects a value of type {@link Boolean}.
- * </p>
- * @see java.net.SocketOptions#TCP_NODELAY
- */
- public static final String TCP_NODELAY = "http.tcp.nodelay";
-
- /**
- * Determines the size of the internal socket buffer used to buffer data
- * while receiving / transmitting HTTP messages.
- * <p>
- * This parameter expects a value of type {@link Integer}.
- * </p>
- */
- public static final String SOCKET_BUFFER_SIZE = "http.socket.buffer-size";
-
- /**
- * Sets SO_LINGER with the specified linger time in seconds. The maximum timeout
- * value is platform specific. Value <tt>0</tt> implies that the option is disabled.
- * Value <tt>-1</tt> implies that the JRE default is used. The setting only affects
- * socket close.
- * <p>
- * This parameter expects a value of type {@link Integer}.
- * </p>
- * @see java.net.SocketOptions#SO_LINGER
- */
- public static final String SO_LINGER = "http.socket.linger";
-
- /**
- * Determines the timeout until a connection is etablished. A value of zero
- * means the timeout is not used. The default value is zero.
- * <p>
- * This parameter expects a value of type {@link Integer}.
- * </p>
- */
- public static final String CONNECTION_TIMEOUT = "http.connection.timeout";
-
- /**
- * Determines whether stale connection check is to be used. Disabling
- * stale connection check may result in slight performance improvement
- * at the risk of getting an I/O error when executing a request over a
- * connection that has been closed at the server side.
- * <p>
- * This parameter expects a value of type {@link Boolean}.
- * </p>
- */
- public static final String STALE_CONNECTION_CHECK = "http.connection.stalecheck";
-
- /**
- * Determines the maximum line length limit. If set to a positive value, any HTTP
- * line exceeding this limit will cause an IOException. A negative or zero value
- * will effectively disable the check.
- * <p>
- * This parameter expects a value of type {@link Integer}.
- * </p>
- */
- public static final String MAX_LINE_LENGTH = "http.connection.max-line-length";
-
- /**
- * Determines the maximum HTTP header count allowed. If set to a positive value,
- * the number of HTTP headers received from the data stream exceeding this limit
- * will cause an IOException. A negative or zero value will effectively disable
- * the check.
- * <p>
- * This parameter expects a value of type {@link Integer}.
- * </p>
- */
- public static final String MAX_HEADER_COUNT = "http.connection.max-header-count";
-
-}
diff --git a/src/org/apache/http/params/HttpConnectionParams.java b/src/org/apache/http/params/HttpConnectionParams.java
deleted file mode 100644
index a7b31fc..0000000
--- a/src/org/apache/http/params/HttpConnectionParams.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpConnectionParams.java $
- * $Revision: 576089 $
- * $Date: 2007-09-16 05:39:56 -0700 (Sun, 16 Sep 2007) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.params;
-
-/**
- * An adaptor for accessing connection parameters in {@link HttpParams}.
- * <br/>
- * Note that the <i>implements</i> relation to {@link CoreConnectionPNames}
- * is for compatibility with existing application code only. References to
- * the parameter names should use the interface, not this class.
- *
- * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
- *
- * @version $Revision: 576089 $
- *
- * @since 4.0
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public final class HttpConnectionParams implements CoreConnectionPNames {
-
- /**
- */
- private HttpConnectionParams() {
- super();
- }
-
- /**
- * Returns the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
- * timeout for waiting for data. A timeout value of zero is interpreted as an infinite
- * timeout. This value is used when no socket timeout is set in the
- * method parameters.
- *
- * @return timeout in milliseconds
- */
- public static int getSoTimeout(final HttpParams params) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- return params.getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0);
- }
-
- /**
- * Sets the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
- * timeout for waiting for data. A timeout value of zero is interpreted as an infinite
- * timeout. This value is used when no socket timeout is set in the
- * method parameters.
- *
- * @param timeout Timeout in milliseconds
- */
- public static void setSoTimeout(final HttpParams params, int timeout) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
-
- }
-
- /**
- * Tests if Nagle's algorithm is to be used.
- *
- * @return <tt>true</tt> if the Nagle's algorithm is to NOT be used
- * (that is enable TCP_NODELAY), <tt>false</tt> otherwise.
- */
- public static boolean getTcpNoDelay(final HttpParams params) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- return params.getBooleanParameter
- (CoreConnectionPNames.TCP_NODELAY, true);
- }
-
- /**
- * Determines whether Nagle's algorithm is to be used. The Nagle's algorithm
- * tries to conserve bandwidth by minimizing the number of segments that are
- * sent. When applications wish to decrease network latency and increase
- * performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY).
- * Data will be sent earlier, at the cost of an increase in bandwidth consumption.
- *
- * @param value <tt>true</tt> if the Nagle's algorithm is to NOT be used
- * (that is enable TCP_NODELAY), <tt>false</tt> otherwise.
- */
- public static void setTcpNoDelay(final HttpParams params, boolean value) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, value);
- }
-
- public static int getSocketBufferSize(final HttpParams params) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- return params.getIntParameter
- (CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1);
- }
-
- public static void setSocketBufferSize(final HttpParams params, int size) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, size);
- }
-
- /**
- * Returns linger-on-close timeout. Value <tt>0</tt> implies that the option is
- * disabled. Value <tt>-1</tt> implies that the JRE default is used.
- *
- * @return the linger-on-close timeout
- */
- public static int getLinger(final HttpParams params) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- return params.getIntParameter(CoreConnectionPNames.SO_LINGER, -1);
- }
-
- /**
- * Returns linger-on-close timeout. This option disables/enables immediate return
- * from a close() of a TCP Socket. Enabling this option with a non-zero Integer
- * timeout means that a close() will block pending the transmission and
- * acknowledgement of all data written to the peer, at which point the socket is
- * closed gracefully. Value <tt>0</tt> implies that the option is
- * disabled. Value <tt>-1</tt> implies that the JRE default is used.
- *
- * @param value the linger-on-close timeout
- */
- public static void setLinger(final HttpParams params, int value) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- params.setIntParameter(CoreConnectionPNames.SO_LINGER, value);
- }
-
- /**
- * Returns the timeout until a connection is etablished. A value of zero
- * means the timeout is not used. The default value is zero.
- *
- * @return timeout in milliseconds.
- */
- public static int getConnectionTimeout(final HttpParams params) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- return params.getIntParameter
- (CoreConnectionPNames.CONNECTION_TIMEOUT, 0);
- }
-
- /**
- * Sets the timeout until a connection is etablished. A value of zero
- * means the timeout is not used. The default value is zero.
- *
- * @param timeout Timeout in milliseconds.
- */
- public static void setConnectionTimeout(final HttpParams params, int timeout) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- params.setIntParameter
- (CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
- }
-
- /**
- * Tests whether stale connection check is to be used. Disabling
- * stale connection check may result in slight performance improvement
- * at the risk of getting an I/O error when executing a request over a
- * connection that has been closed at the server side.
- *
- * @return <tt>true</tt> if stale connection check is to be used,
- * <tt>false</tt> otherwise.
- */
- public static boolean isStaleCheckingEnabled(final HttpParams params) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- return params.getBooleanParameter
- (CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
- }
-
- /**
- * Defines whether stale connection check is to be used. Disabling
- * stale connection check may result in slight performance improvement
- * at the risk of getting an I/O error when executing a request over a
- * connection that has been closed at the server side.
- *
- * @param value <tt>true</tt> if stale connection check is to be used,
- * <tt>false</tt> otherwise.
- */
- public static void setStaleCheckingEnabled(final HttpParams params, boolean value) {
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- params.setBooleanParameter
- (CoreConnectionPNames.STALE_CONNECTION_CHECK, value);
- }
-
-}
diff --git a/src/org/apache/http/params/HttpParams.java b/src/org/apache/http/params/HttpParams.java
deleted file mode 100644
index 9562e54..0000000
--- a/src/org/apache/http/params/HttpParams.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpParams.java $
- * $Revision: 610763 $
- * $Date: 2008-01-10 04:01:13 -0800 (Thu, 10 Jan 2008) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.params;
-
-/**
- * Represents a collection of HTTP protocol and framework parameters.
- *
- * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
- *
- * @version $Revision: 610763 $
- *
- * @since 4.0
- *
- * @deprecated Please use {@link java.net.URL#openConnection} instead.
- * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
- */
-@Deprecated
-public interface HttpParams {
-
- /**
- * Obtains the value of the given parameter.
- *
- * @param name the parent name.
- *
- * @return an object that represents the value of the parameter,
- * <code>null</code> if the parameter is not set or if it
- * is explicitly set to <code>null</code>
- *
- * @see #setParameter(String, Object)
- */
- Object getParameter(String name);
-
- /**
- * Assigns the value to the parameter with the given name.
- *
- * @param name parameter name
- * @param value parameter value
- */
- HttpParams setParameter(String name, Object value);
-
- /**
- * Creates a copy of these parameters.
- *
- * @return a new set of parameters holding the same values as this one
- */
- HttpParams copy();
-
- /**
- * Removes the parameter with the specified name.
- *
- * @param name parameter name
- *
- * @return true if the parameter existed and has been removed, false else.
- */
- boolean removeParameter(String name);
-
- /**
- * Returns a {@link Long} parameter value with the given name.
- * If the parameter is not explicitly set, the default value is returned.
- *
- * @param name the parent name.
- * @param defaultValue the default value.
- *
- * @return a {@link Long} that represents the value of the parameter.
- *
- * @see #setLongParameter(String, long)
- */
- long getLongParameter(String name, long defaultValue);
-
- /**
- * Assigns a {@link Long} to the parameter with the given name
- *
- * @param name parameter name
- * @param value parameter value
- */
- HttpParams setLongParameter(String name, long value);
-
- /**
- * Returns an {@link Integer} parameter value with the given name.
- * If the parameter is not explicitly set, the default value is returned.
- *
- * @param name the parent name.
- * @param defaultValue the default value.
- *
- * @return a {@link Integer} that represents the value of the parameter.
- *
- * @see #setIntParameter(String, int)
- */
- int getIntParameter(String name, int defaultValue);
-
- /**
- * Assigns an {@link Integer} to the parameter with the given name
- *
- * @param name parameter name
- * @param value parameter value
- */
- HttpParams setIntParameter(String name, int value);
-
- /**
- * Returns a {@link Double} parameter value with the given name.
- * If the parameter is not explicitly set, the default value is returned.
- *
- * @param name the parent name.
- * @param defaultValue the default value.
- *
- * @return a {@link Double} that represents the value of the parameter.
- *
- * @see #setDoubleParameter(String, double)
- */
- double getDoubleParameter(String name, double defaultValue);
-
- /**
- * Assigns a {@link Double} to the parameter with the given name
- *
- * @param name parameter name
- * @param value parameter value
- */
- HttpParams setDoubleParameter(String name, double value);
-
- /**
- * Returns a {@link Boolean} parameter value with the given name.
- * If the parameter is not explicitly set, the default value is returned.
- *
- * @param name the parent name.
- * @param defaultValue the default value.
- *
- * @return a {@link Boolean} that represents the value of the parameter.
- *
- * @see #setBooleanParameter(String, boolean)
- */
- boolean getBooleanParameter(String name, boolean defaultValue);
-
- /**
- * Assigns a {@link Boolean} to the parameter with the given name
- *
- * @param name parameter name
- * @param value parameter value
- */
- HttpParams setBooleanParameter(String name, boolean value);
-
- /**
- * Checks if a boolean parameter is set to <code>true</code>.
- *
- * @param name parameter name
- *
- * @return <tt>true</tt> if the parameter is set to value <tt>true</tt>,
- * <tt>false</tt> if it is not set or set to <code>false</code>
- */
- boolean isParameterTrue(String name);
-
- /**
- * Checks if a boolean parameter is not set or <code>false</code>.
- *
- * @param name parameter name
- *
- * @return <tt>true</tt> if the parameter is either not set or
- * set to value <tt>false</tt>,
- * <tt>false</tt> if it is set to <code>true</code>
- */
- boolean isParameterFalse(String name);
-
-}