summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
Diffstat (limited to 'luni')
-rw-r--r--luni/src/main/java/java/net/ExtendedResponseCache.java51
-rw-r--r--luni/src/main/java/java/net/HttpURLConnection.java20
-rw-r--r--luni/src/main/java/java/net/ResponseSource.java45
-rw-r--r--luni/src/main/java/java/net/URL.java16
-rw-r--r--luni/src/main/java/java/nio/DirectByteBuffer.java2
-rw-r--r--luni/src/main/java/java/util/Locale.java24
-rw-r--r--luni/src/main/java/java/util/zip/ZipEntry.java8
-rw-r--r--luni/src/main/java/java/util/zip/ZipFile.java22
-rw-r--r--luni/src/main/java/java/util/zip/ZipInputStream.java4
-rw-r--r--luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java107
-rw-r--r--luni/src/main/java/libcore/net/http/AbstractHttpOutputStream.java40
-rw-r--r--luni/src/main/java/libcore/net/http/Challenge.java46
-rw-r--r--luni/src/main/java/libcore/net/http/ChunkedInputStream.java114
-rw-r--r--luni/src/main/java/libcore/net/http/ChunkedOutputStream.java133
-rw-r--r--luni/src/main/java/libcore/net/http/FixedLengthInputStream.java72
-rw-r--r--luni/src/main/java/libcore/net/http/FixedLengthOutputStream.java61
-rw-r--r--luni/src/main/java/libcore/net/http/HeaderParser.java166
-rw-r--r--luni/src/main/java/libcore/net/http/HttpConnection.java343
-rw-r--r--luni/src/main/java/libcore/net/http/HttpConnectionPool.java120
-rw-r--r--luni/src/main/java/libcore/net/http/HttpEngine.java852
-rw-r--r--luni/src/main/java/libcore/net/http/HttpHandler.java42
-rw-r--r--luni/src/main/java/libcore/net/http/HttpResponseCache.java591
-rw-r--r--luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java519
-rw-r--r--luni/src/main/java/libcore/net/http/HttpsHandler.java42
-rw-r--r--luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java580
-rw-r--r--luni/src/main/java/libcore/net/http/RawHeaders.java297
-rw-r--r--luni/src/main/java/libcore/net/http/RequestHeaders.java292
-rw-r--r--luni/src/main/java/libcore/net/http/ResponseHeaders.java508
-rw-r--r--luni/src/main/java/libcore/net/http/RetryableOutputStream.java72
-rw-r--r--luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java65
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java6
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java4
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java80
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java4
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java4
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java15
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java2
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java9
-rw-r--r--luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp48
-rw-r--r--luni/src/test/java/libcore/java/net/URLConnectionTest.java17
-rw-r--r--luni/src/test/java/libcore/java/nio/BufferTest.java2
-rw-r--r--luni/src/test/java/libcore/java/util/LocaleTest.java41
-rw-r--r--luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java1886
-rw-r--r--luni/src/test/java/libcore/net/http/ParsedHeadersTest.java244
-rw-r--r--luni/src/test/java/libcore/net/http/RawHeadersTest.java54
-rw-r--r--luni/src/test/java/tests/api/java/util/LocaleTest.java37
46 files changed, 203 insertions, 7504 deletions
diff --git a/luni/src/main/java/java/net/ExtendedResponseCache.java b/luni/src/main/java/java/net/ExtendedResponseCache.java
deleted file mode 100644
index a70e734..0000000
--- a/luni/src/main/java/java/net/ExtendedResponseCache.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2012 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 java.net;
-
-/**
- * A response cache that supports statistics tracking and updating stored
- * responses. Implementations of {@link ResponseCache} should implement this
- * interface to receive additional support from the HTTP engine.
- *
- * @hide
- */
-public interface ExtendedResponseCache {
-
- /*
- * This hidden interface is defined in a non-hidden package (java.net) so
- * its @hide tag will be parsed by Doclava. This hides this interface from
- * implementing classes' documentation.
- */
-
- /**
- * Track an HTTP response being satisfied by {@code source}.
- * @hide
- */
- void trackResponse(ResponseSource source);
-
- /**
- * Track an conditional GET that was satisfied by this cache.
- * @hide
- */
- void trackConditionalCacheHit();
-
- /**
- * Updates stored HTTP headers using a hit on a conditional GET.
- * @hide
- */
- void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection);
-}
diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java
index 2023887..573dde6 100644
--- a/luni/src/main/java/java/net/HttpURLConnection.java
+++ b/luni/src/main/java/java/net/HttpURLConnection.java
@@ -20,7 +20,6 @@ package java.net;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
-import libcore.net.http.HttpEngine;
/**
* An {@link URLConnection} for HTTP (<a
@@ -257,19 +256,20 @@ import libcore.net.http.HttpEngine;
* request/response pair. Instances of this class are not thread safe.
*/
public abstract class HttpURLConnection extends URLConnection {
+ private static final int DEFAULT_CHUNK_LENGTH = 1024;
/**
* The subset of HTTP methods that the user may select via {@link
* #setRequestMethod(String)}.
*/
private static final String[] PERMITTED_USER_METHODS = {
- HttpEngine.OPTIONS,
- HttpEngine.GET,
- HttpEngine.HEAD,
- HttpEngine.POST,
- HttpEngine.PUT,
- HttpEngine.DELETE,
- HttpEngine.TRACE
+ "OPTIONS",
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ "DELETE",
+ "TRACE"
// Note: we don't allow users to specify "CONNECT"
};
@@ -277,7 +277,7 @@ public abstract class HttpURLConnection extends URLConnection {
* The HTTP request method of this {@code HttpURLConnection}. The default
* value is {@code "GET"}.
*/
- protected String method = HttpEngine.GET;
+ protected String method = "GET";
/**
* The status code of the response obtained from the HTTP request. The
@@ -787,7 +787,7 @@ public abstract class HttpURLConnection extends URLConnection {
throw new IllegalStateException("Already in fixed-length mode");
}
if (chunkLength <= 0) {
- this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH;
+ this.chunkLength = DEFAULT_CHUNK_LENGTH;
} else {
this.chunkLength = chunkLength;
}
diff --git a/luni/src/main/java/java/net/ResponseSource.java b/luni/src/main/java/java/net/ResponseSource.java
deleted file mode 100644
index fb974e9..0000000
--- a/luni/src/main/java/java/net/ResponseSource.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2011 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 java.net;
-
-/**
- * Where the HTTP client should look for a response.
- *
- * @hide
- */
-public enum ResponseSource {
-
- /**
- * Return the response from the cache immediately.
- */
- CACHE,
-
- /**
- * Make a conditional request to the host, returning the cache response if
- * the cache is valid and the network response otherwise.
- */
- CONDITIONAL_CACHE,
-
- /**
- * Return the response from the network.
- */
- NETWORK;
-
- public boolean requiresConnection() {
- return this == CONDITIONAL_CACHE || this == NETWORK;
- }
-}
diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java
index 9d44498..dd487bc 100644
--- a/luni/src/main/java/java/net/URL.java
+++ b/luni/src/main/java/java/net/URL.java
@@ -24,8 +24,6 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.jar.JarFile;
-import libcore.net.http.HttpHandler;
-import libcore.net.http.HttpsHandler;
import libcore.net.url.FileHandler;
import libcore.net.url.FtpHandler;
import libcore.net.url.JarHandler;
@@ -427,9 +425,19 @@ public final class URL implements Serializable {
} else if (protocol.equals("ftp")) {
streamHandler = new FtpHandler();
} else if (protocol.equals("http")) {
- streamHandler = new HttpHandler();
+ try {
+ String name = "com.android.okhttp.HttpHandler";
+ streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
} else if (protocol.equals("https")) {
- streamHandler = new HttpsHandler();
+ try {
+ String name = "com.android.okhttp.HttpsHandler";
+ streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
} else if (protocol.equals("jar")) {
streamHandler = new JarHandler();
}
diff --git a/luni/src/main/java/java/nio/DirectByteBuffer.java b/luni/src/main/java/java/nio/DirectByteBuffer.java
index 43db9e0..1d12f2e 100644
--- a/luni/src/main/java/java/nio/DirectByteBuffer.java
+++ b/luni/src/main/java/java/nio/DirectByteBuffer.java
@@ -44,7 +44,7 @@ class DirectByteBuffer extends MappedByteBuffer {
}
// Used by the JNI NewDirectByteBuffer function.
- DirectByteBuffer(int address, int capacity) {
+ DirectByteBuffer(long address, int capacity) {
this(MemoryBlock.wrapFromJni(address, capacity), capacity, 0, false, null);
}
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index d513bd7..2b89c76 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -495,29 +495,33 @@ public final class Locale implements Cloneable, Serializable {
}
/**
- * Returns the three letter ISO country code which corresponds to the country
+ * Returns the three-letter ISO 3166 country code which corresponds to the country
* code for this {@code Locale}.
+ * @throws MissingResourceException if there's no 3-letter country code for this locale.
*/
public String getISO3Country() {
- if (countryCode.length() == 0) {
- return countryCode;
+ String code = ICU.getISO3CountryNative(toString());
+ if (!countryCode.isEmpty() && code.isEmpty()) {
+ throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry");
}
- return ICU.getISO3CountryNative(toString());
+ return code;
}
/**
- * Returns the three letter ISO language code which corresponds to the language
+ * Returns the three-letter ISO 639-2/T language code which corresponds to the language
* code for this {@code Locale}.
+ * @throws MissingResourceException if there's no 3-letter language code for this locale.
*/
public String getISO3Language() {
- if (languageCode.length() == 0) {
- return languageCode;
+ String code = ICU.getISO3LanguageNative(toString());
+ if (!languageCode.isEmpty() && code.isEmpty()) {
+ throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage");
}
- return ICU.getISO3LanguageNative(toString());
+ return code;
}
/**
- * Returns an array of strings containing all the two-letter ISO country codes that can be
+ * Returns an array of strings containing all the two-letter ISO 3166 country codes that can be
* used as the country code when constructing a {@code Locale}.
*/
public static String[] getISOCountries() {
@@ -525,7 +529,7 @@ public final class Locale implements Cloneable, Serializable {
}
/**
- * Returns an array of strings containing all the two-letter ISO language codes that can be
+ * Returns an array of strings containing all the two-letter ISO 639-1 language codes that can be
* used as the language code when constructing a {@code Locale}.
*/
public static String[] getISOLanguages() {
diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java
index 563f649..c8c733d 100644
--- a/luni/src/main/java/java/util/zip/ZipEntry.java
+++ b/luni/src/main/java/java/util/zip/ZipEntry.java
@@ -357,7 +357,13 @@ public class ZipEntry implements ZipConstants, Cloneable {
throw new ZipException("Central Directory Entry not found");
}
- it.seek(10);
+ it.seek(8);
+ int gpbf = it.readShort();
+
+ if ((gpbf & ~ZipFile.GPBF_SUPPORTED_MASK) != 0) {
+ throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
+ }
+
compressionMethod = it.readShort();
time = it.readShort();
modDate = it.readShort();
diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java
index 4dacb82..93410b7 100644
--- a/luni/src/main/java/java/util/zip/ZipFile.java
+++ b/luni/src/main/java/java/util/zip/ZipFile.java
@@ -68,6 +68,12 @@ public class ZipFile implements ZipConstants {
static final int GPBF_UTF8_FLAG = 1 << 11;
/**
+ * Supported General Purpose Bit Flags Mask.
+ * Bit mask of supported GPBF bits.
+ */
+ static final int GPBF_SUPPORTED_MASK = GPBF_DATA_DESCRIPTOR_FLAG | GPBF_UTF8_FLAG;
+
+ /**
* Open zip file for reading.
*/
public static final int OPEN_READ = 1;
@@ -242,11 +248,19 @@ public class ZipFile implements ZipConstants {
RandomAccessFile localRaf = raf;
synchronized (localRaf) {
// We don't know the entry data's start position. All we have is the
- // position of the entry's local header. At position 28 we find the
- // length of the extra data. In some cases this length differs from
- // the one coming in the central header.
- RAFStream rafStream = new RAFStream(localRaf, entry.localHeaderRelOffset + 28);
+ // position of the entry's local header. At position 6 we find the
+ // General Purpose Bit Flag.
+ // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+ RAFStream rafStream= new RAFStream(localRaf, entry.localHeaderRelOffset + 6);
DataInputStream is = new DataInputStream(rafStream);
+ int gpbf = Short.reverseBytes(is.readShort());
+ if ((gpbf & ~ZipFile.GPBF_SUPPORTED_MASK) != 0) {
+ throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
+ }
+
+ // At position 28 we find the length of the extra data. In some cases
+ // this length differs from the one coming in the central header.
+ is.skipBytes(20);
int localExtraLenOrWhatever = Short.reverseBytes(is.readShort());
is.close();
diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java
index 97aa350..0135e1e 100644
--- a/luni/src/main/java/java/util/zip/ZipInputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipInputStream.java
@@ -237,6 +237,10 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
throw new ZipException("Cannot read local header version " + version);
}
int flags = peekShort(LOCFLG - LOCVER);
+ if ((flags & ~ZipFile.GPBF_SUPPORTED_MASK) != 0) {
+ throw new ZipException("Invalid General Purpose Bit Flag: " + flags);
+ }
+
hasDD = ((flags & ZipFile.GPBF_DATA_DESCRIPTOR_FLAG) != 0);
int ceLastModifiedTime = peekShort(LOCTIM - LOCVER);
int ceLastModifiedDate = peekShort(LOCTIM - LOCVER + 2);
diff --git a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
deleted file mode 100644
index 70f76b7..0000000
--- a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.CacheRequest;
-import libcore.io.Streams;
-
-/**
- * An input stream for the body of an HTTP response.
- *
- * <p>Since a single socket's input stream may be used to read multiple HTTP
- * responses from the same server, subclasses shouldn't close the socket stream.
- *
- * <p>A side effect of reading an HTTP response is that the response cache
- * is populated. If the stream is closed early, that cache entry will be
- * invalidated.
- */
-abstract class AbstractHttpInputStream extends InputStream {
- protected final InputStream in;
- protected final HttpEngine httpEngine;
- private final CacheRequest cacheRequest;
- private final OutputStream cacheBody;
- protected boolean closed;
-
- AbstractHttpInputStream(InputStream in, HttpEngine httpEngine,
- CacheRequest cacheRequest) throws IOException {
- this.in = in;
- this.httpEngine = httpEngine;
-
- OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null;
-
- // some apps return a null body; for compatibility we treat that like a null cache request
- if (cacheBody == null) {
- cacheRequest = null;
- }
-
- this.cacheBody = cacheBody;
- this.cacheRequest = cacheRequest;
- }
-
- /**
- * read() is implemented using read(byte[], int, int) so subclasses only
- * need to override the latter.
- */
- @Override public final int read() throws IOException {
- return Streams.readSingleByte(this);
- }
-
- protected final void checkNotClosed() throws IOException {
- if (closed) {
- throw new IOException("stream closed");
- }
- }
-
- protected final void cacheWrite(byte[] buffer, int offset, int count) throws IOException {
- if (cacheBody != null) {
- cacheBody.write(buffer, offset, count);
- }
- }
-
- /**
- * Closes the cache entry and makes the socket available for reuse. This
- * should be invoked when the end of the body has been reached.
- */
- protected final void endOfInput(boolean reuseSocket) throws IOException {
- if (cacheRequest != null) {
- cacheBody.close();
- }
- httpEngine.release(reuseSocket);
- }
-
- /**
- * Calls abort on the cache entry and disconnects the socket. This
- * should be invoked when the connection is closed unexpectedly to
- * invalidate the cache entry and to prevent the HTTP connection from
- * being reused. HTTP messages are sent in serial so whenever a message
- * cannot be read to completion, subsequent messages cannot be read
- * either and the connection must be discarded.
- *
- * <p>An earlier implementation skipped the remaining bytes, but this
- * requires that the entire transfer be completed. If the intention was
- * to cancel the transfer, closing the connection is the only solution.
- */
- protected final void unexpectedEndOfInput() {
- if (cacheRequest != null) {
- cacheRequest.abort();
- }
- httpEngine.release(false);
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/AbstractHttpOutputStream.java b/luni/src/main/java/libcore/net/http/AbstractHttpOutputStream.java
deleted file mode 100644
index 1e1b47b..0000000
--- a/luni/src/main/java/libcore/net/http/AbstractHttpOutputStream.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An output stream for the body of an HTTP request.
- *
- * <p>Since a single socket's output stream may be used to write multiple HTTP
- * requests to the same server, subclasses should not close the socket stream.
- */
-abstract class AbstractHttpOutputStream extends OutputStream {
- protected boolean closed;
-
- @Override public final void write(int data) throws IOException {
- write(new byte[] { (byte) data });
- }
-
- protected final void checkNotClosed() throws IOException {
- if (closed) {
- throw new IOException("stream closed");
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/Challenge.java b/luni/src/main/java/libcore/net/http/Challenge.java
deleted file mode 100644
index 0326c17..0000000
--- a/luni/src/main/java/libcore/net/http/Challenge.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 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 libcore.net.http;
-
-/**
- * An RFC 2617 challenge.
- *
- * @hide
- */
-public final class Challenge {
- final String scheme;
- final String realm;
-
- public Challenge(String scheme, String realm) {
- this.scheme = scheme;
- this.realm = realm;
- }
-
- @Override public boolean equals(Object o) {
- return o instanceof Challenge
- && ((Challenge) o).scheme.equals(scheme)
- && ((Challenge) o).realm.equals(realm);
- }
-
- @Override public int hashCode() {
- return scheme.hashCode() + 31 * realm.hashCode();
- }
-
- @Override public String toString() {
- return "Challenge[" + scheme + " " + realm + "]";
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
deleted file mode 100644
index 91cff7a..0000000
--- a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.CacheRequest;
-import java.util.Arrays;
-import libcore.io.Streams;
-
-/**
- * An HTTP body with alternating chunk sizes and chunk bodies.
- */
-final class ChunkedInputStream extends AbstractHttpInputStream {
- private static final int MIN_LAST_CHUNK_LENGTH = "\r\n0\r\n\r\n".length();
- private static final int NO_CHUNK_YET = -1;
- private int bytesRemainingInChunk = NO_CHUNK_YET;
- private boolean hasMoreChunks = true;
-
- ChunkedInputStream(InputStream is, CacheRequest cacheRequest,
- HttpEngine httpEngine) throws IOException {
- super(is, httpEngine, cacheRequest);
- }
-
- @Override public int read(byte[] buffer, int offset, int count) throws IOException {
- Arrays.checkOffsetAndCount(buffer.length, offset, count);
- checkNotClosed();
-
- if (!hasMoreChunks) {
- return -1;
- }
- if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
- readChunkSize();
- if (!hasMoreChunks) {
- return -1;
- }
- }
- int read = in.read(buffer, offset, Math.min(count, bytesRemainingInChunk));
- if (read == -1) {
- unexpectedEndOfInput(); // the server didn't supply the promised chunk length
- throw new IOException("unexpected end of stream");
- }
- bytesRemainingInChunk -= read;
- cacheWrite(buffer, offset, read);
-
- /*
- * If we're at the end of a chunk and the next chunk size is readable,
- * read it! Reading the last chunk causes the underlying connection to
- * be recycled and we want to do that as early as possible. Otherwise
- * self-delimiting streams like gzip will never be recycled.
- * http://code.google.com/p/android/issues/detail?id=7059
- */
- if (bytesRemainingInChunk == 0 && in.available() >= MIN_LAST_CHUNK_LENGTH) {
- readChunkSize();
- }
-
- return read;
- }
-
- private void readChunkSize() throws IOException {
- // read the suffix of the previous chunk
- if (bytesRemainingInChunk != NO_CHUNK_YET) {
- Streams.readAsciiLine(in);
- }
- String chunkSizeString = Streams.readAsciiLine(in);
- int index = chunkSizeString.indexOf(";");
- if (index != -1) {
- chunkSizeString = chunkSizeString.substring(0, index);
- }
- try {
- bytesRemainingInChunk = Integer.parseInt(chunkSizeString.trim(), 16);
- } catch (NumberFormatException e) {
- throw new IOException("Expected a hex chunk size, but was " + chunkSizeString);
- }
- if (bytesRemainingInChunk == 0) {
- hasMoreChunks = false;
- httpEngine.readTrailers();
- endOfInput(true);
- }
- }
-
- @Override public int available() throws IOException {
- checkNotClosed();
- if (!hasMoreChunks || bytesRemainingInChunk == NO_CHUNK_YET) {
- return 0;
- }
- return Math.min(in.available(), bytesRemainingInChunk);
- }
-
- @Override public void close() throws IOException {
- if (closed) {
- return;
- }
-
- closed = true;
- if (hasMoreChunks) {
- unexpectedEndOfInput();
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/ChunkedOutputStream.java b/luni/src/main/java/libcore/net/http/ChunkedOutputStream.java
deleted file mode 100644
index db65f7b..0000000
--- a/luni/src/main/java/libcore/net/http/ChunkedOutputStream.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-/**
- * An HTTP body with alternating chunk sizes and chunk bodies. Chunks are
- * buffered until {@code maxChunkLength} bytes are ready, at which point the
- * chunk is written and the buffer is cleared.
- */
-final class ChunkedOutputStream extends AbstractHttpOutputStream {
- private static final byte[] CRLF = { '\r', '\n' };
- private static final byte[] HEX_DIGITS = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
- };
- private static final byte[] FINAL_CHUNK = new byte[] { '0', '\r', '\n', '\r', '\n' };
-
- /** Scratch space for up to 8 hex digits, and then a constant CRLF */
- private final byte[] hex = { 0, 0, 0, 0, 0, 0, 0, 0, '\r', '\n' };
-
- private final OutputStream socketOut;
- private final int maxChunkLength;
- private final ByteArrayOutputStream bufferedChunk;
-
-
- public ChunkedOutputStream(OutputStream socketOut, int maxChunkLength) {
- this.socketOut = socketOut;
- this.maxChunkLength = Math.max(1, dataLength(maxChunkLength));
- this.bufferedChunk = new ByteArrayOutputStream(maxChunkLength);
- }
-
- /**
- * Returns the amount of data that can be transmitted in a chunk whose total
- * length (data+headers) is {@code dataPlusHeaderLength}. This is presumably
- * useful to match sizes with wire-protocol packets.
- */
- private int dataLength(int dataPlusHeaderLength) {
- int headerLength = 4; // "\r\n" after the size plus another "\r\n" after the data
- for (int i = dataPlusHeaderLength - headerLength; i > 0; i >>= 4) {
- headerLength++;
- }
- return dataPlusHeaderLength - headerLength;
- }
-
- @Override public synchronized void write(byte[] buffer, int offset, int count)
- throws IOException {
- checkNotClosed();
- Arrays.checkOffsetAndCount(buffer.length, offset, count);
-
- while (count > 0) {
- int numBytesWritten;
-
- if (bufferedChunk.size() > 0 || count < maxChunkLength) {
- // fill the buffered chunk and then maybe write that to the stream
- numBytesWritten = Math.min(count, maxChunkLength - bufferedChunk.size());
- // TODO: skip unnecessary copies from buffer->bufferedChunk?
- bufferedChunk.write(buffer, offset, numBytesWritten);
- if (bufferedChunk.size() == maxChunkLength) {
- writeBufferedChunkToSocket();
- }
-
- } else {
- // write a single chunk of size maxChunkLength to the stream
- numBytesWritten = maxChunkLength;
- writeHex(numBytesWritten);
- socketOut.write(buffer, offset, numBytesWritten);
- socketOut.write(CRLF);
- }
-
- offset += numBytesWritten;
- count -= numBytesWritten;
- }
- }
-
- /**
- * Equivalent to, but cheaper than writing Integer.toHexString().getBytes()
- * followed by CRLF.
- */
- private void writeHex(int i) throws IOException {
- int cursor = 8;
- do {
- hex[--cursor] = HEX_DIGITS[i & 0xf];
- } while ((i >>>= 4) != 0);
- socketOut.write(hex, cursor, hex.length - cursor);
- }
-
- @Override public synchronized void flush() throws IOException {
- if (closed) {
- return; // don't throw; this stream might have been closed on the caller's behalf
- }
- writeBufferedChunkToSocket();
- socketOut.flush();
- }
-
- @Override public synchronized void close() throws IOException {
- if (closed) {
- return;
- }
- closed = true;
- writeBufferedChunkToSocket();
- socketOut.write(FINAL_CHUNK);
- }
-
- private void writeBufferedChunkToSocket() throws IOException {
- int size = bufferedChunk.size();
- if (size <= 0) {
- return;
- }
-
- writeHex(size);
- bufferedChunk.writeTo(socketOut);
- bufferedChunk.reset();
- socketOut.write(CRLF);
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java b/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
deleted file mode 100644
index 1091af7..0000000
--- a/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.CacheRequest;
-import java.util.Arrays;
-
-/**
- * An HTTP body with a fixed length specified in advance.
- */
-final class FixedLengthInputStream extends AbstractHttpInputStream {
- private int bytesRemaining;
-
- public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest,
- HttpEngine httpEngine, int length) throws IOException {
- super(is, httpEngine, cacheRequest);
- bytesRemaining = length;
- if (bytesRemaining == 0) {
- endOfInput(true);
- }
- }
-
- @Override public int read(byte[] buffer, int offset, int count) throws IOException {
- Arrays.checkOffsetAndCount(buffer.length, offset, count);
- checkNotClosed();
- if (bytesRemaining == 0) {
- return -1;
- }
- int read = in.read(buffer, offset, Math.min(count, bytesRemaining));
- if (read == -1) {
- unexpectedEndOfInput(); // the server didn't supply the promised content length
- throw new IOException("unexpected end of stream");
- }
- bytesRemaining -= read;
- cacheWrite(buffer, offset, read);
- if (bytesRemaining == 0) {
- endOfInput(true);
- }
- return read;
- }
-
- @Override public int available() throws IOException {
- checkNotClosed();
- return bytesRemaining == 0 ? 0 : Math.min(in.available(), bytesRemaining);
- }
-
- @Override public void close() throws IOException {
- if (closed) {
- return;
- }
- closed = true;
- if (bytesRemaining != 0) {
- unexpectedEndOfInput();
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/FixedLengthOutputStream.java b/luni/src/main/java/libcore/net/http/FixedLengthOutputStream.java
deleted file mode 100644
index 461eed9..0000000
--- a/luni/src/main/java/libcore/net/http/FixedLengthOutputStream.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-/**
- * An HTTP body with a fixed length known in advance.
- */
-final class FixedLengthOutputStream extends AbstractHttpOutputStream {
- private final OutputStream socketOut;
- private int bytesRemaining;
-
- public FixedLengthOutputStream(OutputStream socketOut, int bytesRemaining) {
- this.socketOut = socketOut;
- this.bytesRemaining = bytesRemaining;
- }
-
- @Override public void write(byte[] buffer, int offset, int count) throws IOException {
- checkNotClosed();
- Arrays.checkOffsetAndCount(buffer.length, offset, count);
- if (count > bytesRemaining) {
- throw new IOException("expected " + bytesRemaining + " bytes but received " + count);
- }
- socketOut.write(buffer, offset, count);
- bytesRemaining -= count;
- }
-
- @Override public void flush() throws IOException {
- if (closed) {
- return; // don't throw; this stream might have been closed on the caller's behalf
- }
- socketOut.flush();
- }
-
- @Override public void close() throws IOException {
- if (closed) {
- return;
- }
- closed = true;
- if (bytesRemaining > 0) {
- throw new IOException("unexpected end of stream");
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HeaderParser.java b/luni/src/main/java/libcore/net/http/HeaderParser.java
deleted file mode 100644
index 8d5770e..0000000
--- a/luni/src/main/java/libcore/net/http/HeaderParser.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2011 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 libcore.net.http;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public final class HeaderParser {
-
- public interface CacheControlHandler {
- void handle(String directive, String parameter);
- }
-
- /**
- * Parse a comma-separated list of cache control header values.
- */
- public static void parseCacheControl(String value, CacheControlHandler handler) {
- int pos = 0;
- while (pos < value.length()) {
- int tokenStart = pos;
- pos = skipUntil(value, pos, "=,");
- String directive = value.substring(tokenStart, pos).trim();
-
- if (pos == value.length() || value.charAt(pos) == ',') {
- pos++; // consume ',' (if necessary)
- handler.handle(directive, null);
- continue;
- }
-
- pos++; // consume '='
- pos = skipWhitespace(value, pos);
-
- String parameter;
-
- // quoted string
- if (pos < value.length() && value.charAt(pos) == '\"') {
- pos++; // consume '"' open quote
- int parameterStart = pos;
- pos = skipUntil(value, pos, "\"");
- parameter = value.substring(parameterStart, pos);
- pos++; // consume '"' close quote (if necessary)
-
- // unquoted string
- } else {
- int parameterStart = pos;
- pos = skipUntil(value, pos, ",");
- parameter = value.substring(parameterStart, pos).trim();
- }
-
- handler.handle(directive, parameter);
- }
- }
-
- /**
- * Parse RFC 2617 challenges. This API is only interested in the scheme
- * name and realm.
- */
- public static List<Challenge> parseChallenges(
- RawHeaders responseHeaders, String challengeHeader) {
- /*
- * auth-scheme = token
- * auth-param = token "=" ( token | quoted-string )
- * challenge = auth-scheme 1*SP 1#auth-param
- * realm = "realm" "=" realm-value
- * realm-value = quoted-string
- */
- List<Challenge> result = new ArrayList<Challenge>();
- for (int h = 0; h < responseHeaders.length(); h++) {
- if (!challengeHeader.equalsIgnoreCase(responseHeaders.getFieldName(h))) {
- continue;
- }
- String value = responseHeaders.getValue(h);
- int pos = 0;
- while (pos < value.length()) {
- int tokenStart = pos;
- pos = skipUntil(value, pos, " ");
-
- String scheme = value.substring(tokenStart, pos).trim();
- pos = skipWhitespace(value, pos);
-
- // TODO: This currently only handles schemes with a 'realm' parameter;
- // It needs to be fixed to handle any scheme and any parameters
- // http://code.google.com/p/android/issues/detail?id=11140
-
- if (!value.regionMatches(pos, "realm=\"", 0, "realm=\"".length())) {
- break; // unexpected challenge parameter; give up
- }
-
- pos += "realm=\"".length();
- int realmStart = pos;
- pos = skipUntil(value, pos, "\"");
- String realm = value.substring(realmStart, pos);
- pos++; // consume '"' close quote
- pos = skipUntil(value, pos, ",");
- pos++; // consume ',' comma
- pos = skipWhitespace(value, pos);
- result.add(new Challenge(scheme, realm));
- }
- }
- return result;
- }
-
- /**
- * Returns the next index in {@code input} at or after {@code pos} that
- * contains a character from {@code characters}. Returns the input length if
- * none of the requested characters can be found.
- */
- private static int skipUntil(String input, int pos, String characters) {
- for (; pos < input.length(); pos++) {
- if (characters.indexOf(input.charAt(pos)) != -1) {
- break;
- }
- }
- return pos;
- }
-
- /**
- * Returns the next non-whitespace character in {@code input} that is white
- * space. Result is undefined if input contains newline characters.
- */
- private static int skipWhitespace(String input, int pos) {
- for (; pos < input.length(); pos++) {
- char c = input.charAt(pos);
- if (c != ' ' && c != '\t') {
- break;
- }
- }
- return pos;
- }
-
- /**
- * Returns {@code value} as a positive integer, or 0 if it is negative, or
- * -1 if it cannot be parsed.
- */
- public static int parseSeconds(String value) {
- try {
- long seconds = Long.parseLong(value);
- if (seconds > Integer.MAX_VALUE) {
- return Integer.MAX_VALUE;
- } else if (seconds < 0) {
- return 0;
- } else {
- return (int) seconds;
- }
- } catch (NumberFormatException e) {
- return -1;
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpConnection.java b/luni/src/main/java/libcore/net/http/HttpConnection.java
deleted file mode 100644
index 4a6e65d..0000000
--- a/luni/src/main/java/libcore/net/http/HttpConnection.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.URI;
-import java.net.UnknownHostException;
-import java.util.List;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import libcore.io.IoUtils;
-import libcore.util.Objects;
-import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
-
-/**
- * Holds the sockets and streams of an HTTP or HTTPS connection, which may be
- * used for multiple HTTP request/response exchanges. Connections may be direct
- * to the origin server or via a proxy. Create an instance using the {@link
- * Address} inner class.
- *
- * <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
- * which isn't so much a connection as a single request/response pair.
- */
-final class HttpConnection {
- private final Address address;
- private final Socket socket;
- private InputStream inputStream;
- private OutputStream outputStream;
- private SSLSocket unverifiedSocket;
- private SSLSocket sslSocket;
- private InputStream sslInputStream;
- private OutputStream sslOutputStream;
- private boolean recycled = false;
-
- private HttpConnection(Address config, int connectTimeout) throws IOException {
- this.address = config;
-
- /*
- * Try each of the host's addresses for best behavior in mixed IPv4/IPv6
- * environments. See http://b/2876927
- * TODO: add a hidden method so that Socket.tryAllAddresses can does this for us
- */
- Socket socketCandidate = null;
- InetAddress[] addresses = InetAddress.getAllByName(config.socketHost);
- for (int i = 0; i < addresses.length; i++) {
- socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP)
- ? new Socket(config.proxy)
- : new Socket();
- try {
- socketCandidate.connect(
- new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);
- break;
- } catch (IOException e) {
- if (i == addresses.length - 1) {
- throw e;
- }
- }
- }
-
- this.socket = socketCandidate;
- }
-
- public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory,
- Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException {
- /*
- * Try an explicitly-specified proxy.
- */
- if (proxy != null) {
- Address address = (proxy.type() == Proxy.Type.DIRECT)
- ? new Address(uri, sslSocketFactory)
- : new Address(uri, sslSocketFactory, proxy, requiresTunnel);
- return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
- }
-
- /*
- * Try connecting to each of the proxies provided by the ProxySelector
- * until a connection succeeds.
- */
- ProxySelector selector = ProxySelector.getDefault();
- List<Proxy> proxyList = selector.select(uri);
- if (proxyList != null) {
- for (Proxy selectedProxy : proxyList) {
- if (selectedProxy.type() == Proxy.Type.DIRECT) {
- // the same as NO_PROXY
- // TODO: if the selector recommends a direct connection, attempt that?
- continue;
- }
- try {
- Address address = new Address(uri, sslSocketFactory,
- selectedProxy, requiresTunnel);
- return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
- } catch (IOException e) {
- // failed to connect, tell it to the selector
- selector.connectFailed(uri, selectedProxy.address(), e);
- }
- }
- }
-
- /*
- * Try a direct connection. If this fails, this method will throw.
- */
- return HttpConnectionPool.INSTANCE.get(new Address(uri, sslSocketFactory), connectTimeout);
- }
-
- public void closeSocketAndStreams() {
- IoUtils.closeQuietly(sslOutputStream);
- IoUtils.closeQuietly(sslInputStream);
- IoUtils.closeQuietly(sslSocket);
- IoUtils.closeQuietly(outputStream);
- IoUtils.closeQuietly(inputStream);
- IoUtils.closeQuietly(socket);
- }
-
- public void setSoTimeout(int readTimeout) throws SocketException {
- socket.setSoTimeout(readTimeout);
- }
-
- public OutputStream getOutputStream() throws IOException {
- if (sslSocket != null) {
- if (sslOutputStream == null) {
- sslOutputStream = sslSocket.getOutputStream();
- }
- return sslOutputStream;
- } else if(outputStream == null) {
- outputStream = socket.getOutputStream();
- }
- return outputStream;
- }
-
- public InputStream getInputStream() throws IOException {
- if (sslSocket != null) {
- if (sslInputStream == null) {
- sslInputStream = sslSocket.getInputStream();
- }
- return sslInputStream;
- } else if (inputStream == null) {
- /*
- * Buffer the socket stream to permit efficient parsing of HTTP
- * headers and chunk sizes. Benchmarks suggest 128 is sufficient.
- * We cannot buffer when setting up a tunnel because we may consume
- * bytes intended for the SSL socket.
- */
- int bufferSize = 128;
- inputStream = address.requiresTunnel
- ? socket.getInputStream()
- : new BufferedInputStream(socket.getInputStream(), bufferSize);
- }
- return inputStream;
- }
-
- protected Socket getSocket() {
- return sslSocket != null ? sslSocket : socket;
- }
-
- public Address getAddress() {
- return address;
- }
-
- /**
- * Create an {@code SSLSocket} and perform the SSL handshake
- * (performing certificate validation.
- *
- * @param sslSocketFactory Source of new {@code SSLSocket} instances.
- * @param tlsTolerant If true, assume server can handle common
- * TLS extensions and SSL deflate compression. If false, use
- * an SSL3 only fallback mode without compression.
- */
- public void setupSecureSocket(SSLSocketFactory sslSocketFactory, boolean tlsTolerant)
- throws IOException {
- // create the wrapper over connected socket
- unverifiedSocket = (SSLSocket) sslSocketFactory.createSocket(socket,
- address.uriHost, address.uriPort, true /* autoClose */);
- // tlsTolerant mimics Chrome's behavior
- if (tlsTolerant && unverifiedSocket instanceof OpenSSLSocketImpl) {
- OpenSSLSocketImpl openSslSocket = (OpenSSLSocketImpl) unverifiedSocket;
- openSslSocket.setUseSessionTickets(true);
- openSslSocket.setHostname(address.uriHost);
- // use SSLSocketFactory default enabled protocols
- } else {
- unverifiedSocket.setEnabledProtocols(new String [] { "SSLv3" });
- }
- // force handshake, which can throw
- unverifiedSocket.startHandshake();
- }
-
- /**
- * Return an {@code SSLSocket} that is not only connected but has
- * also passed hostname verification.
- *
- * @param hostnameVerifier Used to verify the hostname we
- * connected to is an acceptable match for the peer certificate
- * chain of the SSLSession.
- */
- public SSLSocket verifySecureSocketHostname(HostnameVerifier hostnameVerifier)
- throws IOException {
- if (!hostnameVerifier.verify(address.uriHost, unverifiedSocket.getSession())) {
- throw new IOException("Hostname '" + address.uriHost + "' was not verified");
- }
- sslSocket = unverifiedSocket;
- return sslSocket;
- }
-
- /**
- * Return an {@code SSLSocket} if already connected, otherwise null.
- */
- public SSLSocket getSecureSocketIfConnected() {
- return sslSocket;
- }
-
- /**
- * Returns true if this connection has been used to satisfy an earlier
- * HTTP request/response pair.
- */
- public boolean isRecycled() {
- return recycled;
- }
-
- public void setRecycled() {
- this.recycled = true;
- }
-
- /**
- * Returns true if this connection is eligible to be reused for another
- * request/response pair.
- */
- protected boolean isEligibleForRecycling() {
- return !socket.isClosed()
- && !socket.isInputShutdown()
- && !socket.isOutputShutdown();
- }
-
- /**
- * This address has two parts: the address we connect to directly and the
- * origin address of the resource. These are the same unless a proxy is
- * being used. It also includes the SSL socket factory so that a socket will
- * not be reused if its SSL configuration is different.
- */
- public static final class Address {
- private final Proxy proxy;
- private final boolean requiresTunnel;
- private final String uriHost;
- private final int uriPort;
- private final String socketHost;
- private final int socketPort;
- private final SSLSocketFactory sslSocketFactory;
-
- public Address(URI uri, SSLSocketFactory sslSocketFactory) throws UnknownHostException {
- this.proxy = null;
- this.requiresTunnel = false;
- this.uriHost = uri.getHost();
- this.uriPort = uri.getEffectivePort();
- this.sslSocketFactory = sslSocketFactory;
- this.socketHost = uriHost;
- this.socketPort = uriPort;
- if (uriHost == null) {
- throw new UnknownHostException(uri.toString());
- }
- }
-
- /**
- * @param requiresTunnel true if the HTTP connection needs to tunnel one
- * protocol over another, such as when using HTTPS through an HTTP
- * proxy. When doing so, we must avoid buffering bytes intended for
- * the higher-level protocol.
- */
- public Address(URI uri, SSLSocketFactory sslSocketFactory,
- Proxy proxy, boolean requiresTunnel) throws UnknownHostException {
- this.proxy = proxy;
- this.requiresTunnel = requiresTunnel;
- this.uriHost = uri.getHost();
- this.uriPort = uri.getEffectivePort();
- this.sslSocketFactory = sslSocketFactory;
-
- SocketAddress proxyAddress = proxy.address();
- if (!(proxyAddress instanceof InetSocketAddress)) {
- throw new IllegalArgumentException("Proxy.address() is not an InetSocketAddress: "
- + proxyAddress.getClass());
- }
- InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
- this.socketHost = proxySocketAddress.getHostName();
- this.socketPort = proxySocketAddress.getPort();
- if (uriHost == null) {
- throw new UnknownHostException(uri.toString());
- }
- }
-
- public Proxy getProxy() {
- return proxy;
- }
-
- @Override public boolean equals(Object other) {
- if (other instanceof Address) {
- Address that = (Address) other;
- return Objects.equal(this.proxy, that.proxy)
- && this.uriHost.equals(that.uriHost)
- && this.uriPort == that.uriPort
- && Objects.equal(this.sslSocketFactory, that.sslSocketFactory)
- && this.requiresTunnel == that.requiresTunnel;
- }
- return false;
- }
-
- @Override public int hashCode() {
- int result = 17;
- result = 31 * result + uriHost.hashCode();
- result = 31 * result + uriPort;
- result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
- result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
- result = 31 * result + (requiresTunnel ? 1 : 0);
- return result;
- }
-
- public HttpConnection connect(int connectTimeout) throws IOException {
- return new HttpConnection(this, connectTimeout);
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpConnectionPool.java b/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
deleted file mode 100644
index 1f5f4d9..0000000
--- a/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import dalvik.system.SocketTagger;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * A pool of HTTP connections. This class exposes its tuning parameters as
- * system properties:
- * <ul>
- * <li>{@code http.keepAlive} true if HTTP connections should be pooled at
- * all. Default is true.
- * <li>{@code http.maxConnections} maximum number of connections to each URI.
- * Default is 5.
- * </ul>
- *
- * <p>This class <i>doesn't</i> adjust its configuration as system properties
- * are changed. This assumes that the applications that set these parameters do
- * so before making HTTP connections, and that this class is initialized lazily.
- */
-final class HttpConnectionPool {
-
- public static final HttpConnectionPool INSTANCE = new HttpConnectionPool();
-
- private final int maxConnections;
- private final HashMap<HttpConnection.Address, List<HttpConnection>> connectionPool
- = new HashMap<HttpConnection.Address, List<HttpConnection>>();
-
- private HttpConnectionPool() {
- String keepAlive = System.getProperty("http.keepAlive");
- if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) {
- maxConnections = 0;
- return;
- }
-
- String maxConnectionsString = System.getProperty("http.maxConnections");
- this.maxConnections = maxConnectionsString != null
- ? Integer.parseInt(maxConnectionsString)
- : 5;
- }
-
- public HttpConnection get(HttpConnection.Address address, int connectTimeout)
- throws IOException {
- // First try to reuse an existing HTTP connection.
- synchronized (connectionPool) {
- List<HttpConnection> connections = connectionPool.get(address);
- while (connections != null) {
- HttpConnection connection = connections.remove(connections.size() - 1);
- if (connections.isEmpty()) {
- connectionPool.remove(address);
- connections = null;
- }
- if (connection.isEligibleForRecycling()) {
- // Since Socket is recycled, re-tag before using
- Socket socket = connection.getSocket();
- SocketTagger.get().tag(socket);
- return connection;
- }
- }
- }
-
- /*
- * We couldn't find a reusable connection, so we need to create a new
- * connection. We're careful not to do so while holding a lock!
- */
- return address.connect(connectTimeout);
- }
-
- public void recycle(HttpConnection connection) {
- Socket socket = connection.getSocket();
- try {
- SocketTagger.get().untag(socket);
- } catch (SocketException e) {
- // When unable to remove tagging, skip recycling and close
- System.logW("Unable to untagSocket(): " + e);
- connection.closeSocketAndStreams();
- return;
- }
-
- if (maxConnections > 0 && connection.isEligibleForRecycling()) {
- HttpConnection.Address address = connection.getAddress();
- synchronized (connectionPool) {
- List<HttpConnection> connections = connectionPool.get(address);
- if (connections == null) {
- connections = new ArrayList<HttpConnection>();
- connectionPool.put(address, connections);
- }
- if (connections.size() < maxConnections) {
- connection.setRecycled();
- connections.add(connection);
- return; // keep the connection open
- }
- }
- }
-
- // don't close streams while holding a lock!
- connection.closeSocketAndStreams();
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java
deleted file mode 100644
index 8d81c38..0000000
--- a/luni/src/main/java/libcore/net/http/HttpEngine.java
+++ /dev/null
@@ -1,852 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.CookieHandler;
-import java.net.ExtendedResponseCache;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.ResponseCache;
-import java.net.ResponseSource;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.Charsets;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.zip.GZIPInputStream;
-import javax.net.ssl.SSLSocketFactory;
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-import libcore.util.EmptyArray;
-
-/**
- * Handles a single HTTP request/response pair. Each HTTP engine follows this
- * lifecycle:
- * <ol>
- * <li>It is created.
- * <li>The HTTP request message is sent with sendRequest(). Once the request
- * is sent it is an error to modify the request headers. After
- * sendRequest() has been called the request body can be written to if
- * it exists.
- * <li>The HTTP response message is read with readResponse(). After the
- * response has been read the response headers and body can be read.
- * All responses have a response body input stream, though in some
- * instances this stream is empty.
- * </ol>
- *
- * <p>The request and response may be served by the HTTP response cache, by the
- * network, or by both in the event of a conditional GET.
- *
- * <p>This class may hold a socket connection that needs to be released or
- * recycled. By default, this socket connection is held when the last byte of
- * the response is consumed. To release the connection when it is no longer
- * required, use {@link #automaticallyReleaseConnectionToPool()}.
- */
-public class HttpEngine {
- private static final CacheResponse GATEWAY_TIMEOUT_RESPONSE = new CacheResponse() {
- @Override public Map<String, List<String>> getHeaders() throws IOException {
- Map<String, List<String>> result = new HashMap<String, List<String>>();
- result.put(null, Collections.singletonList("HTTP/1.1 504 Gateway Timeout"));
- return result;
- }
- @Override public InputStream getBody() throws IOException {
- return new ByteArrayInputStream(EmptyArray.BYTE);
- }
- };
-
- /**
- * The maximum number of bytes to buffer when sending headers and a request
- * body. When the headers and body can be sent in a single write, the
- * request completes sooner. In one WiFi benchmark, using a large enough
- * buffer sped up some uploads by half.
- */
- private static final int MAX_REQUEST_BUFFER_LENGTH = 32768;
-
- public static final int DEFAULT_CHUNK_LENGTH = 1024;
-
- public static final String OPTIONS = "OPTIONS";
- public static final String GET = "GET";
- public static final String HEAD = "HEAD";
- public static final String POST = "POST";
- public static final String PUT = "PUT";
- public static final String DELETE = "DELETE";
- public static final String TRACE = "TRACE";
- public static final String CONNECT = "CONNECT";
-
- public static final int HTTP_CONTINUE = 100;
-
- /**
- * HTTP 1.1 doesn't specify how many redirects to follow, but HTTP/1.0
- * recommended 5. http://www.w3.org/Protocols/HTTP/1.0/spec.html#Code3xx
- */
- public static final int MAX_REDIRECTS = 5;
-
- protected final HttpURLConnectionImpl policy;
-
- protected final String method;
-
- private ResponseSource responseSource;
-
- protected HttpConnection connection;
- private InputStream socketIn;
- private OutputStream socketOut;
-
- /**
- * This stream buffers the request headers and the request body when their
- * combined size is less than MAX_REQUEST_BUFFER_LENGTH. By combining them
- * we can save socket writes, which in turn saves a packet transmission.
- * This is socketOut if the request size is large or unknown.
- */
- private OutputStream requestOut;
- private AbstractHttpOutputStream requestBodyOut;
-
- private InputStream responseBodyIn;
-
- private final ResponseCache responseCache = ResponseCache.getDefault();
- private CacheResponse cacheResponse;
- private CacheRequest cacheRequest;
-
- /** The time when the request headers were written, or -1 if they haven't been written yet. */
- private long sentRequestMillis = -1;
-
- /**
- * True if this client added an "Accept-Encoding: gzip" header field and is
- * therefore responsible for also decompressing the transfer stream.
- */
- private boolean transparentGzip;
-
- boolean sendChunked;
-
- /**
- * The version this client will use. Either 0 for HTTP/1.0, or 1 for
- * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client
- * automatically sets its version to HTTP/1.0.
- */
- // TODO: is HTTP minor version tracked across HttpEngines?
- private int httpMinorVersion = 1; // Assume HTTP/1.1
-
- private final URI uri;
-
- private final RequestHeaders requestHeaders;
-
- /** Null until a response is received from the network or the cache */
- private ResponseHeaders responseHeaders;
-
- /*
- * The cache response currently being validated on a conditional get. Null
- * if the cached response doesn't exist or doesn't need validation. If the
- * conditional get succeeds, these will be used for the response headers and
- * body. If it fails, these be closed and set to null.
- */
- private ResponseHeaders cachedResponseHeaders;
- private InputStream cachedResponseBody;
-
- /**
- * True if the socket connection should be released to the connection pool
- * when the response has been fully read.
- */
- private boolean automaticallyReleaseConnectionToPool;
-
- /** True if the socket connection is no longer needed by this engine. */
- private boolean connectionReleased;
-
- /**
- * @param requestHeaders the client's supplied request headers. This class
- * creates a private copy that it can mutate.
- * @param connection the connection used for an intermediate response
- * immediately prior to this request/response pair, such as a same-host
- * redirect. This engine assumes ownership of the connection and must
- * release it when it is unneeded.
- */
- public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
- HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException {
- this.policy = policy;
- this.method = method;
- this.connection = connection;
- this.requestBodyOut = requestBodyOut;
-
- try {
- uri = policy.getURL().toURILenient();
- } catch (URISyntaxException e) {
- throw new IOException(e);
- }
-
- this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
- }
-
- public URI getUri() {
- return uri;
- }
-
- /**
- * Figures out what the response source will be, and opens a socket to that
- * source if necessary. Prepares the request headers and gets ready to start
- * writing the request body if it exists.
- */
- public final void sendRequest() throws IOException {
- if (responseSource != null) {
- return;
- }
-
- prepareRawRequestHeaders();
- initResponseSource();
- if (responseCache instanceof ExtendedResponseCache) {
- ((ExtendedResponseCache) responseCache).trackResponse(responseSource);
- }
-
- /*
- * The raw response source may require the network, but the request
- * headers may forbid network use. In that case, dispose of the network
- * response and use a GATEWAY_TIMEOUT response instead, as specified
- * by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
- */
- if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
- if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
- IoUtils.closeQuietly(cachedResponseBody);
- }
- this.responseSource = ResponseSource.CACHE;
- this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
- RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders());
- setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
- }
-
- if (responseSource.requiresConnection()) {
- sendSocketRequest();
- } else if (connection != null) {
- HttpConnectionPool.INSTANCE.recycle(connection);
- connection = null;
- }
- }
-
- /**
- * Initialize the source for this response. It may be corrected later if the
- * request headers forbids network use.
- */
- private void initResponseSource() throws IOException {
- responseSource = ResponseSource.NETWORK;
- if (!policy.getUseCaches() || responseCache == null) {
- return;
- }
-
- CacheResponse candidate = responseCache.get(uri, method,
- requestHeaders.getHeaders().toMultimap());
- if (candidate == null) {
- return;
- }
-
- Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
- cachedResponseBody = candidate.getBody();
- if (!acceptCacheResponseType(candidate)
- || responseHeadersMap == null
- || cachedResponseBody == null) {
- IoUtils.closeQuietly(cachedResponseBody);
- return;
- }
-
- RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap);
- cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
- long now = System.currentTimeMillis();
- this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
- if (responseSource == ResponseSource.CACHE) {
- this.cacheResponse = candidate;
- setResponse(cachedResponseHeaders, cachedResponseBody);
- } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
- this.cacheResponse = candidate;
- } else if (responseSource == ResponseSource.NETWORK) {
- IoUtils.closeQuietly(cachedResponseBody);
- } else {
- throw new AssertionError();
- }
- }
-
- private void sendSocketRequest() throws IOException {
- if (connection == null) {
- connect();
- }
-
- if (socketOut != null || requestOut != null || socketIn != null) {
- throw new IllegalStateException();
- }
-
- socketOut = connection.getOutputStream();
- requestOut = socketOut;
- socketIn = connection.getInputStream();
-
- if (hasRequestBody()) {
- initRequestBodyOut();
- }
- }
-
- /**
- * Connect to the origin server either directly or via a proxy.
- */
- protected void connect() throws IOException {
- if (connection == null) {
- connection = openSocketConnection();
- }
- }
-
- protected final HttpConnection openSocketConnection() throws IOException {
- HttpConnection result = HttpConnection.connect(uri, getSslSocketFactory(),
- policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());
- Proxy proxy = result.getAddress().getProxy();
- if (proxy != null) {
- policy.setProxy(proxy);
- }
- result.setSoTimeout(policy.getReadTimeout());
- return result;
- }
-
- protected void initRequestBodyOut() throws IOException {
- int chunkLength = policy.getChunkLength();
- if (chunkLength > 0 || requestHeaders.isChunked()) {
- sendChunked = true;
- if (chunkLength == -1) {
- chunkLength = DEFAULT_CHUNK_LENGTH;
- }
- }
-
- if (socketOut == null) {
- throw new IllegalStateException("No socket to write to; was a POST cached?");
- }
-
- if (httpMinorVersion == 0) {
- sendChunked = false;
- }
-
- int fixedContentLength = policy.getFixedContentLength();
- if (requestBodyOut != null) {
- // request body was already initialized by the predecessor HTTP engine
- } else if (fixedContentLength != -1) {
- writeRequestHeaders(fixedContentLength);
- requestBodyOut = new FixedLengthOutputStream(requestOut, fixedContentLength);
- } else if (sendChunked) {
- writeRequestHeaders(-1);
- requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength);
- } else if (requestHeaders.getContentLength() != -1) {
- writeRequestHeaders(requestHeaders.getContentLength());
- requestBodyOut = new RetryableOutputStream(requestHeaders.getContentLength());
- } else {
- requestBodyOut = new RetryableOutputStream();
- }
- }
-
- /**
- * @param body the response body, or null if it doesn't exist or isn't
- * available.
- */
- private void setResponse(ResponseHeaders headers, InputStream body) throws IOException {
- if (this.responseBodyIn != null) {
- throw new IllegalStateException();
- }
- this.responseHeaders = headers;
- this.httpMinorVersion = responseHeaders.getHeaders().getHttpMinorVersion();
- if (body != null) {
- initContentStream(body);
- }
- }
-
- private boolean hasRequestBody() {
- return method == POST || method == PUT;
- }
-
- /**
- * Returns the request body or null if this request doesn't have a body.
- */
- public final OutputStream getRequestBody() {
- if (responseSource == null) {
- throw new IllegalStateException();
- }
- return requestBodyOut;
- }
-
- public final boolean hasResponse() {
- return responseHeaders != null;
- }
-
- public final RequestHeaders getRequestHeaders() {
- return requestHeaders;
- }
-
- public final ResponseHeaders getResponseHeaders() {
- if (responseHeaders == null) {
- throw new IllegalStateException();
- }
- return responseHeaders;
- }
-
- public final int getResponseCode() {
- if (responseHeaders == null) {
- throw new IllegalStateException();
- }
- return responseHeaders.getHeaders().getResponseCode();
- }
-
- public final InputStream getResponseBody() {
- if (responseHeaders == null) {
- throw new IllegalStateException();
- }
- return responseBodyIn;
- }
-
- public final CacheResponse getCacheResponse() {
- return cacheResponse;
- }
-
- public final HttpConnection getConnection() {
- return connection;
- }
-
- public final boolean hasRecycledConnection() {
- return connection != null && connection.isRecycled();
- }
-
- /**
- * Returns true if {@code cacheResponse} is of the right type. This
- * condition is necessary but not sufficient for the cached response to
- * be used.
- */
- protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
- return true;
- }
-
- private void maybeCache() throws IOException {
- // Never cache responses to proxy CONNECT requests.
- if (method == CONNECT) {
- return;
- }
-
- // Are we caching at all?
- if (!policy.getUseCaches() || responseCache == null) {
- return;
- }
-
- // Should we cache this response for this request?
- if (!responseHeaders.isCacheable(requestHeaders)) {
- return;
- }
-
- // Offer this request to the cache.
- cacheRequest = responseCache.put(uri, getHttpConnectionToCache());
- }
-
- protected HttpURLConnection getHttpConnectionToCache() {
- return policy;
- }
-
- /**
- * Cause the socket connection to be released to the connection pool when
- * it is no longer needed. If it is already unneeded, it will be pooled
- * immediately.
- */
- public final void automaticallyReleaseConnectionToPool() {
- automaticallyReleaseConnectionToPool = true;
- if (connection != null && connectionReleased) {
- HttpConnectionPool.INSTANCE.recycle(connection);
- connection = null;
- }
- }
-
- public final void markConnectionAsRecycled() {
- if (connection != null) {
- connection.setRecycled();
- }
- }
-
- /**
- * Releases this engine so that its resources may be either reused or
- * closed.
- */
- public final void release(boolean reusable) {
- // If the response body comes from the cache, close it.
- if (responseBodyIn == cachedResponseBody) {
- IoUtils.closeQuietly(responseBodyIn);
- }
-
- if (!connectionReleased && connection != null) {
- connectionReleased = true;
-
- // We cannot reuse sockets that have incomplete output.
- if (requestBodyOut != null && !requestBodyOut.closed) {
- reusable = false;
- }
-
- // If the request specified that the connection shouldn't be reused,
- // don't reuse it. This advice doesn't apply to CONNECT requests because
- // the "Connection: close" header goes the origin server, not the proxy.
- if (requestHeaders.hasConnectionClose() && method != CONNECT) {
- reusable = false;
- }
-
- // If the response specified that the connection shouldn't be reused, don't reuse it.
- if (responseHeaders != null && responseHeaders.hasConnectionClose()) {
- reusable = false;
- }
-
- if (responseBodyIn instanceof UnknownLengthHttpInputStream) {
- reusable = false;
- }
-
- if (reusable && responseBodyIn != null) {
- // We must discard the response body before the connection can be reused.
- try {
- Streams.skipAll(responseBodyIn);
- } catch (IOException e) {
- reusable = false;
- }
- }
-
- if (!reusable) {
- connection.closeSocketAndStreams();
- connection = null;
- } else if (automaticallyReleaseConnectionToPool) {
- HttpConnectionPool.INSTANCE.recycle(connection);
- connection = null;
- }
- }
- }
-
- private void initContentStream(InputStream transferStream) throws IOException {
- if (transparentGzip && responseHeaders.isContentEncodingGzip()) {
- /*
- * If the response was transparently gzipped, remove the gzip header field
- * so clients don't double decompress. http://b/3009828
- *
- * Also remove the Content-Length in this case because it contains the length
- * of the gzipped response. This isn't terribly useful and is dangerous because
- * clients can query the content length, but not the content encoding.
- */
- responseHeaders.stripContentEncoding();
- responseHeaders.stripContentLength();
- responseBodyIn = new GZIPInputStream(transferStream);
- } else {
- responseBodyIn = transferStream;
- }
- }
-
- private InputStream getTransferStream() throws IOException {
- if (!hasResponseBody()) {
- return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
- }
-
- if (responseHeaders.isChunked()) {
- return new ChunkedInputStream(socketIn, cacheRequest, this);
- }
-
- if (responseHeaders.getContentLength() != -1) {
- return new FixedLengthInputStream(socketIn, cacheRequest, this,
- responseHeaders.getContentLength());
- }
-
- /*
- * Wrap the input stream from the HttpConnection (rather than
- * just returning "socketIn" directly here), so that we can control
- * its use after the reference escapes.
- */
- return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this);
- }
-
- private void readResponseHeaders() throws IOException {
- RawHeaders headers;
- do {
- headers = new RawHeaders();
- headers.setStatusLine(Streams.readAsciiLine(socketIn));
- readHeaders(headers);
- } while (headers.getResponseCode() == HTTP_CONTINUE);
- setResponse(new ResponseHeaders(uri, headers), null);
- }
-
- /**
- * Returns true if the response must have a (possibly 0-length) body.
- * See RFC 2616 section 4.3.
- */
- public final boolean hasResponseBody() {
- int responseCode = responseHeaders.getHeaders().getResponseCode();
-
- // HEAD requests never yield a body regardless of the response headers.
- if (method == HEAD) {
- return false;
- }
-
- if (method != CONNECT
- && (responseCode < HTTP_CONTINUE || responseCode >= 200)
- && responseCode != HttpURLConnectionImpl.HTTP_NO_CONTENT
- && responseCode != HttpURLConnectionImpl.HTTP_NOT_MODIFIED) {
- return true;
- }
-
- /*
- * If the Content-Length or Transfer-Encoding headers disagree with the
- * response code, the response is malformed. For best compatibility, we
- * honor the headers.
- */
- if (responseHeaders.getContentLength() != -1 || responseHeaders.isChunked()) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Trailers are headers included after the last chunk of a response encoded
- * with chunked encoding.
- */
- final void readTrailers() throws IOException {
- readHeaders(responseHeaders.getHeaders());
- }
-
- private void readHeaders(RawHeaders headers) throws IOException {
- // parse the result headers until the first blank line
- String line;
- while (!(line = Streams.readAsciiLine(socketIn)).isEmpty()) {
- headers.addLine(line);
- }
-
- CookieHandler cookieHandler = CookieHandler.getDefault();
- if (cookieHandler != null) {
- cookieHandler.put(uri, headers.toMultimap());
- }
- }
-
- /**
- * Prepares the HTTP headers and sends them to the server.
- *
- * <p>For streaming requests with a body, headers must be prepared
- * <strong>before</strong> the output stream has been written to. Otherwise
- * the body would need to be buffered!
- *
- * <p>For non-streaming requests with a body, headers must be prepared
- * <strong>after</strong> the output stream has been written to and closed.
- * This ensures that the {@code Content-Length} header field receives the
- * proper value.
- *
- * @param contentLength the number of bytes in the request body, or -1 if
- * the request body length is unknown.
- */
- private void writeRequestHeaders(int contentLength) throws IOException {
- if (sentRequestMillis != -1) {
- throw new IllegalStateException();
- }
-
- RawHeaders headersToSend = getNetworkRequestHeaders();
- byte[] bytes = headersToSend.toHeaderString().getBytes(Charsets.ISO_8859_1);
-
- if (contentLength != -1 && bytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) {
- requestOut = new BufferedOutputStream(socketOut, bytes.length + contentLength);
- }
-
- sentRequestMillis = System.currentTimeMillis();
- requestOut.write(bytes);
- }
-
- /**
- * Returns the headers to send on a network request.
- *
- * <p>This adds the content length and content-type headers, which are
- * neither needed nor known when querying the response cache.
- *
- * <p>It updates the status line, which may need to be fully qualified if
- * the connection is using a proxy.
- */
- protected RawHeaders getNetworkRequestHeaders() throws IOException {
- requestHeaders.getHeaders().setStatusLine(getRequestLine());
-
- int fixedContentLength = policy.getFixedContentLength();
- if (fixedContentLength != -1) {
- requestHeaders.setContentLength(fixedContentLength);
- } else if (sendChunked) {
- requestHeaders.setChunked();
- } else if (requestBodyOut instanceof RetryableOutputStream) {
- int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength();
- requestHeaders.setContentLength(contentLength);
- }
-
- return requestHeaders.getHeaders();
- }
-
- /**
- * Populates requestHeaders with defaults and cookies.
- *
- * <p>This client doesn't specify a default {@code Accept} header because it
- * doesn't know what content types the application is interested in.
- */
- private void prepareRawRequestHeaders() throws IOException {
- requestHeaders.getHeaders().setStatusLine(getRequestLine());
-
- if (requestHeaders.getUserAgent() == null) {
- requestHeaders.setUserAgent(getDefaultUserAgent());
- }
-
- if (requestHeaders.getHost() == null) {
- requestHeaders.setHost(getOriginAddress(policy.getURL()));
- }
-
- if (httpMinorVersion > 0 && requestHeaders.getConnection() == null) {
- requestHeaders.setConnection("Keep-Alive");
- }
-
- if (requestHeaders.getAcceptEncoding() == null) {
- transparentGzip = true;
- requestHeaders.setAcceptEncoding("gzip");
- }
-
- if (hasRequestBody() && requestHeaders.getContentType() == null) {
- requestHeaders.setContentType("application/x-www-form-urlencoded");
- }
-
- long ifModifiedSince = policy.getIfModifiedSince();
- if (ifModifiedSince != 0) {
- requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
- }
-
- CookieHandler cookieHandler = CookieHandler.getDefault();
- if (cookieHandler != null) {
- requestHeaders.addCookies(
- cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap()));
- }
- }
-
- private String getRequestLine() {
- String protocol = (httpMinorVersion == 0) ? "HTTP/1.0" : "HTTP/1.1";
- return method + " " + requestString() + " " + protocol;
- }
-
- private String requestString() {
- URL url = policy.getURL();
- if (includeAuthorityInRequestLine()) {
- return url.toString();
- } else {
- String fileOnly = url.getFile();
- if (fileOnly == null) {
- fileOnly = "/";
- } else if (!fileOnly.startsWith("/")) {
- fileOnly = "/" + fileOnly;
- }
- return fileOnly;
- }
- }
-
- /**
- * Returns true if the request line should contain the full URL with host
- * and port (like "GET http://android.com/foo HTTP/1.1") or only the path
- * (like "GET /foo HTTP/1.1").
- *
- * <p>This is non-final because for HTTPS it's never necessary to supply the
- * full URL, even if a proxy is in use.
- */
- protected boolean includeAuthorityInRequestLine() {
- return policy.usingProxy();
- }
-
- /**
- * Returns the SSL configuration for connections created by this engine.
- * We cannot reuse HTTPS connections if the socket factory has changed.
- */
- protected SSLSocketFactory getSslSocketFactory() {
- return null;
- }
-
- protected final String getDefaultUserAgent() {
- String agent = System.getProperty("http.agent");
- return agent != null ? agent : ("Java" + System.getProperty("java.version"));
- }
-
- protected final String getOriginAddress(URL url) {
- int port = url.getPort();
- String result = url.getHost();
- if (port > 0 && port != policy.getDefaultPort()) {
- result = result + ":" + port;
- }
- return result;
- }
-
- protected boolean requiresTunnel() {
- return false;
- }
-
- /**
- * Flushes the remaining request header and body, parses the HTTP response
- * headers and starts reading the HTTP response body if it exists.
- */
- public final void readResponse() throws IOException {
- if (hasResponse()) {
- return;
- }
-
- if (responseSource == null) {
- throw new IllegalStateException("readResponse() without sendRequest()");
- }
-
- if (!responseSource.requiresConnection()) {
- return;
- }
-
- if (sentRequestMillis == -1) {
- int contentLength = requestBodyOut instanceof RetryableOutputStream
- ? ((RetryableOutputStream) requestBodyOut).contentLength()
- : -1;
- writeRequestHeaders(contentLength);
- }
-
- if (requestBodyOut != null) {
- requestBodyOut.close();
- if (requestBodyOut instanceof RetryableOutputStream) {
- ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut);
- }
- }
-
- requestOut.flush();
- requestOut = socketOut;
-
- readResponseHeaders();
- responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());
-
- if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
- if (cachedResponseHeaders.validate(responseHeaders)) {
- release(true);
- ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
- setResponse(combinedHeaders, cachedResponseBody);
- if (responseCache instanceof ExtendedResponseCache) {
- ExtendedResponseCache httpResponseCache = (ExtendedResponseCache) responseCache;
- httpResponseCache.trackConditionalCacheHit();
- httpResponseCache.update(cacheResponse, getHttpConnectionToCache());
- }
- return;
- } else {
- IoUtils.closeQuietly(cachedResponseBody);
- }
- }
-
- if (hasResponseBody()) {
- maybeCache(); // reentrant. this calls into user code which may call back into this!
- }
-
- initContentStream(getTransferStream());
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpHandler.java b/luni/src/main/java/libcore/net/http/HttpHandler.java
deleted file mode 100644
index e168f42..0000000
--- a/luni/src/main/java/libcore/net/http/HttpHandler.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.io.IOException;
-import java.net.Proxy;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-public final class HttpHandler extends URLStreamHandler {
-
- @Override protected URLConnection openConnection(URL u) throws IOException {
- return new HttpURLConnectionImpl(u, getDefaultPort());
- }
-
- @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
- if (url == null || proxy == null) {
- throw new IllegalArgumentException("url == null || proxy == null");
- }
- return new HttpURLConnectionImpl(url, getDefaultPort(), proxy);
- }
-
- @Override protected int getDefaultPort() {
- return 80;
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpResponseCache.java b/luni/src/main/java/libcore/net/http/HttpResponseCache.java
deleted file mode 100644
index 1a9dfd1..0000000
--- a/luni/src/main/java/libcore/net/http/HttpResponseCache.java
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FilterInputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.ExtendedResponseCache;
-import java.net.HttpURLConnection;
-import java.net.ResponseCache;
-import java.net.ResponseSource;
-import java.net.SecureCacheResponse;
-import java.net.URI;
-import java.net.URLConnection;
-import java.nio.charset.Charsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import libcore.io.Base64;
-import libcore.io.DiskLruCache;
-import libcore.io.IoUtils;
-import libcore.io.StrictLineReader;
-
-/**
- * Cache responses in a directory on the file system. Most clients should use
- * {@code android.net.HttpResponseCache}, the stable, documented front end for
- * this.
- */
-public final class HttpResponseCache extends ResponseCache implements ExtendedResponseCache {
- // TODO: add APIs to iterate the cache?
- private static final int VERSION = 201105;
- private static final int ENTRY_METADATA = 0;
- private static final int ENTRY_BODY = 1;
- private static final int ENTRY_COUNT = 2;
-
- private final DiskLruCache cache;
-
- /* read and write statistics, all guarded by 'this' */
- private int writeSuccessCount;
- private int writeAbortCount;
- private int networkCount;
- private int hitCount;
- private int requestCount;
-
- public HttpResponseCache(File directory, long maxSize) throws IOException {
- cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
- }
-
- private String uriToKey(URI uri) {
- try {
- MessageDigest messageDigest = MessageDigest.getInstance("MD5");
- byte[] md5bytes = messageDigest.digest(uri.toString().getBytes(Charsets.UTF_8));
- return IntegralToString.bytesToHexString(md5bytes, false);
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError(e);
- }
- }
-
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) {
- String key = uriToKey(uri);
- DiskLruCache.Snapshot snapshot;
- Entry entry;
- try {
- snapshot = cache.get(key);
- if (snapshot == null) {
- return null;
- }
- entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
- } catch (IOException e) {
- // Give up because the cache cannot be read.
- return null;
- }
-
- if (!entry.matches(uri, requestMethod, requestHeaders)) {
- snapshot.close();
- return null;
- }
-
- return entry.isHttps()
- ? new EntrySecureCacheResponse(entry, snapshot)
- : new EntryCacheResponse(entry, snapshot);
- }
-
- @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
- if (!(urlConnection instanceof HttpURLConnection)) {
- return null;
- }
-
- HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
- String requestMethod = httpConnection.getRequestMethod();
- String key = uriToKey(uri);
-
- if (requestMethod.equals(HttpEngine.POST)
- || requestMethod.equals(HttpEngine.PUT)
- || requestMethod.equals(HttpEngine.DELETE)) {
- try {
- cache.remove(key);
- } catch (IOException ignored) {
- // The cache cannot be written.
- }
- return null;
- } else if (!requestMethod.equals(HttpEngine.GET)) {
- /*
- * Don't cache non-GET responses. We're technically allowed to cache
- * HEAD requests and some POST requests, but the complexity of doing
- * so is high and the benefit is low.
- */
- return null;
- }
-
- HttpEngine httpEngine = getHttpEngine(httpConnection);
- if (httpEngine == null) {
- // Don't cache unless the HTTP implementation is ours.
- return null;
- }
-
- ResponseHeaders response = httpEngine.getResponseHeaders();
- if (response.hasVaryAll()) {
- return null;
- }
-
- RawHeaders varyHeaders = httpEngine.getRequestHeaders().getHeaders().getAll(
- response.getVaryFields());
- Entry entry = new Entry(uri, varyHeaders, httpConnection);
- DiskLruCache.Editor editor = null;
- try {
- editor = cache.edit(key);
- if (editor == null) {
- return null;
- }
- entry.writeTo(editor);
- return new CacheRequestImpl(editor);
- } catch (IOException e) {
- abortQuietly(editor);
- return null;
- }
- }
-
- /**
- * Handles a conditional request hit by updating the stored cache response
- * with the headers from {@code httpConnection}. The cached response body is
- * not updated. If the stored response has changed since {@code
- * conditionalCacheHit} was returned, this does nothing.
- */
- public void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) {
- HttpEngine httpEngine = getHttpEngine(httpConnection);
- URI uri = httpEngine.getUri();
- ResponseHeaders response = httpEngine.getResponseHeaders();
- RawHeaders varyHeaders = httpEngine.getRequestHeaders().getHeaders()
- .getAll(response.getVaryFields());
- Entry entry = new Entry(uri, varyHeaders, httpConnection);
- DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse)
- ? ((EntryCacheResponse) conditionalCacheHit).snapshot
- : ((EntrySecureCacheResponse) conditionalCacheHit).snapshot;
- DiskLruCache.Editor editor = null;
- try {
- editor = snapshot.edit(); // returns null if snapshot is not current
- if (editor != null) {
- entry.writeTo(editor);
- editor.commit();
- }
- } catch (IOException e) {
- abortQuietly(editor);
- }
- }
-
- private void abortQuietly(DiskLruCache.Editor editor) {
- // Give up because the cache cannot be written.
- try {
- if (editor != null) {
- editor.abort();
- }
- } catch (IOException ignored) {
- }
- }
-
- private HttpEngine getHttpEngine(HttpURLConnection httpConnection) {
- if (httpConnection instanceof HttpURLConnectionImpl) {
- return ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
- } else if (httpConnection instanceof HttpsURLConnectionImpl) {
- return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine();
- } else {
- return null;
- }
- }
-
- public DiskLruCache getCache() {
- return cache;
- }
-
- public synchronized int getWriteAbortCount() {
- return writeAbortCount;
- }
-
- public synchronized int getWriteSuccessCount() {
- return writeSuccessCount;
- }
-
- public synchronized void trackResponse(ResponseSource source) {
- requestCount++;
-
- switch (source) {
- case CACHE:
- hitCount++;
- break;
- case CONDITIONAL_CACHE:
- case NETWORK:
- networkCount++;
- break;
- }
- }
-
- public synchronized void trackConditionalCacheHit() {
- hitCount++;
- }
-
- public synchronized int getNetworkCount() {
- return networkCount;
- }
-
- public synchronized int getHitCount() {
- return hitCount;
- }
-
- public synchronized int getRequestCount() {
- return requestCount;
- }
-
- private final class CacheRequestImpl extends CacheRequest {
- private final DiskLruCache.Editor editor;
- private OutputStream cacheOut;
- private boolean done;
- private OutputStream body;
-
- public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
- this.editor = editor;
- this.cacheOut = editor.newOutputStream(ENTRY_BODY);
- this.body = new FilterOutputStream(cacheOut) {
- @Override public void close() throws IOException {
- synchronized (HttpResponseCache.this) {
- if (done) {
- return;
- }
- done = true;
- writeSuccessCount++;
- }
- super.close();
- editor.commit();
- }
-
- @Override
- public void write(byte[] buffer, int offset, int length) throws IOException {
- // Since we don't override "write(int oneByte)", we can write directly to "out"
- // and avoid the inefficient implementation from the FilterOutputStream.
- out.write(buffer, offset, length);
- }
- };
- }
-
- @Override public void abort() {
- synchronized (HttpResponseCache.this) {
- if (done) {
- return;
- }
- done = true;
- writeAbortCount++;
- }
- IoUtils.closeQuietly(cacheOut);
- try {
- editor.abort();
- } catch (IOException ignored) {
- }
- }
-
- @Override public OutputStream getBody() throws IOException {
- return body;
- }
- }
-
- private static final class Entry {
- private final String uri;
- private final RawHeaders varyHeaders;
- private final String requestMethod;
- private final RawHeaders responseHeaders;
- private final String cipherSuite;
- private final Certificate[] peerCertificates;
- private final Certificate[] localCertificates;
-
- /*
- * Reads an entry from an input stream. A typical entry looks like this:
- * http://google.com/foo
- * GET
- * 2
- * Accept-Language: fr-CA
- * Accept-Charset: UTF-8
- * HTTP/1.1 200 OK
- * 3
- * Content-Type: image/png
- * Content-Length: 100
- * Cache-Control: max-age=600
- *
- * A typical HTTPS file looks like this:
- * https://google.com/foo
- * GET
- * 2
- * Accept-Language: fr-CA
- * Accept-Charset: UTF-8
- * HTTP/1.1 200 OK
- * 3
- * Content-Type: image/png
- * Content-Length: 100
- * Cache-Control: max-age=600
- *
- * AES_256_WITH_MD5
- * 2
- * base64-encoded peerCertificate[0]
- * base64-encoded peerCertificate[1]
- * -1
- *
- * The file is newline separated. The first two lines are the URL and
- * the request method. Next is the number of HTTP Vary request header
- * lines, followed by those lines.
- *
- * Next is the response status line, followed by the number of HTTP
- * response header lines, followed by those lines.
- *
- * HTTPS responses also contain SSL session information. This begins
- * with a blank line, and then a line containing the cipher suite. Next
- * is the length of the peer certificate chain. These certificates are
- * base64-encoded and appear each on their own line. The next line
- * contains the length of the local certificate chain. These
- * certificates are also base64-encoded and appear each on their own
- * line. A length of -1 is used to encode a null array.
- */
- public Entry(InputStream in) throws IOException {
- try {
- StrictLineReader reader = new StrictLineReader(in, Charsets.US_ASCII);
- uri = reader.readLine();
- requestMethod = reader.readLine();
- varyHeaders = new RawHeaders();
- int varyRequestHeaderLineCount = reader.readInt();
- for (int i = 0; i < varyRequestHeaderLineCount; i++) {
- varyHeaders.addLine(reader.readLine());
- }
-
- responseHeaders = new RawHeaders();
- responseHeaders.setStatusLine(reader.readLine());
- int responseHeaderLineCount = reader.readInt();
- for (int i = 0; i < responseHeaderLineCount; i++) {
- responseHeaders.addLine(reader.readLine());
- }
-
- if (isHttps()) {
- String blank = reader.readLine();
- if (!blank.isEmpty()) {
- throw new IOException("expected \"\" but was \"" + blank + "\"");
- }
- cipherSuite = reader.readLine();
- peerCertificates = readCertArray(reader);
- localCertificates = readCertArray(reader);
- } else {
- cipherSuite = null;
- peerCertificates = null;
- localCertificates = null;
- }
- } finally {
- in.close();
- }
- }
-
- public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) {
- this.uri = uri.toString();
- this.varyHeaders = varyHeaders;
- this.requestMethod = httpConnection.getRequestMethod();
- this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields());
-
- if (isHttps()) {
- HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
- cipherSuite = httpsConnection.getCipherSuite();
- Certificate[] peerCertificatesNonFinal = null;
- try {
- peerCertificatesNonFinal = httpsConnection.getServerCertificates();
- } catch (SSLPeerUnverifiedException ignored) {
- }
- peerCertificates = peerCertificatesNonFinal;
- localCertificates = httpsConnection.getLocalCertificates();
- } else {
- cipherSuite = null;
- peerCertificates = null;
- localCertificates = null;
- }
- }
-
- public void writeTo(DiskLruCache.Editor editor) throws IOException {
- OutputStream out = editor.newOutputStream(ENTRY_METADATA);
- Writer writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.UTF_8));
-
- writer.write(uri + '\n');
- writer.write(requestMethod + '\n');
- writer.write(Integer.toString(varyHeaders.length()) + '\n');
- for (int i = 0; i < varyHeaders.length(); i++) {
- writer.write(varyHeaders.getFieldName(i) + ": "
- + varyHeaders.getValue(i) + '\n');
- }
-
- writer.write(responseHeaders.getStatusLine() + '\n');
- writer.write(Integer.toString(responseHeaders.length()) + '\n');
- for (int i = 0; i < responseHeaders.length(); i++) {
- writer.write(responseHeaders.getFieldName(i) + ": "
- + responseHeaders.getValue(i) + '\n');
- }
-
- if (isHttps()) {
- writer.write('\n');
- writer.write(cipherSuite + '\n');
- writeCertArray(writer, peerCertificates);
- writeCertArray(writer, localCertificates);
- }
- writer.close();
- }
-
- private boolean isHttps() {
- return uri.startsWith("https://");
- }
-
- private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
- int length = reader.readInt();
- if (length == -1) {
- return null;
- }
- try {
- CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
- Certificate[] result = new Certificate[length];
- for (int i = 0; i < result.length; i++) {
- String line = reader.readLine();
- byte[] bytes = Base64.decode(line.getBytes(Charsets.US_ASCII));
- result[i] = certificateFactory.generateCertificate(
- new ByteArrayInputStream(bytes));
- }
- return result;
- } catch (CertificateException e) {
- throw new IOException(e);
- }
- }
-
- private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
- if (certificates == null) {
- writer.write("-1\n");
- return;
- }
- try {
- writer.write(Integer.toString(certificates.length) + '\n');
- for (Certificate certificate : certificates) {
- byte[] bytes = certificate.getEncoded();
- String line = Base64.encode(bytes);
- writer.write(line + '\n');
- }
- } catch (CertificateEncodingException e) {
- throw new IOException(e);
- }
- }
-
- public boolean matches(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) {
- return this.uri.equals(uri.toString())
- && this.requestMethod.equals(requestMethod)
- && new ResponseHeaders(uri, responseHeaders)
- .varyMatches(varyHeaders.toMultimap(), requestHeaders);
- }
- }
-
- /**
- * Returns an input stream that reads the body of a snapshot, closing the
- * snapshot when the stream is closed.
- */
- private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
- return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
- @Override public void close() throws IOException {
- snapshot.close();
- super.close();
- }
- };
- }
-
- static class EntryCacheResponse extends CacheResponse {
- private final Entry entry;
- private final DiskLruCache.Snapshot snapshot;
- private final InputStream in;
-
- public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
- this.entry = entry;
- this.snapshot = snapshot;
- this.in = newBodyInputStream(snapshot);
- }
-
- @Override public Map<String, List<String>> getHeaders() {
- return entry.responseHeaders.toMultimap();
- }
-
- @Override public InputStream getBody() {
- return in;
- }
- }
-
- static class EntrySecureCacheResponse extends SecureCacheResponse {
- private final Entry entry;
- private final DiskLruCache.Snapshot snapshot;
- private final InputStream in;
-
- public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
- this.entry = entry;
- this.snapshot = snapshot;
- this.in = newBodyInputStream(snapshot);
- }
-
- @Override public Map<String, List<String>> getHeaders() {
- return entry.responseHeaders.toMultimap();
- }
-
- @Override public InputStream getBody() {
- return in;
- }
-
- @Override public String getCipherSuite() {
- return entry.cipherSuite;
- }
-
- @Override public List<Certificate> getServerCertificateChain()
- throws SSLPeerUnverifiedException {
- if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
- throw new SSLPeerUnverifiedException(null);
- }
- return Arrays.asList(entry.peerCertificates.clone());
- }
-
- @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
- throw new SSLPeerUnverifiedException(null);
- }
- return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal();
- }
-
- @Override public List<Certificate> getLocalCertificateChain() {
- if (entry.localCertificates == null || entry.localCertificates.length == 0) {
- return null;
- }
- return Arrays.asList(entry.localCertificates.clone());
- }
-
- @Override public Principal getLocalPrincipal() {
- if (entry.localCertificates == null || entry.localCertificates.length == 0) {
- return null;
- }
- return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal();
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
deleted file mode 100644
index 3e6503f..0000000
--- a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Authenticator;
-import java.net.HttpRetryException;
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.PasswordAuthentication;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.SocketPermission;
-import java.net.URL;
-import java.nio.charset.Charsets;
-import java.security.Permission;
-import java.util.List;
-import java.util.Map;
-import libcore.io.Base64;
-import libcore.io.IoUtils;
-
-/**
- * This implementation uses HttpEngine to send requests and receive responses.
- * This class may use multiple HttpEngines to follow redirects, authentication
- * retries, etc. to retrieve the final response body.
- *
- * <h3>What does 'connected' mean?</h3>
- * This class inherits a {@code connected} field from the superclass. That field
- * is <strong>not</strong> used to indicate not whether this URLConnection is
- * currently connected. Instead, it indicates whether a connection has ever been
- * attempted. Once a connection has been attempted, certain properties (request
- * header fields, request method, etc.) are immutable. Test the {@code
- * connection} field on this class for null/non-null to determine of an instance
- * is currently connected to a server.
- */
-class HttpURLConnectionImpl extends HttpURLConnection {
-
- private final int defaultPort;
-
- private Proxy proxy;
-
- private final RawHeaders rawRequestHeaders = new RawHeaders();
-
- private int redirectionCount;
-
- protected IOException httpEngineFailure;
- protected HttpEngine httpEngine;
-
- protected HttpURLConnectionImpl(URL url, int port) {
- super(url);
- defaultPort = port;
- }
-
- protected HttpURLConnectionImpl(URL url, int port, Proxy proxy) {
- this(url, port);
- this.proxy = proxy;
- }
-
- @Override public final void connect() throws IOException {
- initHttpEngine();
- try {
- httpEngine.sendRequest();
- } catch (IOException e) {
- httpEngineFailure = e;
- throw e;
- }
- }
-
- @Override public final void disconnect() {
- // Calling disconnect() before a connection exists should have no effect.
- if (httpEngine != null) {
- // We close the response body here instead of in
- // HttpEngine.release because that is called when input
- // has been completely read from the underlying socket.
- // However the response body can be a GZIPInputStream that
- // still has unread data.
- if (httpEngine.hasResponse()) {
- IoUtils.closeQuietly(httpEngine.getResponseBody());
- }
- httpEngine.release(false);
- }
- }
-
- /**
- * Returns an input stream from the server in the case of error such as the
- * requested file (txt, htm, html) is not found on the remote server.
- */
- @Override public final InputStream getErrorStream() {
- try {
- HttpEngine response = getResponse();
- if (response.hasResponseBody()
- && response.getResponseCode() >= HTTP_BAD_REQUEST) {
- return response.getResponseBody();
- }
- return null;
- } catch (IOException e) {
- return null;
- }
- }
-
- /**
- * Returns the value of the field at {@code position}. Returns null if there
- * are fewer than {@code position} headers.
- */
- @Override public final String getHeaderField(int position) {
- try {
- return getResponse().getResponseHeaders().getHeaders().getValue(position);
- } catch (IOException e) {
- return null;
- }
- }
-
- /**
- * Returns the value of the field corresponding to the {@code fieldName}, or
- * null if there is no such field. If the field has multiple values, the
- * last value is returned.
- */
- @Override public final String getHeaderField(String fieldName) {
- try {
- RawHeaders rawHeaders = getResponse().getResponseHeaders().getHeaders();
- return fieldName == null
- ? rawHeaders.getStatusLine()
- : rawHeaders.get(fieldName);
- } catch (IOException e) {
- return null;
- }
- }
-
- @Override public final String getHeaderFieldKey(int position) {
- try {
- return getResponse().getResponseHeaders().getHeaders().getFieldName(position);
- } catch (IOException e) {
- return null;
- }
- }
-
- @Override public final Map<String, List<String>> getHeaderFields() {
- try {
- return getResponse().getResponseHeaders().getHeaders().toMultimap();
- } catch (IOException e) {
- return null;
- }
- }
-
- @Override public final Map<String, List<String>> getRequestProperties() {
- if (connected) {
- throw new IllegalStateException(
- "Cannot access request header fields after connection is set");
- }
- return rawRequestHeaders.toMultimap();
- }
-
- @Override public final InputStream getInputStream() throws IOException {
- if (!doInput) {
- throw new ProtocolException("This protocol does not support input");
- }
-
- HttpEngine response = getResponse();
-
- /*
- * if the requested file does not exist, throw an exception formerly the
- * Error page from the server was returned if the requested file was
- * text/html this has changed to return FileNotFoundException for all
- * file types
- */
- if (getResponseCode() >= HTTP_BAD_REQUEST) {
- throw new FileNotFoundException(url.toString());
- }
-
- InputStream result = response.getResponseBody();
- if (result == null) {
- throw new IOException("No response body exists; responseCode=" + getResponseCode());
- }
- return result;
- }
-
- @Override public final OutputStream getOutputStream() throws IOException {
- connect();
-
- OutputStream result = httpEngine.getRequestBody();
- if (result == null) {
- throw new ProtocolException("method does not support a request body: " + method);
- } else if (httpEngine.hasResponse()) {
- throw new ProtocolException("cannot write request body after response has been read");
- }
-
- return result;
- }
-
- @Override public final Permission getPermission() throws IOException {
- String connectToAddress = getConnectToHost() + ":" + getConnectToPort();
- return new SocketPermission(connectToAddress, "connect, resolve");
- }
-
- private String getConnectToHost() {
- return usingProxy()
- ? ((InetSocketAddress) proxy.address()).getHostName()
- : getURL().getHost();
- }
-
- private int getConnectToPort() {
- int hostPort = usingProxy()
- ? ((InetSocketAddress) proxy.address()).getPort()
- : getURL().getPort();
- return hostPort < 0 ? getDefaultPort() : hostPort;
- }
-
- @Override public final String getRequestProperty(String field) {
- if (field == null) {
- return null;
- }
- return rawRequestHeaders.get(field);
- }
-
- private void initHttpEngine() throws IOException {
- if (httpEngineFailure != null) {
- throw httpEngineFailure;
- } else if (httpEngine != null) {
- return;
- }
-
- connected = true;
- try {
- if (doOutput) {
- if (method == HttpEngine.GET) {
- // they are requesting a stream to write to. This implies a POST method
- method = HttpEngine.POST;
- } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {
- // If the request method is neither POST nor PUT, then you're not writing
- throw new ProtocolException(method + " does not support writing");
- }
- }
- httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
- } catch (IOException e) {
- httpEngineFailure = e;
- throw e;
- }
- }
-
- /**
- * Create a new HTTP engine. This hook method is non-final so it can be
- * overridden by HttpsURLConnectionImpl.
- */
- protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
- HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
- return new HttpEngine(this, method, requestHeaders, connection, requestBody);
- }
-
- /**
- * Aggressively tries to get the final HTTP response, potentially making
- * many HTTP requests in the process in order to cope with redirects and
- * authentication.
- */
- private HttpEngine getResponse() throws IOException {
- initHttpEngine();
-
- if (httpEngine.hasResponse()) {
- return httpEngine;
- }
-
- while (true) {
- try {
- httpEngine.sendRequest();
- httpEngine.readResponse();
- } catch (IOException e) {
- /*
- * If the connection was recycled, its staleness may have caused
- * the failure. Silently retry with a different connection.
- */
- OutputStream requestBody = httpEngine.getRequestBody();
- if (httpEngine.hasRecycledConnection()
- && (requestBody == null || requestBody instanceof RetryableOutputStream)) {
- httpEngine.release(false);
- httpEngine = newHttpEngine(method, rawRequestHeaders, null,
- (RetryableOutputStream) requestBody);
- continue;
- }
- httpEngineFailure = e;
- throw e;
- }
-
- Retry retry = processResponseHeaders();
- if (retry == Retry.NONE) {
- httpEngine.automaticallyReleaseConnectionToPool();
- return httpEngine;
- }
-
- /*
- * The first request was insufficient. Prepare for another...
- */
- String retryMethod = method;
- OutputStream requestBody = httpEngine.getRequestBody();
-
- /*
- * Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
- * redirect should keep the same method, Chrome, Firefox and the
- * RI all issue GETs when following any redirect.
- */
- int responseCode = getResponseCode();
- if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM
- || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) {
- retryMethod = HttpEngine.GET;
- requestBody = null;
- }
-
- if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
- throw new HttpRetryException("Cannot retry streamed HTTP body",
- httpEngine.getResponseCode());
- }
-
- if (retry == Retry.DIFFERENT_CONNECTION) {
- httpEngine.automaticallyReleaseConnectionToPool();
- } else {
- httpEngine.markConnectionAsRecycled();
- }
-
- httpEngine.release(true);
-
- httpEngine = newHttpEngine(retryMethod, rawRequestHeaders,
- httpEngine.getConnection(), (RetryableOutputStream) requestBody);
- }
- }
-
- HttpEngine getHttpEngine() {
- return httpEngine;
- }
-
- enum Retry {
- NONE,
- SAME_CONNECTION,
- DIFFERENT_CONNECTION
- }
-
- /**
- * Returns the retry action to take for the current response headers. The
- * headers, proxy and target URL or this connection may be adjusted to
- * prepare for a follow up request.
- */
- private Retry processResponseHeaders() throws IOException {
- switch (getResponseCode()) {
- case HTTP_PROXY_AUTH:
- if (!usingProxy()) {
- throw new IOException(
- "Received HTTP_PROXY_AUTH (407) code while not using proxy");
- }
- // fall-through
- case HTTP_UNAUTHORIZED:
- boolean credentialsFound = processAuthHeader(getResponseCode(),
- httpEngine.getResponseHeaders(), rawRequestHeaders);
- return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
-
- case HTTP_MULT_CHOICE:
- case HTTP_MOVED_PERM:
- case HTTP_MOVED_TEMP:
- case HTTP_SEE_OTHER:
- if (!getInstanceFollowRedirects()) {
- return Retry.NONE;
- }
- if (++redirectionCount > HttpEngine.MAX_REDIRECTS) {
- throw new ProtocolException("Too many redirects");
- }
- String location = getHeaderField("Location");
- if (location == null) {
- return Retry.NONE;
- }
- URL previousUrl = url;
- url = new URL(previousUrl, location);
- if (!previousUrl.getProtocol().equals(url.getProtocol())) {
- return Retry.NONE; // the scheme changed; don't retry.
- }
- if (previousUrl.getHost().equals(url.getHost())
- && previousUrl.getEffectivePort() == url.getEffectivePort()) {
- return Retry.SAME_CONNECTION;
- } else {
- return Retry.DIFFERENT_CONNECTION;
- }
-
- default:
- return Retry.NONE;
- }
- }
-
- /**
- * React to a failed authorization response by looking up new credentials.
- *
- * @return true if credentials have been added to successorRequestHeaders
- * and another request should be attempted.
- */
- final boolean processAuthHeader(int responseCode, ResponseHeaders response,
- RawHeaders successorRequestHeaders) throws IOException {
- if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
- throw new IllegalArgumentException("Bad response code: " + responseCode);
- }
-
- // keep asking for username/password until authorized
- String challengeHeader = responseCode == HTTP_PROXY_AUTH
- ? "Proxy-Authenticate"
- : "WWW-Authenticate";
- String credentials = getAuthorizationCredentials(response.getHeaders(), challengeHeader);
- if (credentials == null) {
- return false; // could not find credentials, end request cycle
- }
-
- // add authorization credentials, bypassing the already-connected check
- String fieldName = responseCode == HTTP_PROXY_AUTH
- ? "Proxy-Authorization"
- : "Authorization";
- successorRequestHeaders.set(fieldName, credentials);
- return true;
- }
-
- /**
- * Returns the authorization credentials on the base of provided challenge.
- */
- private String getAuthorizationCredentials(RawHeaders responseHeaders, String challengeHeader)
- throws IOException {
- List<Challenge> challenges = HeaderParser.parseChallenges(responseHeaders, challengeHeader);
- if (challenges.isEmpty()) {
- throw new IOException("No authentication challenges found");
- }
-
- for (Challenge challenge : challenges) {
- // use the global authenticator to get the password
- PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(
- getConnectToInetAddress(), getConnectToPort(), url.getProtocol(),
- challenge.realm, challenge.scheme);
- if (auth == null) {
- continue;
- }
-
- // base64 encode the username and password
- String usernameAndPassword = auth.getUserName() + ":" + new String(auth.getPassword());
- byte[] bytes = usernameAndPassword.getBytes(Charsets.ISO_8859_1);
- String encoded = Base64.encode(bytes);
- return challenge.scheme + " " + encoded;
- }
-
- return null;
- }
-
- private InetAddress getConnectToInetAddress() throws IOException {
- return usingProxy()
- ? ((InetSocketAddress) proxy.address()).getAddress()
- : InetAddress.getByName(getURL().getHost());
- }
-
- final int getDefaultPort() {
- return defaultPort;
- }
-
- /** @see HttpURLConnection#setFixedLengthStreamingMode(int) */
- final int getFixedContentLength() {
- return fixedContentLength;
- }
-
- /** @see HttpURLConnection#setChunkedStreamingMode(int) */
- final int getChunkLength() {
- return chunkLength;
- }
-
- final Proxy getProxy() {
- return proxy;
- }
-
- final void setProxy(Proxy proxy) {
- this.proxy = proxy;
- }
-
- @Override public final boolean usingProxy() {
- return (proxy != null && proxy.type() != Proxy.Type.DIRECT);
- }
-
- @Override public String getResponseMessage() throws IOException {
- return getResponse().getResponseHeaders().getHeaders().getResponseMessage();
- }
-
- @Override public final int getResponseCode() throws IOException {
- return getResponse().getResponseCode();
- }
-
- @Override public final void setRequestProperty(String field, String newValue) {
- if (connected) {
- throw new IllegalStateException("Cannot set request property after connection is made");
- }
- if (field == null) {
- throw new NullPointerException("field == null");
- }
- rawRequestHeaders.set(field, newValue);
- }
-
- @Override public final void addRequestProperty(String field, String value) {
- if (connected) {
- throw new IllegalStateException("Cannot add request property after connection is made");
- }
- if (field == null) {
- throw new NullPointerException("field == null");
- }
- rawRequestHeaders.add(field, value);
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpsHandler.java b/luni/src/main/java/libcore/net/http/HttpsHandler.java
deleted file mode 100644
index ed9ba72..0000000
--- a/luni/src/main/java/libcore/net/http/HttpsHandler.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.io.IOException;
-import java.net.Proxy;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-public final class HttpsHandler extends URLStreamHandler {
-
- @Override protected URLConnection openConnection(URL url) throws IOException {
- return new HttpsURLConnectionImpl(url, getDefaultPort());
- }
-
- @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
- if (url == null || proxy == null) {
- throw new IllegalArgumentException("url == null || proxy == null");
- }
- return new HttpsURLConnectionImpl(url, getDefaultPort(), proxy);
- }
-
- @Override protected int getDefaultPort() {
- return 443;
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
deleted file mode 100644
index 9e3e4ef..0000000
--- a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
+++ /dev/null
@@ -1,580 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.CacheResponse;
-import java.net.HttpURLConnection;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.SecureCacheResponse;
-import java.net.URL;
-import java.security.Permission;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-
-final class HttpsURLConnectionImpl extends HttpsURLConnection {
-
- /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl */
- private final HttpUrlConnectionDelegate delegate;
-
- protected HttpsURLConnectionImpl(URL url, int port) {
- super(url);
- delegate = new HttpUrlConnectionDelegate(url, port);
- }
-
- protected HttpsURLConnectionImpl(URL url, int port, Proxy proxy) {
- super(url);
- delegate = new HttpUrlConnectionDelegate(url, port, proxy);
- }
-
- private void checkConnected() {
- if (delegate.getSSLSocket() == null) {
- throw new IllegalStateException("Connection has not yet been established");
- }
- }
-
- HttpEngine getHttpEngine() {
- return delegate.getHttpEngine();
- }
-
- @Override
- public String getCipherSuite() {
- SecureCacheResponse cacheResponse = delegate.getCacheResponse();
- if (cacheResponse != null) {
- return cacheResponse.getCipherSuite();
- }
- checkConnected();
- return delegate.getSSLSocket().getSession().getCipherSuite();
- }
-
- @Override
- public Certificate[] getLocalCertificates() {
- SecureCacheResponse cacheResponse = delegate.getCacheResponse();
- if (cacheResponse != null) {
- List<Certificate> result = cacheResponse.getLocalCertificateChain();
- return result != null ? result.toArray(new Certificate[result.size()]) : null;
- }
- checkConnected();
- return delegate.getSSLSocket().getSession().getLocalCertificates();
- }
-
- @Override
- public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
- SecureCacheResponse cacheResponse = delegate.getCacheResponse();
- if (cacheResponse != null) {
- List<Certificate> result = cacheResponse.getServerCertificateChain();
- return result != null ? result.toArray(new Certificate[result.size()]) : null;
- }
- checkConnected();
- return delegate.getSSLSocket().getSession().getPeerCertificates();
- }
-
- @Override
- public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- SecureCacheResponse cacheResponse = delegate.getCacheResponse();
- if (cacheResponse != null) {
- return cacheResponse.getPeerPrincipal();
- }
- checkConnected();
- return delegate.getSSLSocket().getSession().getPeerPrincipal();
- }
-
- @Override
- public Principal getLocalPrincipal() {
- SecureCacheResponse cacheResponse = delegate.getCacheResponse();
- if (cacheResponse != null) {
- return cacheResponse.getLocalPrincipal();
- }
- checkConnected();
- return delegate.getSSLSocket().getSession().getLocalPrincipal();
- }
-
- @Override
- public void disconnect() {
- delegate.disconnect();
- }
-
- @Override
- public InputStream getErrorStream() {
- return delegate.getErrorStream();
- }
-
- @Override
- public String getRequestMethod() {
- return delegate.getRequestMethod();
- }
-
- @Override
- public int getResponseCode() throws IOException {
- return delegate.getResponseCode();
- }
-
- @Override
- public String getResponseMessage() throws IOException {
- return delegate.getResponseMessage();
- }
-
- @Override
- public void setRequestMethod(String method) throws ProtocolException {
- delegate.setRequestMethod(method);
- }
-
- @Override
- public boolean usingProxy() {
- return delegate.usingProxy();
- }
-
- @Override
- public boolean getInstanceFollowRedirects() {
- return delegate.getInstanceFollowRedirects();
- }
-
- @Override
- public void setInstanceFollowRedirects(boolean followRedirects) {
- delegate.setInstanceFollowRedirects(followRedirects);
- }
-
- @Override
- public void connect() throws IOException {
- connected = true;
- delegate.connect();
- }
-
- @Override
- public boolean getAllowUserInteraction() {
- return delegate.getAllowUserInteraction();
- }
-
- @Override
- public Object getContent() throws IOException {
- return delegate.getContent();
- }
-
- @SuppressWarnings("unchecked") // Spec does not generify
- @Override
- public Object getContent(Class[] types) throws IOException {
- return delegate.getContent(types);
- }
-
- @Override
- public String getContentEncoding() {
- return delegate.getContentEncoding();
- }
-
- @Override
- public int getContentLength() {
- return delegate.getContentLength();
- }
-
- @Override
- public String getContentType() {
- return delegate.getContentType();
- }
-
- @Override
- public long getDate() {
- return delegate.getDate();
- }
-
- @Override
- public boolean getDefaultUseCaches() {
- return delegate.getDefaultUseCaches();
- }
-
- @Override
- public boolean getDoInput() {
- return delegate.getDoInput();
- }
-
- @Override
- public boolean getDoOutput() {
- return delegate.getDoOutput();
- }
-
- @Override
- public long getExpiration() {
- return delegate.getExpiration();
- }
-
- @Override
- public String getHeaderField(int pos) {
- return delegate.getHeaderField(pos);
- }
-
- @Override
- public Map<String, List<String>> getHeaderFields() {
- return delegate.getHeaderFields();
- }
-
- @Override
- public Map<String, List<String>> getRequestProperties() {
- return delegate.getRequestProperties();
- }
-
- @Override
- public void addRequestProperty(String field, String newValue) {
- delegate.addRequestProperty(field, newValue);
- }
-
- @Override
- public String getHeaderField(String key) {
- return delegate.getHeaderField(key);
- }
-
- @Override
- public long getHeaderFieldDate(String field, long defaultValue) {
- return delegate.getHeaderFieldDate(field, defaultValue);
- }
-
- @Override
- public int getHeaderFieldInt(String field, int defaultValue) {
- return delegate.getHeaderFieldInt(field, defaultValue);
- }
-
- @Override
- public String getHeaderFieldKey(int posn) {
- return delegate.getHeaderFieldKey(posn);
- }
-
- @Override
- public long getIfModifiedSince() {
- return delegate.getIfModifiedSince();
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- return delegate.getInputStream();
- }
-
- @Override
- public long getLastModified() {
- return delegate.getLastModified();
- }
-
- @Override
- public OutputStream getOutputStream() throws IOException {
- return delegate.getOutputStream();
- }
-
- @Override
- public Permission getPermission() throws IOException {
- return delegate.getPermission();
- }
-
- @Override
- public String getRequestProperty(String field) {
- return delegate.getRequestProperty(field);
- }
-
- @Override
- public URL getURL() {
- return delegate.getURL();
- }
-
- @Override
- public boolean getUseCaches() {
- return delegate.getUseCaches();
- }
-
- @Override
- public void setAllowUserInteraction(boolean newValue) {
- delegate.setAllowUserInteraction(newValue);
- }
-
- @Override
- public void setDefaultUseCaches(boolean newValue) {
- delegate.setDefaultUseCaches(newValue);
- }
-
- @Override
- public void setDoInput(boolean newValue) {
- delegate.setDoInput(newValue);
- }
-
- @Override
- public void setDoOutput(boolean newValue) {
- delegate.setDoOutput(newValue);
- }
-
- @Override
- public void setIfModifiedSince(long newValue) {
- delegate.setIfModifiedSince(newValue);
- }
-
- @Override
- public void setRequestProperty(String field, String newValue) {
- delegate.setRequestProperty(field, newValue);
- }
-
- @Override
- public void setUseCaches(boolean newValue) {
- delegate.setUseCaches(newValue);
- }
-
- @Override
- public void setConnectTimeout(int timeoutMillis) {
- delegate.setConnectTimeout(timeoutMillis);
- }
-
- @Override
- public int getConnectTimeout() {
- return delegate.getConnectTimeout();
- }
-
- @Override
- public void setReadTimeout(int timeoutMillis) {
- delegate.setReadTimeout(timeoutMillis);
- }
-
- @Override
- public int getReadTimeout() {
- return delegate.getReadTimeout();
- }
-
- @Override
- public String toString() {
- return delegate.toString();
- }
-
- @Override
- public void setFixedLengthStreamingMode(int contentLength) {
- delegate.setFixedLengthStreamingMode(contentLength);
- }
-
- @Override
- public void setChunkedStreamingMode(int chunkLength) {
- delegate.setChunkedStreamingMode(chunkLength);
- }
-
- private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
- private HttpUrlConnectionDelegate(URL url, int port) {
- super(url, port);
- }
-
- private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) {
- super(url, port, proxy);
- }
-
- @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
- HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
- return new HttpsEngine(this, method, requestHeaders, connection, requestBody,
- HttpsURLConnectionImpl.this);
- }
-
- public SecureCacheResponse getCacheResponse() {
- HttpsEngine engine = (HttpsEngine) httpEngine;
- return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null;
- }
-
- public SSLSocket getSSLSocket() {
- HttpsEngine engine = (HttpsEngine) httpEngine;
- return engine != null ? engine.sslSocket : null;
- }
- }
-
- private static class HttpsEngine extends HttpEngine {
-
- /**
- * Local stash of HttpsEngine.connection.sslSocket for answering
- * queries such as getCipherSuite even after
- * httpsEngine.Connection has been recycled. It's presence is also
- * used to tell if the HttpsURLConnection is considered connected,
- * as opposed to the connected field of URLConnection or the a
- * non-null connect in HttpURLConnectionImpl
- */
- private SSLSocket sslSocket;
-
- private final HttpsURLConnectionImpl enclosing;
-
- /**
- * @param policy the HttpURLConnectionImpl with connection configuration
- * @param enclosing the HttpsURLConnection with HTTPS features
- */
- private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
- HttpConnection connection, RetryableOutputStream requestBody,
- HttpsURLConnectionImpl enclosing) throws IOException {
- super(policy, method, requestHeaders, connection, requestBody);
- this.sslSocket = connection != null ? connection.getSecureSocketIfConnected() : null;
- this.enclosing = enclosing;
- }
-
- @Override protected void connect() throws IOException {
- // first try an SSL connection with compression and
- // various TLS extensions enabled, if it fails (and its
- // not unheard of that it will) fallback to a more
- // barebones connections
- boolean connectionReused;
- try {
- connectionReused = makeSslConnection(true);
- } catch (IOException e) {
- // If the problem was a CertificateException from the X509TrustManager,
- // do not retry, we didn't have an abrupt server initiated exception.
- if (e instanceof SSLHandshakeException
- && e.getCause() instanceof CertificateException) {
- throw e;
- }
- release(false);
- connectionReused = makeSslConnection(false);
- }
-
- if (!connectionReused) {
- sslSocket = connection.verifySecureSocketHostname(enclosing.getHostnameVerifier());
- }
- }
-
- /**
- * Attempt to make an https connection. Returns true if a
- * connection was reused, false otherwise.
- *
- * @param tlsTolerant If true, assume server can handle common
- * TLS extensions and SSL deflate compression. If false, use
- * an SSL3 only fallback mode without compression.
- */
- private boolean makeSslConnection(boolean tlsTolerant) throws IOException {
- // make an SSL Tunnel on the first message pair of each SSL + proxy connection
- if (connection == null) {
- connection = openSocketConnection();
- if (connection.getAddress().getProxy() != null) {
- makeTunnel(policy, connection, getRequestHeaders());
- }
- }
-
- // if super.makeConnection returned a connection from the
- // pool, sslSocket needs to be initialized here. If it is
- // a new connection, it will be initialized by
- // getSecureSocket below.
- sslSocket = connection.getSecureSocketIfConnected();
-
- // we already have an SSL connection,
- if (sslSocket != null) {
- return true;
- }
-
- connection.setupSecureSocket(enclosing.getSSLSocketFactory(), tlsTolerant);
- return false;
- }
-
- /**
- * To make an HTTPS connection over an HTTP proxy, send an unencrypted
- * CONNECT request to create the proxy connection. This may need to be
- * retried if the proxy requires authorization.
- */
- private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection,
- RequestHeaders requestHeaders) throws IOException {
- RawHeaders rawRequestHeaders = requestHeaders.getHeaders();
- while (true) {
- HttpEngine connect = new ProxyConnectEngine(policy, rawRequestHeaders, connection);
- connect.sendRequest();
- connect.readResponse();
-
- int responseCode = connect.getResponseCode();
- switch (connect.getResponseCode()) {
- case HTTP_OK:
- return;
- case HTTP_PROXY_AUTH:
- rawRequestHeaders = new RawHeaders(rawRequestHeaders);
- boolean credentialsFound = policy.processAuthHeader(HTTP_PROXY_AUTH,
- connect.getResponseHeaders(), rawRequestHeaders);
- if (credentialsFound) {
- continue;
- } else {
- throw new IOException("Failed to authenticate with proxy");
- }
- default:
- throw new IOException("Unexpected response code for CONNECT: " + responseCode);
- }
- }
- }
-
- @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
- return cacheResponse instanceof SecureCacheResponse;
- }
-
- @Override protected boolean includeAuthorityInRequestLine() {
- // Even if there is a proxy, it isn't involved. Always request just the file.
- return false;
- }
-
- @Override protected SSLSocketFactory getSslSocketFactory() {
- return enclosing.getSSLSocketFactory();
- }
-
- @Override protected HttpURLConnection getHttpConnectionToCache() {
- return enclosing;
- }
- }
-
- private static class ProxyConnectEngine extends HttpEngine {
- public ProxyConnectEngine(HttpURLConnectionImpl policy, RawHeaders requestHeaders,
- HttpConnection connection) throws IOException {
- super(policy, HttpEngine.CONNECT, requestHeaders, connection, null);
- }
-
- /**
- * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), send
- * only the minimum set of headers. This avoids sending potentially
- * sensitive data like HTTP cookies to the proxy unencrypted.
- */
- @Override protected RawHeaders getNetworkRequestHeaders() throws IOException {
- RequestHeaders privateHeaders = getRequestHeaders();
- URL url = policy.getURL();
-
- RawHeaders result = new RawHeaders();
- result.setStatusLine("CONNECT " + url.getHost() + ":" + url.getEffectivePort()
- + " HTTP/1.1");
-
- // Always set Host and User-Agent.
- String host = privateHeaders.getHost();
- if (host == null) {
- host = getOriginAddress(url);
- }
- result.set("Host", host);
-
- String userAgent = privateHeaders.getUserAgent();
- if (userAgent == null) {
- userAgent = getDefaultUserAgent();
- }
- result.set("User-Agent", userAgent);
-
- // Copy over the Proxy-Authorization header if it exists.
- String proxyAuthorization = privateHeaders.getProxyAuthorization();
- if (proxyAuthorization != null) {
- result.set("Proxy-Authorization", proxyAuthorization);
- }
-
- // Always set the Proxy-Connection to Keep-Alive for the benefit of
- // HTTP/1.0 proxies like Squid.
- result.set("Proxy-Connection", "Keep-Alive");
- return result;
- }
-
- @Override protected boolean requiresTunnel() {
- return true;
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/RawHeaders.java b/luni/src/main/java/libcore/net/http/RawHeaders.java
deleted file mode 100644
index 21b48f1..0000000
--- a/luni/src/main/java/libcore/net/http/RawHeaders.java
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 libcore.net.http;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-
-/**
- * The HTTP status and unparsed header fields of a single HTTP message. Values
- * are represented as uninterpreted strings; use {@link RequestHeaders} and
- * {@link ResponseHeaders} for interpreted headers. This class maintains the
- * order of the header fields within the HTTP message.
- *
- * <p>This class tracks fields line-by-line. A field with multiple comma-
- * separated values on the same line will be treated as a field with a single
- * value by this class. It is the caller's responsibility to detect and split
- * on commas if their field permits multiple values. This simplifies use of
- * single-valued fields whose values routinely contain commas, such as cookies
- * or dates.
- *
- * <p>This class trims whitespace from values. It never returns values with
- * leading or trailing whitespace.
- */
-public final class RawHeaders {
- private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
- @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
- @Override public int compare(String a, String b) {
- if (a == b) {
- return 0;
- } else if (a == null) {
- return -1;
- } else if (b == null) {
- return 1;
- } else {
- return String.CASE_INSENSITIVE_ORDER.compare(a, b);
- }
- }
- };
-
- private final List<String> namesAndValues = new ArrayList<String>(20);
- private String statusLine;
- private int httpMinorVersion = 1;
- private int responseCode = -1;
- private String responseMessage;
-
- public RawHeaders() {}
-
- public RawHeaders(RawHeaders copyFrom) {
- namesAndValues.addAll(copyFrom.namesAndValues);
- statusLine = copyFrom.statusLine;
- httpMinorVersion = copyFrom.httpMinorVersion;
- responseCode = copyFrom.responseCode;
- responseMessage = copyFrom.responseMessage;
- }
-
- /**
- * Sets the response status line (like "HTTP/1.0 200 OK") or request line
- * (like "GET / HTTP/1.1").
- */
- public void setStatusLine(String statusLine) {
- statusLine = statusLine.trim();
- this.statusLine = statusLine;
-
- if (statusLine == null || !statusLine.startsWith("HTTP/")) {
- return;
- }
- statusLine = statusLine.trim();
- int mark = statusLine.indexOf(" ") + 1;
- if (mark == 0) {
- return;
- }
- if (statusLine.charAt(mark - 2) != '1') {
- this.httpMinorVersion = 0;
- }
- int last = mark + 3;
- if (last > statusLine.length()) {
- last = statusLine.length();
- }
- this.responseCode = Integer.parseInt(statusLine.substring(mark, last));
- if (last + 1 <= statusLine.length()) {
- this.responseMessage = statusLine.substring(last + 1);
- }
- }
-
- public String getStatusLine() {
- return statusLine;
- }
-
- /**
- * Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0
- * and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown.
- */
- public int getHttpMinorVersion() {
- return httpMinorVersion != -1 ? httpMinorVersion : 1;
- }
-
- /**
- * Returns the HTTP status code or -1 if it is unknown.
- */
- public int getResponseCode() {
- return responseCode;
- }
-
- /**
- * Returns the HTTP status message or null if it is unknown.
- */
- public String getResponseMessage() {
- return responseMessage;
- }
-
- /**
- * Add an HTTP header line containing a field name, a literal colon, and a
- * value.
- */
- public void addLine(String line) {
- int index = line.indexOf(":");
- if (index == -1) {
- add("", line);
- } else {
- add(line.substring(0, index), line.substring(index + 1));
- }
- }
-
- /**
- * Add a field with the specified value.
- */
- public void add(String fieldName, String value) {
- if (fieldName == null) {
- throw new IllegalArgumentException("fieldName == null");
- }
- if (value == null) {
- /*
- * Given null values, the RI sends a malformed field line like
- * "Accept\r\n". For platform compatibility and HTTP compliance, we
- * print a warning and ignore null values.
- */
- System.logW("Ignoring HTTP header field '" + fieldName + "' because its value is null");
- return;
- }
- namesAndValues.add(fieldName);
- namesAndValues.add(value.trim());
- }
-
- public void removeAll(String fieldName) {
- for (int i = 0; i < namesAndValues.size(); i += 2) {
- if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) {
- namesAndValues.remove(i); // field name
- namesAndValues.remove(i); // value
- }
- }
- }
-
- public void addAll(String fieldName, List<String> headerFields) {
- for (String value : headerFields) {
- add(fieldName, value);
- }
- }
-
- /**
- * Set a field with the specified value. If the field is not found, it is
- * added. If the field is found, the existing values are replaced.
- */
- public void set(String fieldName, String value) {
- removeAll(fieldName);
- add(fieldName, value);
- }
-
- /**
- * Returns the number of field values.
- */
- public int length() {
- return namesAndValues.size() / 2;
- }
-
- /**
- * Returns the field at {@code position} or null if that is out of range.
- */
- public String getFieldName(int index) {
- int fieldNameIndex = index * 2;
- if (fieldNameIndex < 0 || fieldNameIndex >= namesAndValues.size()) {
- return null;
- }
- return namesAndValues.get(fieldNameIndex);
- }
-
- /**
- * Returns the value at {@code index} or null if that is out of range.
- */
- public String getValue(int index) {
- int valueIndex = index * 2 + 1;
- if (valueIndex < 0 || valueIndex >= namesAndValues.size()) {
- return null;
- }
- return namesAndValues.get(valueIndex);
- }
-
- /**
- * Returns the last value corresponding to the specified field, or null.
- */
- public String get(String fieldName) {
- for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
- if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) {
- return namesAndValues.get(i + 1);
- }
- }
- return null;
- }
-
- /**
- * @param fieldNames a case-insensitive set of HTTP header field names.
- */
- public RawHeaders getAll(Set<String> fieldNames) {
- RawHeaders result = new RawHeaders();
- for (int i = 0; i < namesAndValues.size(); i += 2) {
- String fieldName = namesAndValues.get(i);
- if (fieldNames.contains(fieldName)) {
- result.add(fieldName, namesAndValues.get(i + 1));
- }
- }
- return result;
- }
-
- public String toHeaderString() {
- StringBuilder result = new StringBuilder(256);
- result.append(statusLine).append("\r\n");
- for (int i = 0; i < namesAndValues.size(); i += 2) {
- result.append(namesAndValues.get(i)).append(": ")
- .append(namesAndValues.get(i + 1)).append("\r\n");
- }
- result.append("\r\n");
- return result.toString();
- }
-
- /**
- * Returns an immutable map containing each field to its list of values. The
- * status line is mapped to null.
- */
- public Map<String, List<String>> toMultimap() {
- Map<String, List<String>> result = new TreeMap<String, List<String>>(FIELD_NAME_COMPARATOR);
- for (int i = 0; i < namesAndValues.size(); i += 2) {
- String fieldName = namesAndValues.get(i);
- String value = namesAndValues.get(i + 1);
-
- List<String> allValues = new ArrayList<String>();
- List<String> otherValues = result.get(fieldName);
- if (otherValues != null) {
- allValues.addAll(otherValues);
- }
- allValues.add(value);
- result.put(fieldName, Collections.unmodifiableList(allValues));
- }
- if (statusLine != null) {
- result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine)));
- }
- return Collections.unmodifiableMap(result);
- }
-
- /**
- * Creates a new instance from the given map of fields to values. If
- * present, the null field's last element will be used to set the status
- * line.
- */
- public static RawHeaders fromMultimap(Map<String, List<String>> map) {
- RawHeaders result = new RawHeaders();
- for (Entry<String, List<String>> entry : map.entrySet()) {
- String fieldName = entry.getKey();
- List<String> values = entry.getValue();
- if (fieldName != null) {
- result.addAll(fieldName, values);
- } else if (!values.isEmpty()) {
- result.setStatusLine(values.get(values.size() - 1));
- }
- }
- return result;
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/RequestHeaders.java b/luni/src/main/java/libcore/net/http/RequestHeaders.java
deleted file mode 100644
index 3b536ce..0000000
--- a/luni/src/main/java/libcore/net/http/RequestHeaders.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2011 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 libcore.net.http;
-
-import java.net.URI;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Parsed HTTP request headers.
- */
-public final class RequestHeaders {
- private final URI uri;
- private final RawHeaders headers;
-
- /** Don't use a cache to satisfy this request. */
- private boolean noCache;
- private int maxAgeSeconds = -1;
- private int maxStaleSeconds = -1;
- private int minFreshSeconds = -1;
-
- /**
- * This field's name "only-if-cached" is misleading. It actually means "do
- * not use the network". It is set by a client who only wants to make a
- * request if it can be fully satisfied by the cache. Cached responses that
- * would require validation (ie. conditional gets) are not permitted if this
- * header is set.
- */
- private boolean onlyIfCached;
-
- /**
- * True if the request contains an authorization field. Although this isn't
- * necessarily a shared cache, it follows the spec's strict requirements for
- * shared caches.
- */
- private boolean hasAuthorization;
-
- private int contentLength = -1;
- private String transferEncoding;
- private String userAgent;
- private String host;
- private String connection;
- private String acceptEncoding;
- private String contentType;
- private String ifModifiedSince;
- private String ifNoneMatch;
- private String proxyAuthorization;
-
- public RequestHeaders(URI uri, RawHeaders headers) {
- this.uri = uri;
- this.headers = headers;
-
- HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
- @Override public void handle(String directive, String parameter) {
- if (directive.equalsIgnoreCase("no-cache")) {
- noCache = true;
- } else if (directive.equalsIgnoreCase("max-age")) {
- maxAgeSeconds = HeaderParser.parseSeconds(parameter);
- } else if (directive.equalsIgnoreCase("max-stale")) {
- maxStaleSeconds = HeaderParser.parseSeconds(parameter);
- } else if (directive.equalsIgnoreCase("min-fresh")) {
- minFreshSeconds = HeaderParser.parseSeconds(parameter);
- } else if (directive.equalsIgnoreCase("only-if-cached")) {
- onlyIfCached = true;
- }
- }
- };
-
- for (int i = 0; i < headers.length(); i++) {
- String fieldName = headers.getFieldName(i);
- String value = headers.getValue(i);
- if ("Cache-Control".equalsIgnoreCase(fieldName)) {
- HeaderParser.parseCacheControl(value, handler);
- } else if ("Pragma".equalsIgnoreCase(fieldName)) {
- if (value.equalsIgnoreCase("no-cache")) {
- noCache = true;
- }
- } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
- ifNoneMatch = value;
- } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
- ifModifiedSince = value;
- } else if ("Authorization".equalsIgnoreCase(fieldName)) {
- hasAuthorization = true;
- } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
- try {
- contentLength = Integer.parseInt(value);
- } catch (NumberFormatException ignored) {
- }
- } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
- transferEncoding = value;
- } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
- userAgent = value;
- } else if ("Host".equalsIgnoreCase(fieldName)) {
- host = value;
- } else if ("Connection".equalsIgnoreCase(fieldName)) {
- connection = value;
- } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) {
- acceptEncoding = value;
- } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
- contentType = value;
- } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
- proxyAuthorization = value;
- }
- }
- }
-
- public boolean isChunked() {
- return "chunked".equalsIgnoreCase(transferEncoding);
- }
-
- public boolean hasConnectionClose() {
- return "close".equalsIgnoreCase(connection);
- }
-
- public URI getUri() {
- return uri;
- }
-
- public RawHeaders getHeaders() {
- return headers;
- }
-
- public boolean isNoCache() {
- return noCache;
- }
-
- public int getMaxAgeSeconds() {
- return maxAgeSeconds;
- }
-
- public int getMaxStaleSeconds() {
- return maxStaleSeconds;
- }
-
- public int getMinFreshSeconds() {
- return minFreshSeconds;
- }
-
- public boolean isOnlyIfCached() {
- return onlyIfCached;
- }
-
- public boolean hasAuthorization() {
- return hasAuthorization;
- }
-
- public int getContentLength() {
- return contentLength;
- }
-
- public String getTransferEncoding() {
- return transferEncoding;
- }
-
- public String getUserAgent() {
- return userAgent;
- }
-
- public String getHost() {
- return host;
- }
-
- public String getConnection() {
- return connection;
- }
-
- public String getAcceptEncoding() {
- return acceptEncoding;
- }
-
- public String getContentType() {
- return contentType;
- }
-
- public String getIfModifiedSince() {
- return ifModifiedSince;
- }
-
- public String getIfNoneMatch() {
- return ifNoneMatch;
- }
-
- public String getProxyAuthorization() {
- return proxyAuthorization;
- }
-
- public void setChunked() {
- if (this.transferEncoding != null) {
- headers.removeAll("Transfer-Encoding");
- }
- headers.add("Transfer-Encoding", "chunked");
- this.transferEncoding = "chunked";
- }
-
- public void setContentLength(int contentLength) {
- if (this.contentLength != -1) {
- headers.removeAll("Content-Length");
- }
- headers.add("Content-Length", Integer.toString(contentLength));
- this.contentLength = contentLength;
- }
-
- public void setUserAgent(String userAgent) {
- if (this.userAgent != null) {
- headers.removeAll("User-Agent");
- }
- headers.add("User-Agent", userAgent);
- this.userAgent = userAgent;
- }
-
- public void setHost(String host) {
- if (this.host != null) {
- headers.removeAll("Host");
- }
- headers.add("Host", host);
- this.host = host;
- }
-
- public void setConnection(String connection) {
- if (this.connection != null) {
- headers.removeAll("Connection");
- }
- headers.add("Connection", connection);
- this.connection = connection;
- }
-
- public void setAcceptEncoding(String acceptEncoding) {
- if (this.acceptEncoding != null) {
- headers.removeAll("Accept-Encoding");
- }
- headers.add("Accept-Encoding", acceptEncoding);
- this.acceptEncoding = acceptEncoding;
- }
-
- public void setContentType(String contentType) {
- if (this.contentType != null) {
- headers.removeAll("Content-Type");
- }
- headers.add("Content-Type", contentType);
- this.contentType = contentType;
- }
-
- public void setIfModifiedSince(Date date) {
- if (ifModifiedSince != null) {
- headers.removeAll("If-Modified-Since");
- }
- String formattedDate = HttpDate.format(date);
- headers.add("If-Modified-Since", formattedDate);
- ifModifiedSince = formattedDate;
- }
-
- public void setIfNoneMatch(String ifNoneMatch) {
- if (this.ifNoneMatch != null) {
- headers.removeAll("If-None-Match");
- }
- headers.add("If-None-Match", ifNoneMatch);
- this.ifNoneMatch = ifNoneMatch;
- }
-
- /**
- * Returns true if the request contains conditions that save the server from
- * sending a response that the client has locally. When the caller adds
- * conditions, this cache won't participate in the request.
- */
- public boolean hasConditions() {
- return ifModifiedSince != null || ifNoneMatch != null;
- }
-
- public void addCookies(Map<String, List<String>> allCookieHeaders) {
- for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
- String key = entry.getKey();
- if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
- headers.addAll(key, entry.getValue());
- }
- }
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/ResponseHeaders.java b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
deleted file mode 100644
index c0b4200..0000000
--- a/luni/src/main/java/libcore/net/http/ResponseHeaders.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Copyright (C) 2011 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 libcore.net.http;
-
-import java.net.HttpURLConnection;
-import java.net.ResponseSource;
-import java.net.URI;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
-import libcore.util.Objects;
-
-/**
- * Parsed HTTP response headers.
- */
-public final class ResponseHeaders {
-
- /** HTTP header name for the local time when the request was sent. */
- private static final String SENT_MILLIS = "X-Android-Sent-Millis";
-
- /** HTTP header name for the local time when the response was received. */
- private static final String RECEIVED_MILLIS = "X-Android-Received-Millis";
-
- private final URI uri;
- private final RawHeaders headers;
-
- /** The server's time when this response was served, if known. */
- private Date servedDate;
-
- /** The last modified date of the response, if known. */
- private Date lastModified;
-
- /**
- * The expiration date of the response, if known. If both this field and the
- * max age are set, the max age is preferred.
- */
- private Date expires;
-
- /**
- * Extension header set by HttpURLConnectionImpl specifying the timestamp
- * when the HTTP request was first initiated.
- */
- private long sentRequestMillis;
-
- /**
- * Extension header set by HttpURLConnectionImpl specifying the timestamp
- * when the HTTP response was first received.
- */
- private long receivedResponseMillis;
-
- /**
- * In the response, this field's name "no-cache" is misleading. It doesn't
- * prevent us from caching the response; it only means we have to validate
- * the response with the origin server before returning it. We can do this
- * with a conditional get.
- */
- private boolean noCache;
-
- /** If true, this response should not be cached. */
- private boolean noStore;
-
- /**
- * The duration past the response's served date that it can be served
- * without validation.
- */
- private int maxAgeSeconds = -1;
-
- /**
- * The "s-maxage" directive is the max age for shared caches. Not to be
- * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
- * this directive is not honored by this cache.
- */
- private int sMaxAgeSeconds = -1;
-
- /**
- * This request header field's name "only-if-cached" is misleading. It
- * actually means "do not use the network". It is set by a client who only
- * wants to make a request if it can be fully satisfied by the cache.
- * Cached responses that would require validation (ie. conditional gets) are
- * not permitted if this header is set.
- */
- private boolean isPublic;
- private boolean mustRevalidate;
- private String etag;
- private int ageSeconds = -1;
-
- /** Case-insensitive set of field names. */
- private Set<String> varyFields = Collections.emptySet();
-
- private String contentEncoding;
- private String transferEncoding;
- private int contentLength = -1;
- private String connection;
-
- public ResponseHeaders(URI uri, RawHeaders headers) {
- this.uri = uri;
- this.headers = headers;
-
- HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
- @Override public void handle(String directive, String parameter) {
- if (directive.equalsIgnoreCase("no-cache")) {
- noCache = true;
- } else if (directive.equalsIgnoreCase("no-store")) {
- noStore = true;
- } else if (directive.equalsIgnoreCase("max-age")) {
- maxAgeSeconds = HeaderParser.parseSeconds(parameter);
- } else if (directive.equalsIgnoreCase("s-maxage")) {
- sMaxAgeSeconds = HeaderParser.parseSeconds(parameter);
- } else if (directive.equalsIgnoreCase("public")) {
- isPublic = true;
- } else if (directive.equalsIgnoreCase("must-revalidate")) {
- mustRevalidate = true;
- }
- }
- };
-
- for (int i = 0; i < headers.length(); i++) {
- String fieldName = headers.getFieldName(i);
- String value = headers.getValue(i);
- if ("Cache-Control".equalsIgnoreCase(fieldName)) {
- HeaderParser.parseCacheControl(value, handler);
- } else if ("Date".equalsIgnoreCase(fieldName)) {
- servedDate = HttpDate.parse(value);
- } else if ("Expires".equalsIgnoreCase(fieldName)) {
- expires = HttpDate.parse(value);
- } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
- lastModified = HttpDate.parse(value);
- } else if ("ETag".equalsIgnoreCase(fieldName)) {
- etag = value;
- } else if ("Pragma".equalsIgnoreCase(fieldName)) {
- if (value.equalsIgnoreCase("no-cache")) {
- noCache = true;
- }
- } else if ("Age".equalsIgnoreCase(fieldName)) {
- ageSeconds = HeaderParser.parseSeconds(value);
- } else if ("Vary".equalsIgnoreCase(fieldName)) {
- // Replace the immutable empty set with something we can mutate.
- if (varyFields.isEmpty()) {
- varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
- }
- for (String varyField : value.split(",")) {
- varyFields.add(varyField.trim());
- }
- } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) {
- contentEncoding = value;
- } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
- transferEncoding = value;
- } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
- try {
- contentLength = Integer.parseInt(value);
- } catch (NumberFormatException ignored) {
- }
- } else if ("Connection".equalsIgnoreCase(fieldName)) {
- connection = value;
- } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
- sentRequestMillis = Long.parseLong(value);
- } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
- receivedResponseMillis = Long.parseLong(value);
- }
- }
- }
-
- public boolean isContentEncodingGzip() {
- return "gzip".equalsIgnoreCase(contentEncoding);
- }
-
- public void stripContentEncoding() {
- contentEncoding = null;
- headers.removeAll("Content-Encoding");
- }
-
- public void stripContentLength() {
- contentLength = -1;
- headers.removeAll("Content-Length");
- }
-
- public boolean isChunked() {
- return "chunked".equalsIgnoreCase(transferEncoding);
- }
-
- public boolean hasConnectionClose() {
- return "close".equalsIgnoreCase(connection);
- }
-
- public URI getUri() {
- return uri;
- }
-
- public RawHeaders getHeaders() {
- return headers;
- }
-
- public Date getServedDate() {
- return servedDate;
- }
-
- public Date getLastModified() {
- return lastModified;
- }
-
- public Date getExpires() {
- return expires;
- }
-
- public boolean isNoCache() {
- return noCache;
- }
-
- public boolean isNoStore() {
- return noStore;
- }
-
- public int getMaxAgeSeconds() {
- return maxAgeSeconds;
- }
-
- public int getSMaxAgeSeconds() {
- return sMaxAgeSeconds;
- }
-
- public boolean isPublic() {
- return isPublic;
- }
-
- public boolean isMustRevalidate() {
- return mustRevalidate;
- }
-
- public String getEtag() {
- return etag;
- }
-
- public Set<String> getVaryFields() {
- return varyFields;
- }
-
- public String getContentEncoding() {
- return contentEncoding;
- }
-
- public int getContentLength() {
- return contentLength;
- }
-
- public String getConnection() {
- return connection;
- }
-
- public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) {
- this.sentRequestMillis = sentRequestMillis;
- headers.add(SENT_MILLIS, Long.toString(sentRequestMillis));
- this.receivedResponseMillis = receivedResponseMillis;
- headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis));
- }
-
- /**
- * Returns the current age of the response, in milliseconds. The calculation
- * is specified by RFC 2616, 13.2.3 Age Calculations.
- */
- private long computeAge(long nowMillis) {
- long apparentReceivedAge = servedDate != null
- ? Math.max(0, receivedResponseMillis - servedDate.getTime())
- : 0;
- long receivedAge = ageSeconds != -1
- ? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds))
- : apparentReceivedAge;
- long responseDuration = receivedResponseMillis - sentRequestMillis;
- long residentDuration = nowMillis - receivedResponseMillis;
- return receivedAge + responseDuration + residentDuration;
- }
-
- /**
- * Returns the number of milliseconds that the response was fresh for,
- * starting from the served date.
- */
- private long computeFreshnessLifetime() {
- if (maxAgeSeconds != -1) {
- return TimeUnit.SECONDS.toMillis(maxAgeSeconds);
- } else if (expires != null) {
- long servedMillis = servedDate != null ? servedDate.getTime() : receivedResponseMillis;
- long delta = expires.getTime() - servedMillis;
- return delta > 0 ? delta : 0;
- } else if (lastModified != null && uri.getRawQuery() == null) {
- /*
- * As recommended by the HTTP RFC and implemented in Firefox, the
- * max age of a document should be defaulted to 10% of the
- * document's age at the time it was served. Default expiration
- * dates aren't used for URIs containing a query.
- */
- long servedMillis = servedDate != null ? servedDate.getTime() : sentRequestMillis;
- long delta = servedMillis - lastModified.getTime();
- return delta > 0 ? (delta / 10) : 0;
- }
- return 0;
- }
-
- /**
- * Returns true if computeFreshnessLifetime used a heuristic. If we used a
- * heuristic to serve a cached response older than 24 hours, we are required
- * to attach a warning.
- */
- private boolean isFreshnessLifetimeHeuristic() {
- return maxAgeSeconds == -1 && expires == null;
- }
-
- /**
- * Returns true if this response can be stored to later serve another
- * request.
- */
- public boolean isCacheable(RequestHeaders request) {
- /*
- * Always go to network for uncacheable response codes (RFC 2616, 13.4),
- * This implementation doesn't support caching partial content.
- */
- int responseCode = headers.getResponseCode();
- if (responseCode != HttpURLConnection.HTTP_OK
- && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE
- && responseCode != HttpURLConnection.HTTP_MULT_CHOICE
- && responseCode != HttpURLConnection.HTTP_MOVED_PERM
- && responseCode != HttpURLConnection.HTTP_GONE) {
- return false;
- }
-
- /*
- * Responses to authorized requests aren't cacheable unless they include
- * a 'public', 'must-revalidate' or 's-maxage' directive.
- */
- if (request.hasAuthorization()
- && !isPublic
- && !mustRevalidate
- && sMaxAgeSeconds == -1) {
- return false;
- }
-
- if (noStore) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns true if a Vary header contains an asterisk. Such responses cannot
- * be cached.
- */
- public boolean hasVaryAll() {
- return varyFields.contains("*");
- }
-
- /**
- * Returns true if none of the Vary headers on this response have changed
- * between {@code cachedRequest} and {@code newRequest}.
- */
- public boolean varyMatches(Map<String, List<String>> cachedRequest,
- Map<String, List<String>> newRequest) {
- for (String field : varyFields) {
- if (!Objects.equal(cachedRequest.get(field), newRequest.get(field))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns the source to satisfy {@code request} given this cached response.
- */
- public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) {
- /*
- * If this response shouldn't have been stored, it should never be used
- * as a response source. This check should be redundant as long as the
- * persistence store is well-behaved and the rules are constant.
- */
- if (!isCacheable(request)) {
- return ResponseSource.NETWORK;
- }
-
- if (request.isNoCache() || request.hasConditions()) {
- return ResponseSource.NETWORK;
- }
-
- long ageMillis = computeAge(nowMillis);
- long freshMillis = computeFreshnessLifetime();
-
- if (request.getMaxAgeSeconds() != -1) {
- freshMillis = Math.min(freshMillis,
- TimeUnit.SECONDS.toMillis(request.getMaxAgeSeconds()));
- }
-
- long minFreshMillis = 0;
- if (request.getMinFreshSeconds() != -1) {
- minFreshMillis = TimeUnit.SECONDS.toMillis(request.getMinFreshSeconds());
- }
-
- long maxStaleMillis = 0;
- if (!mustRevalidate && request.getMaxStaleSeconds() != -1) {
- maxStaleMillis = TimeUnit.SECONDS.toMillis(request.getMaxStaleSeconds());
- }
-
- if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
- if (ageMillis + minFreshMillis >= freshMillis) {
- headers.add("Warning", "110 HttpURLConnection \"Response is stale\"");
- }
- if (ageMillis > TimeUnit.HOURS.toMillis(24) && isFreshnessLifetimeHeuristic()) {
- headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
- }
- return ResponseSource.CACHE;
- }
-
- if (lastModified != null) {
- request.setIfModifiedSince(lastModified);
- } else if (servedDate != null) {
- request.setIfModifiedSince(servedDate);
- }
-
- if (etag != null) {
- request.setIfNoneMatch(etag);
- }
-
- return request.hasConditions()
- ? ResponseSource.CONDITIONAL_CACHE
- : ResponseSource.NETWORK;
- }
-
- /**
- * Returns true if this cached response should be used; false if the
- * network response should be used.
- */
- public boolean validate(ResponseHeaders networkResponse) {
- if (networkResponse.headers.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
- return true;
- }
-
- /*
- * The HTTP spec says that if the network's response is older than our
- * cached response, we may return the cache's response. Like Chrome (but
- * unlike Firefox), this client prefers to return the newer response.
- */
- if (lastModified != null
- && networkResponse.lastModified != null
- && networkResponse.lastModified.getTime() < lastModified.getTime()) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Combines this cached header with a network header as defined by RFC 2616,
- * 13.5.3.
- */
- public ResponseHeaders combine(ResponseHeaders network) {
- RawHeaders result = new RawHeaders();
- result.setStatusLine(headers.getStatusLine());
-
- for (int i = 0; i < headers.length(); i++) {
- String fieldName = headers.getFieldName(i);
- String value = headers.getValue(i);
- if (fieldName.equals("Warning") && value.startsWith("1")) {
- continue; // drop 100-level freshness warnings
- }
- if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) {
- result.add(fieldName, value);
- }
- }
-
- for (int i = 0; i < network.headers.length(); i++) {
- String fieldName = network.headers.getFieldName(i);
- if (isEndToEnd(fieldName)) {
- result.add(fieldName, network.headers.getValue(i));
- }
- }
-
- return new ResponseHeaders(uri, result);
- }
-
- /**
- * Returns true if {@code fieldName} is an end-to-end HTTP header, as
- * defined by RFC 2616, 13.5.1.
- */
- private static boolean isEndToEnd(String fieldName) {
- return !fieldName.equalsIgnoreCase("Connection")
- && !fieldName.equalsIgnoreCase("Keep-Alive")
- && !fieldName.equalsIgnoreCase("Proxy-Authenticate")
- && !fieldName.equalsIgnoreCase("Proxy-Authorization")
- && !fieldName.equalsIgnoreCase("TE")
- && !fieldName.equalsIgnoreCase("Trailers")
- && !fieldName.equalsIgnoreCase("Transfer-Encoding")
- && !fieldName.equalsIgnoreCase("Upgrade");
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/RetryableOutputStream.java b/luni/src/main/java/libcore/net/http/RetryableOutputStream.java
deleted file mode 100644
index 67bb5b8..0000000
--- a/luni/src/main/java/libcore/net/http/RetryableOutputStream.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-/**
- * An HTTP request body that's completely buffered in memory. This allows
- * the post body to be transparently re-sent if the HTTP request must be
- * sent multiple times.
- */
-final class RetryableOutputStream extends AbstractHttpOutputStream {
- private final int limit;
- private final ByteArrayOutputStream content;
-
- public RetryableOutputStream(int limit) {
- this.limit = limit;
- this.content = new ByteArrayOutputStream(limit);
- }
-
- public RetryableOutputStream() {
- this.limit = -1;
- this.content = new ByteArrayOutputStream();
- }
-
- @Override public synchronized void close() throws IOException {
- if (closed) {
- return;
- }
- closed = true;
- if (content.size() < limit) {
- throw new IOException("content-length promised "
- + limit + " bytes, but received " + content.size());
- }
- }
-
- @Override public synchronized void write(byte[] buffer, int offset, int count)
- throws IOException {
- checkNotClosed();
- Arrays.checkOffsetAndCount(buffer.length, offset, count);
- if (limit != -1 && content.size() > limit - count) {
- throw new IOException("exceeded content-length limit of " + limit + " bytes");
- }
- content.write(buffer, offset, count);
- }
-
- public synchronized int contentLength() throws IOException {
- close();
- return content.size();
- }
-
- public void writeToSocket(OutputStream socketOut) throws IOException {
- content.writeTo(socketOut);
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java b/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
deleted file mode 100644
index a8224a3..0000000
--- a/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.CacheRequest;
-import java.util.Arrays;
-
-/**
- * An HTTP payload terminated by the end of the socket stream.
- */
-final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
- private boolean inputExhausted;
-
- UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest,
- HttpEngine httpEngine) throws IOException {
- super(is, httpEngine, cacheRequest);
- }
-
- @Override public int read(byte[] buffer, int offset, int count) throws IOException {
- Arrays.checkOffsetAndCount(buffer.length, offset, count);
- checkNotClosed();
- if (in == null || inputExhausted) {
- return -1;
- }
- int read = in.read(buffer, offset, count);
- if (read == -1) {
- inputExhausted = true;
- endOfInput(false);
- return -1;
- }
- cacheWrite(buffer, offset, read);
- return read;
- }
-
- @Override public int available() throws IOException {
- checkNotClosed();
- return in == null ? 0 : in.available();
- }
-
- @Override public void close() throws IOException {
- if (closed) {
- return;
- }
- closed = true;
- if (!inputExhausted) {
- unexpectedEndOfInput();
- }
- }
-}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
index b24ad0e..9991af4 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
@@ -206,6 +206,8 @@ public final class NativeCrypto {
public static native byte[] EC_GROUP_get_order(long groupCtx);
+ public static native int EC_GROUP_get_degree(long groupCtx);
+
public static native byte[] EC_GROUP_get_cofactor(long groupCtx);
public static native long EC_POINT_new(long groupRef);
@@ -417,8 +419,6 @@ public final class NativeCrypto {
public static native String[] get_X509_ex_xkusage(long x509ctx);
- public static native int X509_check_ca(long x509ctx);
-
public static native int get_X509_ex_pathlen(long x509ctx);
public static native long X509_get_notBefore(long x509ctx);
@@ -439,6 +439,8 @@ public final class NativeCrypto {
// --- X509 EXFLAG ---------------------------------------------------------
+ public static final int EXFLAG_CA = 0x10;
+
public static final int EXFLAG_CRITICAL = 0x200;
// --- PKCS7 ---------------------------------------------------------------
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java
index 0dad242..efa4747 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAKeyFactory.java
@@ -173,7 +173,7 @@ public class OpenSSLDSAKeyFactory extends KeyFactorySpi {
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
- } else if ("PKCS#8".equals(key.getFormat())) {
+ } else if ((key instanceof PrivateKey) && ("PKCS#8".equals(key.getFormat()))) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
@@ -183,7 +183,7 @@ public class OpenSSLDSAKeyFactory extends KeyFactorySpi {
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
- } else if ("X.509".equals(key.getFormat())) {
+ } else if ((key instanceof PublicKey) && ("X.509".equals(key.getFormat()))) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
index 096e300..5146156 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
@@ -19,9 +19,10 @@ package org.apache.harmony.xnet.provider.jsse;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.SecureRandom;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.KeyAgreementSpi;
@@ -54,36 +55,14 @@ public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
if (!lastPhase) {
throw new IllegalStateException("ECDH only has one phase");
}
+
if (key == null) {
throw new InvalidKeyException("key == null");
}
- if (!(key instanceof ECPublicKey)) {
- throw new InvalidKeyException("This phase requires an ECPublicKey. Actual key type: "
- + key.getClass());
- }
- ECPublicKey publicKey = (ECPublicKey) key;
-
- OpenSSLKey openSslPublicKey;
- if (publicKey instanceof OpenSSLECPublicKey) {
- // OpenSSL-backed key
- openSslPublicKey = ((OpenSSLECPublicKey) publicKey).getOpenSSLKey();
- } else {
- // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its X.509 encoding
- if (!"X.509".equals(publicKey.getFormat())) {
- throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
- + ") offers unsupported encoding format: " + publicKey.getFormat());
- }
- byte[] encoded = publicKey.getEncoded();
- if (encoded == null) {
- throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
- + ") does not provide encoded form");
- }
- try {
- openSslPublicKey = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded));
- } catch (Exception e) {
- throw new InvalidKeyException("Failed to decode X.509 encoded public key", e);
- }
+ if (!(key instanceof PublicKey)) {
+ throw new InvalidKeyException("Not a public key: " + key.getClass());
}
+ OpenSSLKey openSslPublicKey = translateKeyToEcOpenSSLKey(key);
byte[] buffer = new byte[mExpectedResultLength];
int actualResultLength = NativeCrypto.ECDH_compute_key(
@@ -142,35 +121,15 @@ public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
if (key == null) {
throw new InvalidKeyException("key == null");
}
- if (!(key instanceof ECPrivateKey)) {
- throw new InvalidKeyException("Not an EC private key: " + key.getClass());
- }
- ECPrivateKey privateKey = (ECPrivateKey) key;
- mExpectedResultLength =
- (privateKey.getParams().getCurve().getField().getFieldSize() + 7) / 8;
-
- OpenSSLKey openSslPrivateKey;
- if (privateKey instanceof OpenSSLECPrivateKey) {
- // OpenSSL-backed key
- openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey();
- } else {
- // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its PKCS#8 encoding
- if (!"PKCS#8".equals(privateKey.getFormat())) {
- throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
- + ") offers unsupported encoding format: " + privateKey.getFormat());
- }
- byte[] encoded = privateKey.getEncoded();
- if (encoded == null) {
- throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
- + ") does not provide encoded form");
- }
- try {
- openSslPrivateKey = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
- } catch (Exception e) {
- throw new InvalidKeyException("Failed to decode PKCS#8 encoded private key", e);
- }
+ if (!(key instanceof PrivateKey)) {
+ throw new InvalidKeyException("Not a private key: " + key.getClass());
}
- mOpenSslPrivateKey = openSslPrivateKey;
+
+ OpenSSLKey openSslKey = translateKeyToEcOpenSSLKey(key);
+ int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(NativeCrypto.EC_KEY_get0_group(
+ openSslKey.getPkeyContext()));
+ mExpectedResultLength = (fieldSizeBits + 7) / 8;
+ mOpenSslPrivateKey = openSslKey;
}
@Override
@@ -188,4 +147,13 @@ public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
throw new IllegalStateException("Key agreement not completed");
}
}
+
+ private static OpenSSLKey translateKeyToEcOpenSSLKey(Key key) throws InvalidKeyException {
+ try {
+ return ((OpenSSLKeyHolder) KeyFactory.getInstance(
+ "EC", OpenSSLProvider.PROVIDER_NAME).translateKey(key)).getOpenSSLKey();
+ } catch (Exception e) {
+ throw new InvalidKeyException("Failed to translate key to OpenSSL EC key", e);
+ }
+ }
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java
index c8e4f4e..e4673a5 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECKeyFactory.java
@@ -159,7 +159,7 @@ public class OpenSSLECKeyFactory extends KeyFactorySpi {
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
- } else if ("PKCS#8".equals(key.getFormat())) {
+ } else if ((key instanceof PrivateKey) && ("PKCS#8".equals(key.getFormat()))) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
@@ -169,7 +169,7 @@ public class OpenSSLECKeyFactory extends KeyFactorySpi {
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
- } else if ("X.509".equals(key.getFormat())) {
+ } else if ((key instanceof PublicKey) && ("X.509".equals(key.getFormat()))) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java
index 8e720a5..9c61abd 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAKeyFactory.java
@@ -204,7 +204,7 @@ public class OpenSSLRSAKeyFactory extends KeyFactorySpi {
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
- } else if ("PKCS#8".equals(key.getFormat())) {
+ } else if ((key instanceof PrivateKey) && ("PKCS#8".equals(key.getFormat()))) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
@@ -214,7 +214,7 @@ public class OpenSSLRSAKeyFactory extends KeyFactorySpi {
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
- } else if ("X.509".equals(key.getFormat())) {
+ } else if ((key instanceof PublicKey) && ("X.509".equals(key.getFormat()))) {
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
index 4639dfd..a96c361 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertPath.java
@@ -137,10 +137,21 @@ public class OpenSSLX509CertPath extends CertPath {
private static CertPath fromPkiPathEncoding(InputStream inStream) throws CertificateException {
OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(inStream);
+ final boolean markable = inStream.markSupported();
+ if (markable) {
+ inStream.mark(PUSHBACK_SIZE);
+ }
+
final long[] certRefs;
try {
certRefs = NativeCrypto.ASN1_seq_unpack_X509_bio(bis.getBioContext());
} catch (Exception e) {
+ if (markable) {
+ try {
+ inStream.reset();
+ } catch (IOException ignored) {
+ }
+ }
throw new CertificateException(e);
} finally {
NativeCrypto.BIO_free(bis.getBioContext());
@@ -218,6 +229,10 @@ public class OpenSSLX509CertPath extends CertPath {
public static CertPath fromEncoding(InputStream inStream, String encoding)
throws CertificateException {
+ if (inStream == null) {
+ throw new CertificateException("inStream == null");
+ }
+
Encoding enc = Encoding.findByApiName(encoding);
if (enc == null) {
throw new CertificateException("Invalid encoding: " + encoding);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java
index af960d5..1f48001 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509Certificate.java
@@ -309,7 +309,7 @@ public class OpenSSLX509Certificate extends X509Certificate {
@Override
public int getBasicConstraints() {
- if (NativeCrypto.X509_check_ca(mContext) != 1) {
+ if ((NativeCrypto.get_X509_ex_flags(mContext) & NativeCrypto.EXFLAG_CA) == 0) {
return -1;
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java
index 09e2507..ca79785 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLX509CertificateFactory.java
@@ -116,8 +116,11 @@ public class OpenSSLX509CertificateFactory extends CertificateFactorySpi {
public Collection<? extends T> generateItems(InputStream inStream)
throws ParsingException {
+ if (inStream == null) {
+ throw new ParsingException("inStream == null");
+ }
try {
- if (inStream == null || inStream.available() == 0) {
+ if (inStream.available() == 0) {
return Collections.emptyList();
}
} catch (IOException e) {
@@ -291,6 +294,10 @@ public class OpenSSLX509CertificateFactory extends CertificateFactorySpi {
@Override
public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException {
+ if (inStream == null) {
+ return Collections.emptyList();
+ }
+
try {
return crlParser.generateItems(inStream);
} catch (ParsingException e) {
diff --git a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index ee6a57e..65ac31f 100644
--- a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -394,6 +394,9 @@ static void throwForEvpError(JNIEnv* env, int reason, const char *message) {
case EVP_R_WRONG_PUBLIC_KEY_TYPE:
throwSignatureException(env, message);
break;
+ case EVP_R_UNSUPPORTED_ALGORITHM:
+ throwNoSuchAlgorithmException(env, message);
+ break;
default:
jniThrowRuntimeException(env, message);
break;
@@ -2360,6 +2363,22 @@ static jbyteArray NativeCrypto_EC_GROUP_get_order(JNIEnv* env, jclass, jlong gro
return orderArray;
}
+static jint NativeCrypto_EC_GROUP_get_degree(JNIEnv* env, jclass, jlong groupRef)
+{
+ const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+ JNI_TRACE("EC_GROUP_get_degree(%p)", group);
+
+ jint degree = EC_GROUP_get_degree(group);
+ if (degree == 0) {
+ JNI_TRACE("EC_GROUP_get_degree(%p) => unsupported", group);
+ jniThrowRuntimeException(env, "not supported");
+ return 0;
+ }
+
+ JNI_TRACE("EC_GROUP_get_degree(%p) => %d", group, degree);
+ return degree;
+}
+
static jbyteArray NativeCrypto_EC_GROUP_get_cofactor(JNIEnv* env, jclass, jlong groupRef)
{
const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
@@ -4672,6 +4691,13 @@ static jbyteArray NativeCrypto_i2d_X509(JNIEnv* env, jclass, jlong x509Ref) {
return ASN1ToByteArray<X509, i2d_X509>(env, x509);
}
+static jbyteArray NativeCrypto_i2d_X509_PUBKEY(JNIEnv* env, jclass, jlong x509Ref) {
+ X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+ JNI_TRACE("i2d_X509_PUBKEY(%p)", x509);
+ return ASN1ToByteArray<X509_PUBKEY, i2d_X509_PUBKEY>(env, X509_get_X509_PUBKEY(x509));
+}
+
+
template<typename T, T* (*PEM_read_func)(BIO*, T**, pem_password_cb*, void*)>
static jlong PEM_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
@@ -4686,6 +4712,10 @@ static jlong PEM_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
T* x = PEM_read_func(bio, NULL, NULL, NULL);
if (x == NULL) {
throwExceptionIfNecessary(env, "PEM_ASN1Object_to_jlong");
+ // Sometimes the PEM functions fail without pushing an error
+ if (!env->ExceptionCheck()) {
+ jniThrowRuntimeException(env, "Failure parsing PEM");
+ }
JNI_TRACE("PEM_ASN1Object_to_jlong(%p) => threw exception", bio);
return 0;
}
@@ -5154,21 +5184,6 @@ static jobjectArray NativeCrypto_get_X509_ex_xkusage(JNIEnv* env, jclass, jlong
return exKeyUsage.release();
}
-static jint NativeCrypto_X509_check_ca(JNIEnv* env, jclass, jlong x509Ref) {
- X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
- JNI_TRACE("X509_check_ca(%p)", x509);
-
- if (x509 == NULL) {
- jniThrowNullPointerException(env, "x509 == null");
- JNI_TRACE("X509_check_ca(%p) => x509 == null", x509);
- return 0;
- }
-
- int ret = X509_check_ca(x509);
- JNI_TRACE("X509_check_ca(%p) => %d", x509, ret);
- return ret;
-}
-
static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref) {
X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
JNI_TRACE("get_X509_ex_pathlen(%p)", x509);
@@ -7821,6 +7836,7 @@ static JNINativeMethod sNativeCryptoMethods[] = {
NATIVE_METHOD(NativeCrypto, EC_GROUP_get_curve_name, "(J)Ljava/lang/String;"),
NATIVE_METHOD(NativeCrypto, EC_GROUP_get_curve, "(J)[[B"),
NATIVE_METHOD(NativeCrypto, EC_GROUP_get_order, "(J)[B"),
+ NATIVE_METHOD(NativeCrypto, EC_GROUP_get_degree, "(J)I"),
NATIVE_METHOD(NativeCrypto, EC_GROUP_get_cofactor, "(J)[B"),
NATIVE_METHOD(NativeCrypto, EC_GROUP_clear_free, "(J)V"),
NATIVE_METHOD(NativeCrypto, EC_GROUP_cmp, "(JJ)Z"),
@@ -7882,6 +7898,7 @@ static JNINativeMethod sNativeCryptoMethods[] = {
NATIVE_METHOD(NativeCrypto, d2i_X509_bio, "(J)J"),
NATIVE_METHOD(NativeCrypto, d2i_X509, "([B)J"),
NATIVE_METHOD(NativeCrypto, i2d_X509, "(J)[B"),
+ NATIVE_METHOD(NativeCrypto, i2d_X509_PUBKEY, "(J)[B"),
NATIVE_METHOD(NativeCrypto, PEM_read_bio_X509, "(J)J"),
NATIVE_METHOD(NativeCrypto, PEM_read_bio_PKCS7, "(JI)[J"),
NATIVE_METHOD(NativeCrypto, d2i_PKCS7_bio, "(JI)[J"),
@@ -7902,7 +7919,6 @@ static JNINativeMethod sNativeCryptoMethods[] = {
NATIVE_METHOD(NativeCrypto, get_X509_subjectUID, "(J)[Z"),
NATIVE_METHOD(NativeCrypto, get_X509_ex_kusage, "(J)[Z"),
NATIVE_METHOD(NativeCrypto, get_X509_ex_xkusage, "(J)[Ljava/lang/String;"),
- NATIVE_METHOD(NativeCrypto, X509_check_ca, "(J)I"),
NATIVE_METHOD(NativeCrypto, get_X509_ex_pathlen, "(J)I"),
NATIVE_METHOD(NativeCrypto, X509_get_ext_oid, "(JLjava/lang/String;)[B"),
NATIVE_METHOD(NativeCrypto, X509_CRL_get_ext_oid, "(JLjava/lang/String;)[B"),
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 1f2ebf9..ac38eb8 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -16,14 +16,11 @@
package libcore.java.net;
+import com.android.okhttp.internal.http.HttpResponseCache;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.RecordedRequest;
import com.google.mockwebserver.SocketPolicy;
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
-import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
-import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
import dalvik.system.CloseGuard;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
@@ -49,21 +46,15 @@ import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.TimeZone;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
@@ -81,9 +72,13 @@ import junit.framework.TestCase;
import libcore.java.lang.ref.FinalizationTester;
import libcore.java.security.TestKeyStore;
import libcore.javax.net.ssl.TestSSLContext;
-import libcore.net.http.HttpResponseCache;
import tests.net.StuckServer;
+import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
+import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
+import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
+import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
+
public final class URLConnectionTest extends TestCase {
private MockWebServer server = new MockWebServer();
private HttpResponseCache cache;
diff --git a/luni/src/test/java/libcore/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java
index a4e5eaf..ae90d49 100644
--- a/luni/src/test/java/libcore/java/nio/BufferTest.java
+++ b/luni/src/test/java/libcore/java/nio/BufferTest.java
@@ -715,7 +715,7 @@ public class BufferTest extends TestCase {
public void testHasArrayOnJniDirectByteBuffer() throws Exception {
// Simulate a call to JNI's NewDirectByteBuffer.
Class<?> c = Class.forName("java.nio.DirectByteBuffer");
- Constructor<?> ctor = c.getDeclaredConstructor(int.class, int.class);
+ Constructor<?> ctor = c.getDeclaredConstructor(long.class, int.class);
ctor.setAccessible(true);
ByteBuffer bb = (ByteBuffer) ctor.newInstance(0, 0);
diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java
index b0522a3..5de67da 100644
--- a/luni/src/test/java/libcore/java/util/LocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/LocaleTest.java
@@ -25,6 +25,7 @@ import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
+import java.util.MissingResourceException;
public class LocaleTest extends junit.framework.TestCase {
// http://b/2611311; if there's no display language/country/variant, use the raw codes.
@@ -125,4 +126,42 @@ public class LocaleTest extends junit.framework.TestCase {
}
assertEquals(1, count);
}
-}
+
+ public void test_getISO3Country() {
+ // Empty country code.
+ assertEquals("", new Locale("en", "").getISO3Country());
+
+ // Invalid country code.
+ try {
+ assertEquals("", new Locale("en", "XX").getISO3Country());
+ fail();
+ } catch (MissingResourceException expected) {
+ assertEquals("FormatData_en_XX", expected.getClassName());
+ assertEquals("ShortCountry", expected.getKey());
+ }
+
+ // Valid country code.
+ assertEquals("CAN", new Locale("", "CA").getISO3Country());
+ assertEquals("CAN", new Locale("en", "CA").getISO3Country());
+ assertEquals("CAN", new Locale("xx", "CA").getISO3Country());
+ }
+
+ public void test_getISO3Language() {
+ // Empty language code.
+ assertEquals("", new Locale("", "US").getISO3Language());
+
+ // Invalid language code.
+ try {
+ assertEquals("", new Locale("xx", "US").getISO3Language());
+ fail();
+ } catch (MissingResourceException expected) {
+ assertEquals("FormatData_xx_US", expected.getClassName());
+ assertEquals("ShortLanguage", expected.getKey());
+ }
+
+ // Valid language code.
+ assertEquals("eng", new Locale("en", "").getISO3Language());
+ assertEquals("eng", new Locale("en", "CA").getISO3Language());
+ assertEquals("eng", new Locale("en", "XX").getISO3Language());
+ }
+ }
diff --git a/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java b/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java
deleted file mode 100644
index 133924e..0000000
--- a/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java
+++ /dev/null
@@ -1,1886 +0,0 @@
-/*
- * Copyright (C) 2011 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 libcore.net.http;
-
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.MockWebServer;
-import com.google.mockwebserver.RecordedRequest;
-import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationHandler;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.CookieHandler;
-import java.net.CookieManager;
-import java.net.HttpCookie;
-import java.net.HttpURLConnection;
-import java.net.ResponseCache;
-import java.net.SecureCacheResponse;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.zip.GZIPOutputStream;
-import javax.net.ssl.HttpsURLConnection;
-import junit.framework.TestCase;
-import libcore.javax.net.ssl.TestSSLContext;
-import tests.io.MockOs;
-
-public final class HttpResponseCacheTest extends TestCase {
- private MockWebServer server = new MockWebServer();
- private HttpResponseCache cache;
- private final MockOs mockOs = new MockOs();
- private final CookieManager cookieManager = new CookieManager();
-
- @Override protected void setUp() throws Exception {
- super.setUp();
-
- String tmp = System.getProperty("java.io.tmpdir");
- File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
- cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
- ResponseCache.setDefault(cache);
- mockOs.install();
- CookieHandler.setDefault(cookieManager);
- }
-
- @Override protected void tearDown() throws Exception {
- mockOs.uninstall();
- server.shutdown();
- ResponseCache.setDefault(null);
- cache.getCache().delete();
- CookieHandler.setDefault(null);
- super.tearDown();
- }
-
- /**
- * Test that response caching is consistent with the RI and the spec.
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
- */
- public void testResponseCachingByResponseCode() throws Exception {
- // Test each documented HTTP/1.1 code, plus the first unused value in each range.
- // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
-
- // We can't test 100 because it's not really a response.
- // assertCached(false, 100);
- assertCached(false, 101);
- assertCached(false, 102);
- assertCached(true, 200);
- assertCached(false, 201);
- assertCached(false, 202);
- assertCached(true, 203);
- assertCached(false, 204);
- assertCached(false, 205);
- assertCached(false, 206); // we don't cache partial responses
- assertCached(false, 207);
- assertCached(true, 300);
- assertCached(true, 301);
- for (int i = 302; i <= 308; ++i) {
- assertCached(false, i);
- }
- for (int i = 400; i <= 406; ++i) {
- assertCached(false, i);
- }
- // (See test_responseCaching_407.)
- assertCached(false, 408);
- assertCached(false, 409);
- // (See test_responseCaching_410.)
- for (int i = 411; i <= 418; ++i) {
- assertCached(false, i);
- }
- for (int i = 500; i <= 506; ++i) {
- assertCached(false, i);
- }
- }
-
- /**
- * Response code 407 should only come from proxy servers. Android's client
- * throws if it is sent by an origin server.
- */
- public void testOriginServerSends407() throws Exception {
- server.enqueue(new MockResponse().setResponseCode(407));
- server.play();
-
- URL url = server.getUrl("/");
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- try {
- conn.getResponseCode();
- fail();
- } catch (IOException expected) {
- }
- }
-
- public void test_responseCaching_410() throws Exception {
- // the HTTP spec permits caching 410s, but the RI doesn't.
- assertCached(true, 410);
- }
-
- private void assertCached(boolean shouldPut, int responseCode) throws Exception {
- server = new MockWebServer();
- MockResponse response = new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setResponseCode(responseCode)
- .setBody("ABCDE")
- .addHeader("WWW-Authenticate: challenge");
- if (responseCode == HttpURLConnection.HTTP_PROXY_AUTH) {
- response.addHeader("Proxy-Authenticate: Basic realm=\"protected area\"");
- } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
- response.addHeader("WWW-Authenticate: Basic realm=\"protected area\"");
- }
- server.enqueue(response);
- server.play();
-
- URL url = server.getUrl("/");
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- assertEquals(responseCode, conn.getResponseCode());
-
- // exhaust the content stream
- readAscii(conn);
-
- CacheResponse cached = cache.get(url.toURI(), "GET",
- Collections.<String, List<String>>emptyMap());
- if (shouldPut) {
- assertNotNull(Integer.toString(responseCode), cached);
- cached.getBody().close();
- } else {
- assertNull(Integer.toString(responseCode), cached);
- }
- server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
- }
-
- /**
- * Test that we can interrogate the response when the cache is being
- * populated. http://code.google.com/p/android/issues/detail?id=7787
- */
- public void testResponseCacheCallbackApis() throws Exception {
- final String body = "ABCDE";
- final AtomicInteger cacheCount = new AtomicInteger();
-
- server.enqueue(new MockResponse()
- .setStatus("HTTP/1.1 200 Fantastic")
- .addHeader("fgh: ijk")
- .setBody(body));
- server.play();
-
- ResponseCache.setDefault(new ResponseCache() {
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) throws IOException {
- return null;
- }
- @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
- HttpURLConnection httpConnection = (HttpURLConnection) conn;
- try {
- httpConnection.getRequestProperties();
- fail();
- } catch (IllegalStateException expected) {
- }
- try {
- httpConnection.addRequestProperty("K", "V");
- fail();
- } catch (IllegalStateException expected) {
- }
- assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
- assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"),
- httpConnection.getHeaderFields().get(null));
- assertEquals(200, httpConnection.getResponseCode());
- assertEquals("Fantastic", httpConnection.getResponseMessage());
- assertEquals(body.length(), httpConnection.getContentLength());
- assertEquals("ijk", httpConnection.getHeaderField("fgh"));
- try {
- httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
- fail();
- } catch (IOException expected) {
- }
- cacheCount.incrementAndGet();
- return null;
- }
- });
-
- URL url = server.getUrl("/");
- URLConnection connection = url.openConnection();
- assertEquals(body, readAscii(connection));
- assertEquals(1, cacheCount.get());
- }
-
-
- public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException {
- testResponseCaching(TransferKind.FIXED_LENGTH);
- }
-
- public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException {
- testResponseCaching(TransferKind.CHUNKED);
- }
-
- public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException {
- testResponseCaching(TransferKind.END_OF_STREAM);
- }
-
- /**
- * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption
- * http://code.google.com/p/android/issues/detail?id=8175
- */
- private void testResponseCaching(TransferKind transferKind) throws IOException {
- MockResponse response = new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setStatus("HTTP/1.1 200 Fantastic");
- transferKind.setBody(response, "I love puppies but hate spiders", 1);
- server.enqueue(response);
- server.play();
-
- // Make sure that calling skip() doesn't omit bytes from the cache.
- HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection();
- InputStream in = urlConnection.getInputStream();
- assertEquals("I love ", readAscii(urlConnection, "I love ".length()));
- reliableSkip(in, "puppies but hate ".length());
- assertEquals("spiders", readAscii(urlConnection, "spiders".length()));
- assertEquals(-1, in.read());
- in.close();
- assertEquals(1, cache.getWriteSuccessCount());
- assertEquals(0, cache.getWriteAbortCount());
-
- urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
- in = urlConnection.getInputStream();
- assertEquals("I love puppies but hate spiders",
- readAscii(urlConnection, "I love puppies but hate spiders".length()));
- assertEquals(200, urlConnection.getResponseCode());
- assertEquals("Fantastic", urlConnection.getResponseMessage());
-
- assertEquals(-1, in.read());
- in.close();
- assertEquals(1, cache.getWriteSuccessCount());
- assertEquals(0, cache.getWriteAbortCount());
- assertEquals(2, cache.getRequestCount());
- assertEquals(1, cache.getHitCount());
- }
-
- public void testSecureResponseCaching() throws IOException {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setBody("ABC"));
- server.play();
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection));
-
- // OpenJDK 6 fails on this line, complaining that the connection isn't open yet
- String suite = connection.getCipherSuite();
- List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates());
- List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates());
- Principal peerPrincipal = connection.getPeerPrincipal();
- Principal localPrincipal = connection.getLocalPrincipal();
-
- connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection));
-
- assertEquals(2, cache.getRequestCount());
- assertEquals(1, cache.getNetworkCount());
- assertEquals(1, cache.getHitCount());
-
- assertEquals(suite, connection.getCipherSuite());
- assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
- assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
- assertEquals(peerPrincipal, connection.getPeerPrincipal());
- assertEquals(localPrincipal, connection.getLocalPrincipal());
- }
-
- public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse().setBody("ABC"));
- server.enqueue(new MockResponse().setBody("DEF"));
- server.play();
-
- ResponseCache.setDefault(new InsecureResponseCache());
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection));
-
- connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("DEF", readAscii(connection));
- }
-
- public void testResponseCachingAndRedirects() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
- .addHeader("Location: /foo"));
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setBody("ABC"));
- server.enqueue(new MockResponse().setBody("DEF"));
- server.play();
-
- URLConnection connection = server.getUrl("/").openConnection();
- assertEquals("ABC", readAscii(connection));
-
- connection = server.getUrl("/").openConnection(); // cached!
- assertEquals("ABC", readAscii(connection));
-
- assertEquals(4, cache.getRequestCount()); // 2 requests + 2 redirects
- assertEquals(2, cache.getNetworkCount());
- assertEquals(2, cache.getHitCount());
- }
-
- public void testRedirectToCachedResult() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .setBody("ABC"));
- server.enqueue(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
- .addHeader("Location: /foo"));
- server.enqueue(new MockResponse().setBody("DEF"));
- server.play();
-
- assertEquals("ABC", readAscii(server.getUrl("/foo").openConnection()));
- RecordedRequest request1 = server.takeRequest();
- assertEquals("GET /foo HTTP/1.1", request1.getRequestLine());
- assertEquals(0, request1.getSequenceNumber());
-
- assertEquals("ABC", readAscii(server.getUrl("/bar").openConnection()));
- RecordedRequest request2 = server.takeRequest();
- assertEquals("GET /bar HTTP/1.1", request2.getRequestLine());
- assertEquals(1, request2.getSequenceNumber());
-
- // an unrelated request should reuse the pooled connection
- assertEquals("DEF", readAscii(server.getUrl("/baz").openConnection()));
- RecordedRequest request3 = server.takeRequest();
- assertEquals("GET /baz HTTP/1.1", request3.getRequestLine());
- assertEquals(2, request3.getSequenceNumber());
- }
-
- public void testSecureResponseCachingAndRedirects() throws IOException {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
- .addHeader("Location: /foo"));
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .setBody("ABC"));
- server.enqueue(new MockResponse().setBody("DEF"));
- server.play();
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection));
-
- connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection));
-
- assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4
- assertEquals(2, cache.getHitCount());
- }
-
- public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException {
- server.enqueue(new MockResponse().setBody("ABC"));
- server.play();
-
- final AtomicReference<Map<String, List<String>>> requestHeadersRef
- = new AtomicReference<Map<String, List<String>>>();
- ResponseCache.setDefault(new ResponseCache() {
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) throws IOException {
- requestHeadersRef.set(requestHeaders);
- return null;
- }
- @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
- return null;
- }
- });
-
- URL url = server.getUrl("/");
- URLConnection urlConnection = url.openConnection();
- urlConnection.addRequestProperty("A", "android");
- readAscii(urlConnection);
- assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
- }
-
-
- public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException {
- testServerPrematureDisconnect(TransferKind.FIXED_LENGTH);
- }
-
- public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException {
- testServerPrematureDisconnect(TransferKind.CHUNKED);
- }
-
- public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException {
- /*
- * Intentionally empty. This case doesn't make sense because there's no
- * such thing as a premature disconnect when the disconnect itself
- * indicates the end of the data stream.
- */
- }
-
- private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException {
- MockResponse response = new MockResponse();
- transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16);
- server.enqueue(truncateViolently(response, 16));
- server.enqueue(new MockResponse().setBody("Request #2"));
- server.play();
-
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- server.getUrl("/").openConnection().getInputStream()));
- assertEquals("ABCDE", reader.readLine());
- try {
- reader.readLine();
- fail("This implementation silently ignored a truncated HTTP body.");
- } catch (IOException expected) {
- } finally {
- reader.close();
- }
-
- assertEquals(1, cache.getWriteAbortCount());
- assertEquals(0, cache.getWriteSuccessCount());
- URLConnection connection = server.getUrl("/").openConnection();
- assertEquals("Request #2", readAscii(connection));
- assertEquals(1, cache.getWriteAbortCount());
- assertEquals(1, cache.getWriteSuccessCount());
- }
-
- public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException {
- testClientPrematureDisconnect(TransferKind.FIXED_LENGTH);
- }
-
- public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException {
- testClientPrematureDisconnect(TransferKind.CHUNKED);
- }
-
- public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException {
- testClientPrematureDisconnect(TransferKind.END_OF_STREAM);
- }
-
- private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException {
- MockResponse response = new MockResponse();
- transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024);
- server.enqueue(response);
- server.enqueue(new MockResponse().setBody("Request #2"));
- server.play();
-
- URLConnection connection = server.getUrl("/").openConnection();
- InputStream in = connection.getInputStream();
- assertEquals("ABCDE", readAscii(connection, 5));
- in.close();
- try {
- in.read();
- fail("Expected an IOException because the stream is closed.");
- } catch (IOException expected) {
- }
-
- assertEquals(1, cache.getWriteAbortCount());
- assertEquals(0, cache.getWriteSuccessCount());
- connection = server.getUrl("/").openConnection();
- assertEquals("Request #2", readAscii(connection));
- assertEquals(1, cache.getWriteAbortCount());
- assertEquals(1, cache.getWriteSuccessCount());
- }
-
- public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception {
- // last modified: 105 seconds ago
- // served: 5 seconds ago
- // default lifetime: (105 - 5) / 10 = 10 seconds
- // expires: 10 seconds from served date = 5 seconds from now
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
- .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
- .setBody("A"));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
- URLConnection connection = url.openConnection();
- assertEquals("A", readAscii(connection));
- assertNull(connection.getHeaderField("Warning"));
- }
-
- public void testDefaultExpirationDateConditionallyCached() throws Exception {
- // last modified: 115 seconds ago
- // served: 15 seconds ago
- // default lifetime: (115 - 15) / 10 = 10 seconds
- // expires: 10 seconds from served date = 5 seconds ago
- String lastModifiedDate = formatDate(-115, TimeUnit.SECONDS);
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Date: " + formatDate(-15, TimeUnit.SECONDS)));
- List<String> headers = conditionalRequest.getHeaders();
- assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception {
- // last modified: 105 days ago
- // served: 5 days ago
- // default lifetime: (105 - 5) / 10 = 10 days
- // expires: 10 days from served date = 5 days from now
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.DAYS))
- .addHeader("Date: " + formatDate(-5, TimeUnit.DAYS))
- .setBody("A"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- URLConnection connection = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection));
- assertEquals("113 HttpURLConnection \"Heuristic expiration\"",
- connection.getHeaderField("Warning"));
- }
-
- public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS))
- .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS))
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/?foo=bar");
- assertEquals("A", readAscii(url.openConnection()));
- assertEquals("B", readAscii(url.openConnection()));
- }
-
- public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception {
- String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
- List<String> headers = conditionalRequest.getHeaders();
- assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception {
- assertNotCached(new MockResponse()
- .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
- }
-
- public void testExpirationDateInTheFuture() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
- }
-
- public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception {
- String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Cache-Control: max-age=60"));
- List<String> headers = conditionalRequest.getHeaders();
- assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception {
- /*
- * Chrome interprets max-age relative to the local clock. Both our cache
- * and Firefox both use the earlier of the local and server's clock.
- */
- assertNotCached(new MockResponse()
- .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS))
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testMaxAgeInTheFutureWithDateHeader() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
- .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
- .addHeader("Cache-Control: s-maxage=60")
- .addHeader("Cache-Control: max-age=180"));
- }
-
- public void testMaxAgePreferredOverHigherMaxAge() throws Exception {
- assertNotCached(new MockResponse()
- .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
- .addHeader("Cache-Control: s-maxage=180")
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testRequestMethodOptionsIsNotCached() throws Exception {
- testRequestMethod("OPTIONS", false);
- }
-
- public void testRequestMethodGetIsCached() throws Exception {
- testRequestMethod("GET", true);
- }
-
- public void testRequestMethodHeadIsNotCached() throws Exception {
- // We could support this but choose not to for implementation simplicity
- testRequestMethod("HEAD", false);
- }
-
- public void testRequestMethodPostIsNotCached() throws Exception {
- // We could support this but choose not to for implementation simplicity
- testRequestMethod("POST", false);
- }
-
- public void testRequestMethodPutIsNotCached() throws Exception {
- testRequestMethod("PUT", false);
- }
-
- public void testRequestMethodDeleteIsNotCached() throws Exception {
- testRequestMethod("DELETE", false);
- }
-
- public void testRequestMethodTraceIsNotCached() throws Exception {
- testRequestMethod("TRACE", false);
- }
-
- private void testRequestMethod(String requestMethod, boolean expectCached) throws Exception {
- /*
- * 1. seed the cache (potentially)
- * 2. expect a cache hit or miss
- */
- server.enqueue(new MockResponse()
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .addHeader("X-Response-ID: 1"));
- server.enqueue(new MockResponse()
- .addHeader("X-Response-ID: 2"));
- server.play();
-
- URL url = server.getUrl("/");
-
- HttpURLConnection request1 = (HttpURLConnection) url.openConnection();
- request1.setRequestMethod(requestMethod);
- addRequestBodyIfNecessary(requestMethod, request1);
- assertEquals("1", request1.getHeaderField("X-Response-ID"));
-
- URLConnection request2 = url.openConnection();
- if (expectCached) {
- assertEquals("1", request1.getHeaderField("X-Response-ID"));
- } else {
- assertEquals("2", request2.getHeaderField("X-Response-ID"));
- }
- }
-
- public void testPostInvalidatesCache() throws Exception {
- testMethodInvalidates("POST");
- }
-
- public void testPutInvalidatesCache() throws Exception {
- testMethodInvalidates("PUT");
- }
-
- public void testDeleteMethodInvalidatesCache() throws Exception {
- testMethodInvalidates("DELETE");
- }
-
- private void testMethodInvalidates(String requestMethod) throws Exception {
- /*
- * 1. seed the cache
- * 2. invalidate it
- * 3. expect a cache miss
- */
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
- server.enqueue(new MockResponse().setBody("B"));
- server.enqueue(new MockResponse().setBody("C"));
- server.play();
-
- URL url = server.getUrl("/");
-
- assertEquals("A", readAscii(url.openConnection()));
-
- HttpURLConnection invalidate = (HttpURLConnection) url.openConnection();
- invalidate.setRequestMethod(requestMethod);
- addRequestBodyIfNecessary(requestMethod, invalidate);
- assertEquals("B", readAscii(invalidate));
-
- assertEquals("C", readAscii(url.openConnection()));
- }
-
- public void testEtag() throws Exception {
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("ETag: v1"));
- assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1"));
- }
-
- public void testEtagAndExpirationDateInThePast() throws Exception {
- String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("ETag: v1")
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
- List<String> headers = conditionalRequest.getHeaders();
- assertTrue(headers.contains("If-None-Match: v1"));
- assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- public void testEtagAndExpirationDateInTheFuture() throws Exception {
- assertFullyCached(new MockResponse()
- .addHeader("ETag: v1")
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
- }
-
- public void testCacheControlNoCache() throws Exception {
- assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache"));
- }
-
- public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception {
- String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .addHeader("Cache-Control: no-cache"));
- List<String> headers = conditionalRequest.getHeaders();
- assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- public void testPragmaNoCache() throws Exception {
- assertNotCached(new MockResponse().addHeader("Pragma: no-cache"));
- }
-
- public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception {
- String lastModifiedDate = formatDate(-2, TimeUnit.HOURS);
- RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse()
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .addHeader("Pragma: no-cache"));
- List<String> headers = conditionalRequest.getHeaders();
- assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- public void testCacheControlNoStore() throws Exception {
- assertNotCached(new MockResponse().addHeader("Cache-Control: no-store"));
- }
-
- public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception {
- assertNotCached(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .addHeader("Cache-Control: no-store"));
- }
-
- public void testPartialRangeResponsesDoNotCorruptCache() throws Exception {
- /*
- * 1. request a range
- * 2. request a full document, expecting a cache miss
- */
- server.enqueue(new MockResponse().setBody("AA")
- .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
- .addHeader("Content-Range: bytes 1000-1001/2000"));
- server.enqueue(new MockResponse().setBody("BB"));
- server.play();
-
- URL url = server.getUrl("/");
-
- URLConnection range = url.openConnection();
- range.addRequestProperty("Range", "bytes=1000-1001");
- assertEquals("AA", readAscii(range));
-
- assertEquals("BB", readAscii(url.openConnection()));
- }
-
- public void testServerReturnsDocumentOlderThanCache() throws Exception {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
- server.enqueue(new MockResponse().setBody("B")
- .addHeader("Last-Modified: " + formatDate(-4, TimeUnit.HOURS)));
- server.play();
-
- URL url = server.getUrl("/");
-
- assertEquals("A", readAscii(url.openConnection()));
- assertEquals("A", readAscii(url.openConnection()));
- }
-
- public void testNonIdentityEncodingAndConditionalCache() throws Exception {
- assertNonIdentityEncodingCached(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)));
- }
-
- public void testNonIdentityEncodingAndFullCache() throws Exception {
- assertNonIdentityEncodingCached(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
- }
-
- private void assertNonIdentityEncodingCached(MockResponse response) throws Exception {
- server.enqueue(response
- .setBody(gzip("ABCABCABC".getBytes("UTF-8")))
- .addHeader("Content-Encoding: gzip"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
-
- server.play();
- assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
- assertEquals("ABCABCABC", readAscii(server.getUrl("/").openConnection()));
- }
-
- public void testExpiresDateBeforeModifiedDate() throws Exception {
- assertConditionallyCached(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS)));
- }
-
- public void testRequestMaxAge() throws IOException {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS))
- .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))
- .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)));
- server.enqueue(new MockResponse().setBody("B"));
-
- server.play();
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
-
- URLConnection connection = server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "max-age=30");
- assertEquals("B", readAscii(connection));
- }
-
- public void testRequestMinFresh() throws IOException {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
- server.enqueue(new MockResponse().setBody("B"));
-
- server.play();
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
-
- URLConnection connection = server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "min-fresh=120");
- assertEquals("B", readAscii(connection));
- }
-
- public void testRequestMaxStale() throws IOException {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Cache-Control: max-age=120")
- .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
- server.enqueue(new MockResponse().setBody("B"));
-
- server.play();
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
-
- URLConnection connection = server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "max-stale=180");
- assertEquals("A", readAscii(connection));
- assertEquals("110 HttpURLConnection \"Response is stale\"",
- connection.getHeaderField("Warning"));
- }
-
- public void testRequestMaxStaleNotHonoredWithMustRevalidate() throws IOException {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Cache-Control: max-age=120, must-revalidate")
- .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES)));
- server.enqueue(new MockResponse().setBody("B"));
-
- server.play();
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
-
- URLConnection connection = server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "max-stale=180");
- assertEquals("B", readAscii(connection));
- }
-
- public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
- // (no responses enqueued)
- server.play();
-
- HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "only-if-cached");
- assertGatewayTimeout(connection);
- }
-
- public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Cache-Control: max-age=30")
- .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES)));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- URLConnection connection = server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "only-if-cached");
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- }
-
- public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException {
- server.enqueue(new MockResponse().setBody("A")
- .addHeader("Cache-Control: max-age=30")
- .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "only-if-cached");
- assertGatewayTimeout(connection);
- }
-
- public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException {
- server.enqueue(new MockResponse().setBody("A"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
- connection.addRequestProperty("Cache-Control", "only-if-cached");
- assertGatewayTimeout(connection);
- }
-
- public void testRequestCacheControlNoCache() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
- .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
- .addHeader("Cache-Control: max-age=60")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
- URLConnection connection = url.openConnection();
- connection.setRequestProperty("Cache-Control", "no-cache");
- assertEquals("B", readAscii(connection));
- }
-
- public void testRequestPragmaNoCache() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS))
- .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS))
- .addHeader("Cache-Control: max-age=60")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
- URLConnection connection = url.openConnection();
- connection.setRequestProperty("Pragma", "no-cache");
- assertEquals("B", readAscii(connection));
- }
-
- public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception {
- MockResponse response = new MockResponse()
- .addHeader("ETag: v3")
- .addHeader("Cache-Control: max-age=0");
- String ifModifiedSinceDate = formatDate(-24, TimeUnit.HOURS);
- RecordedRequest request = assertClientSuppliedCondition(
- response, "If-Modified-Since", ifModifiedSinceDate);
- List<String> headers = request.getHeaders();
- assertTrue(headers.contains("If-Modified-Since: " + ifModifiedSinceDate));
- assertFalse(headers.contains("If-None-Match: v3"));
- }
-
- public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception {
- String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES);
- MockResponse response = new MockResponse()
- .addHeader("Last-Modified: " + lastModifiedDate)
- .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES))
- .addHeader("Cache-Control: max-age=0");
- RecordedRequest request = assertClientSuppliedCondition(
- response, "If-None-Match", "v1");
- List<String> headers = request.getHeaders();
- assertTrue(headers.contains("If-None-Match: v1"));
- assertFalse(headers.contains("If-Modified-Since: " + lastModifiedDate));
- }
-
- private RecordedRequest assertClientSuppliedCondition(MockResponse seed, String conditionName,
- String conditionValue) throws Exception {
- server.enqueue(seed.setBody("A"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
-
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.addRequestProperty(conditionName, conditionValue);
- assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
- assertEquals("", readAscii(connection));
-
- server.takeRequest(); // seed
- return server.takeRequest();
- }
-
- public void testSetIfModifiedSince() throws Exception {
- Date since = new Date();
- server.enqueue(new MockResponse().setBody("A"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection = url.openConnection();
- connection.setIfModifiedSince(since.getTime());
- assertEquals("A", readAscii(connection));
- RecordedRequest request = server.takeRequest();
- assertTrue(request.getHeaders().contains("If-Modified-Since: " + formatDate(since)));
- }
-
- public void testClientSuppliedConditionWithoutCachedResult() throws Exception {
- server.enqueue(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
- String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS);
- connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince);
- assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode());
- assertEquals("", readAscii(connection));
- }
-
- public void testAuthorizationRequestHeaderPreventsCaching() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES))
- .addHeader("Cache-Control: max-age=60")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection = url.openConnection();
- connection.addRequestProperty("Authorization", "password");
- assertEquals("A", readAscii(connection));
- assertEquals("B", readAscii(url.openConnection()));
- }
-
- public void testAuthorizationResponseCachedWithSMaxAge() throws Exception {
- assertAuthorizationRequestFullyCached(new MockResponse()
- .addHeader("Cache-Control: s-maxage=60"));
- }
-
- public void testAuthorizationResponseCachedWithPublic() throws Exception {
- assertAuthorizationRequestFullyCached(new MockResponse()
- .addHeader("Cache-Control: public"));
- }
-
- public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception {
- assertAuthorizationRequestFullyCached(new MockResponse()
- .addHeader("Cache-Control: must-revalidate"));
- }
-
- public void assertAuthorizationRequestFullyCached(MockResponse response) throws Exception {
- server.enqueue(response
- .addHeader("Cache-Control: max-age=60")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection = url.openConnection();
- connection.addRequestProperty("Authorization", "password");
- assertEquals("A", readAscii(connection));
- assertEquals("A", readAscii(url.openConnection()));
- }
-
- public void testContentLocationDoesNotPopulateCache() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Content-Location: /bar")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/foo").openConnection()));
- assertEquals("B", readAscii(server.getUrl("/bar").openConnection()));
- }
-
- public void testUseCachesFalseDoesNotWriteToCache() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .setBody("A").setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URLConnection connection = server.getUrl("/").openConnection();
- connection.setUseCaches(false);
- assertEquals("A", readAscii(connection));
- assertEquals("B", readAscii(server.getUrl("/").openConnection()));
- }
-
- public void testUseCachesFalseDoesNotReadFromCache() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .setBody("A").setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- URLConnection connection = server.getUrl("/").openConnection();
- connection.setUseCaches(false);
- assertEquals("B", readAscii(connection));
- }
-
- public void testDefaultUseCachesSetsInitialValueOnly() throws Exception {
- URL url = new URL("http://localhost/");
- URLConnection c1 = url.openConnection();
- URLConnection c2 = url.openConnection();
- assertTrue(c1.getDefaultUseCaches());
- c1.setDefaultUseCaches(false);
- try {
- assertTrue(c1.getUseCaches());
- assertTrue(c2.getUseCaches());
- URLConnection c3 = url.openConnection();
- assertFalse(c3.getUseCaches());
- } finally {
- c1.setDefaultUseCaches(true);
- }
- }
-
- public void testConnectionIsReturnedToPoolAfterConditionalSuccess() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/a").openConnection()));
- assertEquals("A", readAscii(server.getUrl("/a").openConnection()));
- assertEquals("B", readAscii(server.getUrl("/b").openConnection()));
-
- assertEquals(0, server.takeRequest().getSequenceNumber());
- assertEquals(1, server.takeRequest().getSequenceNumber());
- assertEquals(2, server.takeRequest().getSequenceNumber());
- }
-
- public void testStatisticsConditionalCacheMiss() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.enqueue(new MockResponse().setBody("C"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals(1, cache.getRequestCount());
- assertEquals(1, cache.getNetworkCount());
- assertEquals(0, cache.getHitCount());
- assertEquals("B", readAscii(server.getUrl("/").openConnection()));
- assertEquals("C", readAscii(server.getUrl("/").openConnection()));
- assertEquals(3, cache.getRequestCount());
- assertEquals(3, cache.getNetworkCount());
- assertEquals(0, cache.getHitCount());
- }
-
- public void testStatisticsConditionalCacheHit() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals(1, cache.getRequestCount());
- assertEquals(1, cache.getNetworkCount());
- assertEquals(0, cache.getHitCount());
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals(3, cache.getRequestCount());
- assertEquals(3, cache.getNetworkCount());
- assertEquals(2, cache.getHitCount());
- }
-
- public void testStatisticsFullCacheHit() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .setBody("A"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals(1, cache.getRequestCount());
- assertEquals(1, cache.getNetworkCount());
- assertEquals(0, cache.getHitCount());
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals(3, cache.getRequestCount());
- assertEquals(1, cache.getNetworkCount());
- assertEquals(2, cache.getHitCount());
- }
-
- public void testVaryMatchesChangedRequestHeaderField() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- HttpURLConnection frConnection = (HttpURLConnection) url.openConnection();
- frConnection.addRequestProperty("Accept-Language", "fr-CA");
- assertEquals("A", readAscii(frConnection));
-
- HttpURLConnection enConnection = (HttpURLConnection) url.openConnection();
- enConnection.addRequestProperty("Accept-Language", "en-US");
- assertEquals("B", readAscii(enConnection));
- }
-
- public void testVaryMatchesUnchangedRequestHeaderField() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection1 = url.openConnection();
- connection1.addRequestProperty("Accept-Language", "fr-CA");
- assertEquals("A", readAscii(connection1));
- URLConnection connection2 = url.openConnection();
- connection2.addRequestProperty("Accept-Language", "fr-CA");
- assertEquals("A", readAscii(connection2));
- }
-
- public void testVaryMatchesAbsentRequestHeaderField() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Foo")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- }
-
- public void testVaryMatchesAddedRequestHeaderField() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Foo")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- URLConnection fooConnection = server.getUrl("/").openConnection();
- fooConnection.addRequestProperty("Foo", "bar");
- assertEquals("B", readAscii(fooConnection));
- }
-
- public void testVaryMatchesRemovedRequestHeaderField() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Foo")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URLConnection fooConnection = server.getUrl("/").openConnection();
- fooConnection.addRequestProperty("Foo", "bar");
- assertEquals("A", readAscii(fooConnection));
- assertEquals("B", readAscii(server.getUrl("/").openConnection()));
- }
-
- public void testVaryFieldsAreCaseInsensitive() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: ACCEPT-LANGUAGE")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection1 = url.openConnection();
- connection1.addRequestProperty("Accept-Language", "fr-CA");
- assertEquals("A", readAscii(connection1));
- URLConnection connection2 = url.openConnection();
- connection2.addRequestProperty("accept-language", "fr-CA");
- assertEquals("A", readAscii(connection2));
- }
-
- public void testVaryMultipleFieldsWithMatch() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language, Accept-Charset")
- .addHeader("Vary: Accept-Encoding")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection1 = url.openConnection();
- connection1.addRequestProperty("Accept-Language", "fr-CA");
- connection1.addRequestProperty("Accept-Charset", "UTF-8");
- connection1.addRequestProperty("Accept-Encoding", "identity");
- assertEquals("A", readAscii(connection1));
- URLConnection connection2 = url.openConnection();
- connection2.addRequestProperty("Accept-Language", "fr-CA");
- connection2.addRequestProperty("Accept-Charset", "UTF-8");
- connection2.addRequestProperty("Accept-Encoding", "identity");
- assertEquals("A", readAscii(connection2));
- }
-
- public void testVaryMultipleFieldsWithNoMatch() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language, Accept-Charset")
- .addHeader("Vary: Accept-Encoding")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection frConnection = url.openConnection();
- frConnection.addRequestProperty("Accept-Language", "fr-CA");
- frConnection.addRequestProperty("Accept-Charset", "UTF-8");
- frConnection.addRequestProperty("Accept-Encoding", "identity");
- assertEquals("A", readAscii(frConnection));
- URLConnection enConnection = url.openConnection();
- enConnection.addRequestProperty("Accept-Language", "en-CA");
- enConnection.addRequestProperty("Accept-Charset", "UTF-8");
- enConnection.addRequestProperty("Accept-Encoding", "identity");
- assertEquals("B", readAscii(enConnection));
- }
-
- public void testVaryMultipleFieldValuesWithMatch() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection1 = url.openConnection();
- connection1.addRequestProperty("Accept-Language", "fr-CA, fr-FR");
- connection1.addRequestProperty("Accept-Language", "en-US");
- assertEquals("A", readAscii(connection1));
-
- URLConnection connection2 = url.openConnection();
- connection2.addRequestProperty("Accept-Language", "fr-CA, fr-FR");
- connection2.addRequestProperty("Accept-Language", "en-US");
- assertEquals("A", readAscii(connection2));
- }
-
- public void testVaryMultipleFieldValuesWithNoMatch() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- URLConnection connection1 = url.openConnection();
- connection1.addRequestProperty("Accept-Language", "fr-CA, fr-FR");
- connection1.addRequestProperty("Accept-Language", "en-US");
- assertEquals("A", readAscii(connection1));
-
- URLConnection connection2 = url.openConnection();
- connection2.addRequestProperty("Accept-Language", "fr-CA");
- connection2.addRequestProperty("Accept-Language", "en-US");
- assertEquals("B", readAscii(connection2));
- }
-
- public void testVaryAsterisk() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: *")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- assertEquals("A", readAscii(server.getUrl("/").openConnection()));
- assertEquals("B", readAscii(server.getUrl("/").openConnection()));
- }
-
- public void testVaryAndHttps() throws Exception {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .addHeader("Vary: Accept-Language")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- HttpsURLConnection connection1 = (HttpsURLConnection) url.openConnection();
- connection1.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- connection1.addRequestProperty("Accept-Language", "en-US");
- assertEquals("A", readAscii(connection1));
-
- HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection();
- connection2.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- connection2.addRequestProperty("Accept-Language", "en-US");
- assertEquals("A", readAscii(connection2));
- }
-
- public void testDiskWriteFailureCacheDegradation() throws Exception {
- Deque<InvocationHandler> writeHandlers = mockOs.getHandlers("write");
- int i = 0;
- boolean hasMoreScenarios = true;
- while (hasMoreScenarios) {
- mockOs.enqueueNormal("write", i++);
- mockOs.enqueueFault("write");
- exercisePossiblyFaultyCache(false);
- hasMoreScenarios = writeHandlers.isEmpty();
- writeHandlers.clear();
- }
- System.out.println("Exercising the cache performs " + (i - 1) + " writes.");
- }
-
- public void testDiskReadFailureCacheDegradation() throws Exception {
- Deque<InvocationHandler> readHandlers = mockOs.getHandlers("read");
- int i = 0;
- boolean hasMoreScenarios = true;
- while (hasMoreScenarios) {
- mockOs.enqueueNormal("read", i++);
- mockOs.enqueueFault("read");
- exercisePossiblyFaultyCache(true);
- hasMoreScenarios = readHandlers.isEmpty();
- readHandlers.clear();
- }
- System.out.println("Exercising the cache performs " + (i - 1) + " reads.");
- }
-
- public void testCachePlusCookies() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Set-Cookie: a=FIRST; domain=" + server.getCookieDomain() + ";")
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse()
- .addHeader("Set-Cookie: a=SECOND; domain=" + server.getCookieDomain() + ";")
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
- assertCookies(url, "a=FIRST");
- assertEquals("A", readAscii(url.openConnection()));
- assertCookies(url, "a=SECOND");
- }
-
- public void testGetHeadersReturnsNetworkEndToEndHeaders() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Allow: GET, HEAD")
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse()
- .addHeader("Allow: GET, HEAD, PUT")
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- URLConnection connection1 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection1));
- assertEquals("GET, HEAD", connection1.getHeaderField("Allow"));
-
- URLConnection connection2 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection2));
- assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow"));
- }
-
- public void testGetHeadersReturnsCachedHopByHopHeaders() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Transfer-Encoding: identity")
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse()
- .addHeader("Transfer-Encoding: none")
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- URLConnection connection1 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection1));
- assertEquals("identity", connection1.getHeaderField("Transfer-Encoding"));
-
- URLConnection connection2 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection2));
- assertEquals("identity", connection2.getHeaderField("Transfer-Encoding"));
- }
-
- public void testGetHeadersDeletesCached100LevelWarnings() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Warning: 199 test danger")
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- URLConnection connection1 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection1));
- assertEquals("199 test danger", connection1.getHeaderField("Warning"));
-
- URLConnection connection2 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection2));
- assertEquals(null, connection2.getHeaderField("Warning"));
- }
-
- public void testGetHeadersRetainsCached200LevelWarnings() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Warning: 299 test danger")
- .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.play();
-
- URLConnection connection1 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection1));
- assertEquals("299 test danger", connection1.getHeaderField("Warning"));
-
- URLConnection connection2 = server.getUrl("/").openConnection();
- assertEquals("A", readAscii(connection2));
- assertEquals("299 test danger", connection2.getHeaderField("Warning"));
- }
-
- public void assertCookies(URL url, String... expectedCookies) throws Exception {
- List<String> actualCookies = new ArrayList<String>();
- for (HttpCookie cookie : cookieManager.getCookieStore().get(url.toURI())) {
- actualCookies.add(cookie.toString());
- }
- assertEquals(Arrays.asList(expectedCookies), actualCookies);
- }
-
- public void testCachePlusRange() throws Exception {
- assertNotCached(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_PARTIAL)
- .addHeader("Date: " + formatDate(0, TimeUnit.HOURS))
- .addHeader("Content-Range: bytes 100-100/200")
- .addHeader("Cache-Control: max-age=60"));
- }
-
- public void testConditionalHitUpdatesCache() throws Exception {
- server.enqueue(new MockResponse()
- .addHeader("Last-Modified: " + formatDate(0, TimeUnit.SECONDS))
- .addHeader("Cache-Control: max-age=0")
- .setBody("A"));
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=30")
- .addHeader("Allow: GET, HEAD")
- .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- // cache miss; seed the cache
- HttpURLConnection connection1 = (HttpURLConnection) server.getUrl("/a").openConnection();
- assertEquals("A", readAscii(connection1));
- assertEquals(null, connection1.getHeaderField("Allow"));
-
- // conditional cache hit; update the cache
- HttpURLConnection connection2 = (HttpURLConnection) server.getUrl("/a").openConnection();
- assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
- assertEquals("A", readAscii(connection2));
- assertEquals("GET, HEAD", connection2.getHeaderField("Allow"));
-
- // full cache hit
- HttpURLConnection connection3 = (HttpURLConnection) server.getUrl("/a").openConnection();
- assertEquals("A", readAscii(connection3));
- assertEquals("GET, HEAD", connection3.getHeaderField("Allow"));
-
- assertEquals(2, server.getRequestCount());
- }
-
- /**
- * @param delta the offset from the current date to use. Negative
- * values yield dates in the past; positive values yield dates in the
- * future.
- */
- private String formatDate(long delta, TimeUnit timeUnit) {
- return formatDate(new Date(System.currentTimeMillis() + timeUnit.toMillis(delta)));
- }
-
- private String formatDate(Date date) {
- DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
- rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
- return rfc1123.format(date);
- }
-
- private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate)
- throws IOException {
- if (requestMethod.equals("POST") || requestMethod.equals("PUT")) {
- invalidate.setDoOutput(true);
- OutputStream requestBody = invalidate.getOutputStream();
- requestBody.write('x');
- requestBody.close();
- }
- }
-
- private void assertNotCached(MockResponse response) throws Exception {
- server.enqueue(response.setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
- assertEquals("B", readAscii(url.openConnection()));
- }
-
- private void exercisePossiblyFaultyCache(boolean permitReadBodyFailures) throws Exception {
- server.shutdown();
- server = new MockWebServer();
- server.enqueue(new MockResponse()
- .addHeader("Cache-Control: max-age=60")
- .setBody("A"));
- server.enqueue(new MockResponse().setBody("B"));
- server.play();
-
- URL url = server.getUrl("/" + UUID.randomUUID());
- assertEquals("A", readAscii(url.openConnection()));
-
- URLConnection connection = url.openConnection();
- InputStream in = connection.getInputStream();
- try {
- int bodyChar = in.read();
- assertTrue(bodyChar == 'A' || bodyChar == 'B');
- assertEquals(-1, in.read());
- } catch (IOException e) {
- if (!permitReadBodyFailures) {
- throw e;
- }
- }
- }
-
- /**
- * @return the request with the conditional get headers.
- */
- private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception {
- // scenario 1: condition succeeds
- server.enqueue(response.setBody("A").setStatus("HTTP/1.1 200 A-OK"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
-
- // scenario 2: condition fails
- server.enqueue(response.setBody("B").setStatus("HTTP/1.1 200 B-OK"));
- server.enqueue(new MockResponse().setStatus("HTTP/1.1 200 C-OK").setBody("C"));
-
- server.play();
-
- URL valid = server.getUrl("/valid");
- HttpURLConnection connection1 = (HttpURLConnection) valid.openConnection();
- assertEquals("A", readAscii(connection1));
- assertEquals(HttpURLConnection.HTTP_OK, connection1.getResponseCode());
- assertEquals("A-OK", connection1.getResponseMessage());
- HttpURLConnection connection2 = (HttpURLConnection) valid.openConnection();
- assertEquals("A", readAscii(connection2));
- assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
- assertEquals("A-OK", connection2.getResponseMessage());
-
- URL invalid = server.getUrl("/invalid");
- HttpURLConnection connection3 = (HttpURLConnection) invalid.openConnection();
- assertEquals("B", readAscii(connection3));
- assertEquals(HttpURLConnection.HTTP_OK, connection3.getResponseCode());
- assertEquals("B-OK", connection3.getResponseMessage());
- HttpURLConnection connection4 = (HttpURLConnection) invalid.openConnection();
- assertEquals("C", readAscii(connection4));
- assertEquals(HttpURLConnection.HTTP_OK, connection4.getResponseCode());
- assertEquals("C-OK", connection4.getResponseMessage());
-
- server.takeRequest(); // regular get
- return server.takeRequest(); // conditional get
- }
-
- private void assertFullyCached(MockResponse response) throws Exception {
- server.enqueue(response.setBody("A"));
- server.enqueue(response.setBody("B"));
- server.play();
-
- URL url = server.getUrl("/");
- assertEquals("A", readAscii(url.openConnection()));
- assertEquals("A", readAscii(url.openConnection()));
- }
-
- /**
- * Shortens the body of {@code response} but not the corresponding headers.
- * Only useful to test how clients respond to the premature conclusion of
- * the HTTP body.
- */
- private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) {
- response.setSocketPolicy(DISCONNECT_AT_END);
- List<String> headers = new ArrayList<String>(response.getHeaders());
- response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep));
- response.getHeaders().clear();
- response.getHeaders().addAll(headers);
- return response;
- }
-
- /**
- * Reads {@code count} characters from the stream. If the stream is
- * exhausted before {@code count} characters can be read, the remaining
- * characters are returned and the stream is closed.
- */
- private String readAscii(URLConnection connection, int count) throws IOException {
- HttpURLConnection httpConnection = (HttpURLConnection) connection;
- InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
- ? connection.getInputStream()
- : httpConnection.getErrorStream();
- StringBuilder result = new StringBuilder();
- for (int i = 0; i < count; i++) {
- int value = in.read();
- if (value == -1) {
- in.close();
- break;
- }
- result.append((char) value);
- }
- return result.toString();
- }
-
- private String readAscii(URLConnection connection) throws IOException {
- return readAscii(connection, Integer.MAX_VALUE);
- }
-
- private void reliableSkip(InputStream in, int length) throws IOException {
- while (length > 0) {
- length -= in.skip(length);
- }
- }
-
- private void assertGatewayTimeout(HttpURLConnection connection) throws IOException {
- try {
- connection.getInputStream();
- fail();
- } catch (FileNotFoundException expected) {
- }
- assertEquals(504, connection.getResponseCode());
- assertEquals(-1, connection.getErrorStream().read());
- }
-
- enum TransferKind {
- CHUNKED() {
- @Override void setBody(MockResponse response, byte[] content, int chunkSize)
- throws IOException {
- response.setChunkedBody(content, chunkSize);
- }
- },
- FIXED_LENGTH() {
- @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
- response.setBody(content);
- }
- },
- END_OF_STREAM() {
- @Override void setBody(MockResponse response, byte[] content, int chunkSize) {
- response.setBody(content);
- response.setSocketPolicy(DISCONNECT_AT_END);
- for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) {
- if (h.next().startsWith("Content-Length:")) {
- h.remove();
- break;
- }
- }
- }
- };
-
- abstract void setBody(MockResponse response, byte[] content, int chunkSize)
- throws IOException;
-
- void setBody(MockResponse response, String content, int chunkSize) throws IOException {
- setBody(response, content.getBytes("UTF-8"), chunkSize);
- }
- }
-
- private <T> List<T> toListOrNull(T[] arrayOrNull) {
- return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
- }
-
- /**
- * Returns a gzipped copy of {@code bytes}.
- */
- public byte[] gzip(byte[] bytes) throws IOException {
- ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
- OutputStream gzippedOut = new GZIPOutputStream(bytesOut);
- gzippedOut.write(bytes);
- gzippedOut.close();
- return bytesOut.toByteArray();
- }
-
- private class InsecureResponseCache extends ResponseCache {
- @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
- return cache.put(uri, connection);
- }
-
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) throws IOException {
- final CacheResponse response = cache.get(uri, requestMethod, requestHeaders);
- if (response instanceof SecureCacheResponse) {
- return new CacheResponse() {
- @Override public InputStream getBody() throws IOException {
- return response.getBody();
- }
- @Override public Map<String, List<String>> getHeaders() throws IOException {
- return response.getHeaders();
- }
- };
- }
- return response;
- }
- }
-}
diff --git a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
deleted file mode 100644
index d7ba441..0000000
--- a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
-* Copyright (C) 2011 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 libcore.net.http;
-
-import java.net.URI;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import junit.framework.TestCase;
-
-public final class ParsedHeadersTest extends TestCase {
-
- private URI uri;
-
- @Override protected void setUp() throws Exception {
- super.setUp();
- uri = new URI("http", "localhost", "/");
- }
-
- public void testUpperCaseHttpHeaders() {
- RawHeaders headers = new RawHeaders();
- headers.add("CACHE-CONTROL", "no-store");
- headers.add("DATE", "Thu, 01 Jan 1970 00:00:01 UTC");
- headers.add("EXPIRES", "Thu, 01 Jan 1970 00:00:02 UTC");
- headers.add("LAST-MODIFIED", "Thu, 01 Jan 1970 00:00:03 UTC");
- headers.add("ETAG", "v1");
- headers.add("PRAGMA", "no-cache");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoStore());
- assertEquals(new Date(1000), parsedHeaders.getServedDate());
- assertEquals(new Date(2000), parsedHeaders.getExpires());
- assertEquals(new Date(3000), parsedHeaders.getLastModified());
- assertEquals("v1", parsedHeaders.getEtag());
- assertTrue(parsedHeaders.isNoCache());
- }
-
- public void testCommaSeparatedCacheControlHeaders() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "no-store, max-age=60, public");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoStore());
- assertEquals(60, parsedHeaders.getMaxAgeSeconds());
- assertTrue(parsedHeaders.isPublic());
- }
-
- public void testQuotedFieldName() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "private=\"Set-Cookie\", no-store");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoStore());
- }
-
- public void testUnquotedValue() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "private=Set-Cookie, no-store");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoStore());
- }
-
- public void testQuotedValue() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "private=\" a, no-cache, c \", no-store");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoStore());
- assertFalse(parsedHeaders.isNoCache());
- }
-
- public void testDanglingQuote() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "private=\"a, no-cache, c");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertFalse(parsedHeaders.isNoCache());
- }
-
- public void testTrailingComma() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "public,");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isPublic());
- }
-
- public void testTrailingEquals() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "private=");
- new ResponseHeaders(uri, headers);
- }
-
- public void testSpaceBeforeEquals() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "max-age =60");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertEquals(60, parsedHeaders.getMaxAgeSeconds());
- }
-
- public void testSpaceAfterEqualsWithQuotes() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "max-age= \"60\"");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertEquals(60, parsedHeaders.getMaxAgeSeconds());
- }
-
- public void testSpaceAfterEqualsWithoutQuotes() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "max-age= 60");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertEquals(60, parsedHeaders.getMaxAgeSeconds());
- }
-
- public void testCacheControlRequestDirectivesAreCaseInsensitive() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "NO-CACHE");
- headers.add("Cache-Control", "MAX-AGE=60");
- headers.add("Cache-Control", "MAX-STALE=70");
- headers.add("Cache-Control", "MIN-FRESH=80");
- headers.add("Cache-Control", "ONLY-IF-CACHED");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoCache());
- assertEquals(60, parsedHeaders.getMaxAgeSeconds());
- assertEquals(70, parsedHeaders.getMaxStaleSeconds());
- assertEquals(80, parsedHeaders.getMinFreshSeconds());
- assertTrue(parsedHeaders.isOnlyIfCached());
- }
-
- public void testCacheControlResponseDirectivesAreCaseInsensitive() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "NO-CACHE");
- headers.add("Cache-Control", "NO-STORE");
- headers.add("Cache-Control", "MAX-AGE=60");
- headers.add("Cache-Control", "S-MAXAGE=70");
- headers.add("Cache-Control", "PUBLIC");
- headers.add("Cache-Control", "MUST-REVALIDATE");
- ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoCache());
- assertTrue(parsedHeaders.isNoStore());
- assertEquals(60, parsedHeaders.getMaxAgeSeconds());
- assertEquals(70, parsedHeaders.getSMaxAgeSeconds());
- assertTrue(parsedHeaders.isPublic());
- assertTrue(parsedHeaders.isMustRevalidate());
- }
-
- public void testPragmaDirectivesAreCaseInsensitive() {
- RawHeaders headers = new RawHeaders();
- headers.add("Pragma", "NO-CACHE");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertTrue(parsedHeaders.isNoCache());
- }
-
- public void testMissingInteger() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "max-age");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertEquals(-1, parsedHeaders.getMaxAgeSeconds());
- }
-
- public void testInvalidInteger() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "MAX-AGE=pi");
- RequestHeaders requestHeaders = new RequestHeaders(uri, headers);
- assertEquals(-1, requestHeaders.getMaxAgeSeconds());
- }
-
- public void testVeryLargeInteger() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "MAX-AGE=" + (Integer.MAX_VALUE + 1L));
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertEquals(Integer.MAX_VALUE, parsedHeaders.getMaxAgeSeconds());
- }
-
- public void testNegativeInteger() {
- RawHeaders headers = new RawHeaders();
- headers.add("Cache-Control", "MAX-AGE=-2");
- RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
- assertEquals(0, parsedHeaders.getMaxAgeSeconds());
- }
-
- public void testParseChallengesWithCommaInRealm() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", "s1 realm=\"ab,cd\", s2 realm=\"ef,gh\"");
- assertEquals(Arrays.asList(new Challenge("s1", "ab,cd"), new Challenge("s2", "ef,gh")),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-
- public void testParseChallengesWithMultipleHeaders() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", "s1 realm=\"abc\"");
- headers.add("WWW-Authenticate", "s2 realm=\"def\"");
- assertEquals(Arrays.asList(new Challenge("s1", "abc"), new Challenge("s2", "def")),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-
- public void testParseChallengesWithExtraWhitespace() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", " s1 realm=\"a\" , s2 realm=\"b\", ");
- assertEquals(Arrays.asList(new Challenge("s1", "a"), new Challenge("s2", "b")),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-
- public void testParseChallengesWithMissingRealm() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", "Basic");
- assertEquals(Collections.<Challenge>emptyList(),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-
- public void testParseChallengesWithEmptyRealm() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", "Basic realm=\"\"");
- assertEquals(Arrays.asList(new Challenge("Basic", "")),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-
- public void testParseChallengesWithMissingScheme() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", "realm=\"a\"");
- assertEquals(Collections.<Challenge>emptyList(),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-
- // http://code.google.com/p/android/issues/detail?id=11140
- public void testParseChallengesWithManyParameters() {
- RawHeaders headers = new RawHeaders();
- headers.add("WWW-Authenticate", "Digest realm=\"abc\","
- + " qop=\"auth,auth-int\","
- + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
- + " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\","
- + " Basic realm=\"def\"");
- assertEquals(Arrays.asList(new Challenge("Digest", "abc"), new Challenge("Basic", "def")),
- HeaderParser.parseChallenges(headers, "WWW-Authenticate"));
- }
-}
diff --git a/luni/src/test/java/libcore/net/http/RawHeadersTest.java b/luni/src/test/java/libcore/net/http/RawHeadersTest.java
deleted file mode 100644
index d99e851..0000000
--- a/luni/src/test/java/libcore/net/http/RawHeadersTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2010 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 libcore.net.http;
-
-import java.util.Arrays;
-import junit.framework.TestCase;
-
-public class RawHeadersTest extends TestCase {
- // http://code.google.com/p/android/issues/detail?id=6684
- public void test_caseInsensitiveButCasePreserving() {
- RawHeaders h = new RawHeaders();
- h.add("Content-Type", "text/plain");
- // Case-insensitive:
- assertEquals("text/plain", h.get("Content-Type"));
- assertEquals("text/plain", h.get("Content-type"));
- assertEquals("text/plain", h.get("content-type"));
- assertEquals("text/plain", h.get("CONTENT-TYPE"));
- // ...but case-preserving:
- assertEquals("Content-Type", h.toMultimap().keySet().toArray()[0]);
-
- // We differ from the RI in that the Map we return is also case-insensitive.
- // Our behavior seems more consistent. (And code that works on the RI will work on Android.)
- assertEquals(Arrays.asList("text/plain"), h.toMultimap().get("Content-Type"));
- assertEquals(Arrays.asList("text/plain"), h.toMultimap().get("Content-type")); // RI fails this.
- }
-
- // The copy constructor used to be broken for headers with multiple values.
- // http://code.google.com/p/android/issues/detail?id=6722
- public void test_copyConstructor() {
- RawHeaders h1 = new RawHeaders();
- h1.add("key", "value1");
- h1.add("key", "value2");
- RawHeaders h2 = RawHeaders.fromMultimap(h1.toMultimap());
- assertEquals(2, h2.length());
- assertEquals("key", h2.getFieldName(0));
- assertEquals("value1", h2.getValue(0));
- assertEquals("key", h2.getFieldName(1));
- assertEquals("value2", h2.getValue(1));
- }
-}
diff --git a/luni/src/test/java/tests/api/java/util/LocaleTest.java b/luni/src/test/java/tests/api/java/util/LocaleTest.java
index e5255e4..071dbc8 100644
--- a/luni/src/test/java/tests/api/java/util/LocaleTest.java
+++ b/luni/src/test/java/tests/api/java/util/LocaleTest.java
@@ -17,7 +17,6 @@
package tests.api.java.util;
-import dalvik.annotation.AndroidOnly;
import tests.support.Support_Locale;
import java.security.Permission;
@@ -172,7 +171,6 @@ public class LocaleTest extends junit.framework.TestCase {
/**
* java.util.Locale#getDisplayCountry()
*/
- @AndroidOnly("ICU has different display name for countries")
public void test_getDisplayCountry() {
// Test for method java.lang.String java.util.Locale.getDisplayCountry()
assertTrue("Returned incorrect country: "
@@ -248,35 +246,7 @@ public class LocaleTest extends junit.framework.TestCase {
.getDisplayVariant(l).equals("WIN32"));
}
- /**
- * java.util.Locale#getISO3Country()
- */
- public void test_getISO3Country() {
- // Test for method java.lang.String java.util.Locale.getISO3Country()
- assertTrue("Returned incorrect ISO3 country: "
- + testLocale.getISO3Country(), testLocale.getISO3Country()
- .equals("CAN"));
-
- Locale l = new Locale("", "CD");
- assertEquals("COD", l.getISO3Country());
-
- Locale x = new Locale("xx", "C");
- try {
- x.getISO3Country();
- } catch (MissingResourceException e) {
- //expected
- }
- }
-
- /**
- * java.util.Locale#getISO3Language()
- */
public void test_getISO3Language() {
- // Test for method java.lang.String java.util.Locale.getISO3Language()
- assertTrue("Returned incorrect ISO3 language: "
- + testLocale.getISO3Language(), testLocale.getISO3Language()
- .equals("eng"));
-
Locale l = new Locale("ae");
assertEquals("ave", l.getISO3Language());
@@ -287,13 +257,6 @@ public class LocaleTest extends junit.framework.TestCase {
// Regression for Harmony-1129
l = new Locale("ak", "");
assertEquals("aka", l.getISO3Language());
-
- Locale x = new Locale("xx", "C");
- try {
- x.getISO3Language();
- } catch (MissingResourceException e) {
- //expected
- }
}
/**