Merge remote branch 'goog/dalvik-dev' into dalvik-dev-to-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
deleted file mode 100644
index e5321b7..0000000
--- a/AndroidManifest.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.core.tests">
-
- <uses-permission android:name="android.permission.INTERNET" />
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.core.tests"
- android:label="cts core tests"/>
-
- <instrumentation android:name="android.test.InstrumentationCoreTestRunner"
- android:targetPackage="android.core.tests"
- android:label="cts core tests"/>
-
-</manifest>
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 08459fa..da75104 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -105,7 +105,6 @@
LOCAL_NO_STANDARD_LIBRARIES := true
LOCAL_JAVA_LIBRARIES := bouncycastle core core-junit
LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc
-LOCAL_DX_FLAGS := --core-library
LOCAL_JAVACFLAGS := $(local_javac_flags)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := core-tests
@@ -172,7 +171,6 @@
LOCAL_NO_STANDARD_LIBRARIES := true
LOCAL_JAVA_LIBRARIES := bouncycastle-hostdex core-hostdex core-junit-hostdex
LOCAL_STATIC_JAVA_LIBRARIES := sqlite-jdbc-host
- LOCAL_DX_FLAGS := --core-library
LOCAL_JAVACFLAGS := $(local_javac_flags)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := core-tests-hostdex
diff --git a/dalvik/src/main/java/dalvik/system/BlockGuard.java b/dalvik/src/main/java/dalvik/system/BlockGuard.java
index d3c5088..13cc32d 100644
--- a/dalvik/src/main/java/dalvik/system/BlockGuard.java
+++ b/dalvik/src/main/java/dalvik/system/BlockGuard.java
@@ -201,17 +201,14 @@
return mNetwork.writeDirect(fd, address, offset, count);
}
- public boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
- throws IOException {
+ public boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException {
BlockGuard.getThreadPolicy().onNetwork();
- return mNetwork.connectNonBlocking(fd, inetAddress, port);
+ return mNetwork.connect(fd, inetAddress, port);
}
public boolean isConnected(FileDescriptor fd, int timeout) throws IOException {
if (timeout != 0) {
- // Less than 0 is blocking forever.
- // Greater than 0 is a timeout.
- // Zero is okay.
+ // Greater than 0 is a timeout, but zero means "poll and return immediately".
BlockGuard.getThreadPolicy().onNetwork();
}
return mNetwork.isConnected(fd, timeout);
@@ -251,12 +248,6 @@
mNetwork.sendUrgentData(fd, value);
}
- public void connect(FileDescriptor aFD, InetAddress inetAddress, int port,
- int timeout) throws SocketException {
- BlockGuard.getThreadPolicy().onNetwork();
- mNetwork.connect(aFD, inetAddress, port, timeout);
- }
-
public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs,
int numReadable, int numWritable, long timeout, int[] flags)
throws SocketException {
@@ -264,11 +255,6 @@
return mNetwork.select(readFDs, writeFDs, numReadable, numWritable, timeout, flags);
}
- public void setSocketOption(FileDescriptor aFD, int opt, Object optVal)
- throws SocketException {
- mNetwork.setSocketOption(aFD, opt, optVal);
- }
-
public void close(FileDescriptor aFD) throws IOException {
// We exclude sockets without SO_LINGER so that apps can close their network connections
// in methods like onDestroy, which will run on the UI thread, without jumping through
diff --git a/include/ScopedBytes.h b/include/ScopedBytes.h
new file mode 100644
index 0000000..cb2614b
--- /dev/null
+++ b/include/ScopedBytes.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef SCOPED_BYTES_H_included
+#define SCOPED_BYTES_H_included
+
+#include "JNIHelp.h"
+
+/**
+ * ScopedBytesRO and ScopedBytesRW attempt to paper over the differences between byte[]s and
+ * ByteBuffers. This in turn helps paper over the differences between non-direct ByteBuffers backed
+ * by byte[]s, direct ByteBuffers backed by bytes[]s, and direct ByteBuffers not backed by byte[]s.
+ * (On Android, this last group only contains MappedByteBuffers.)
+ */
+template<bool readOnly>
+class ScopedBytes {
+public:
+ ScopedBytes(JNIEnv* env, jobject object)
+ : mEnv(env), mObject(object), mByteArray(NULL), mPtr(NULL)
+ {
+ if (mObject == NULL) {
+ jniThrowNullPointerException(mEnv, NULL);
+ } else if (mEnv->IsInstanceOf(mObject, JniConstants::byteArrayClass)) {
+ mByteArray = reinterpret_cast<jbyteArray>(mObject);
+ mPtr = mEnv->GetByteArrayElements(mByteArray, NULL);
+ } else {
+ mPtr = reinterpret_cast<jbyte*>(mEnv->GetDirectBufferAddress(mObject));
+ }
+ }
+
+ ~ScopedBytes() {
+ if (mByteArray != NULL) {
+ mEnv->ReleaseByteArrayElements(mByteArray, mPtr, readOnly ? JNI_ABORT : 0);
+ }
+ }
+
+private:
+ JNIEnv* mEnv;
+ jobject mObject;
+ jbyteArray mByteArray;
+
+protected:
+ jbyte* mPtr;
+
+private:
+ // Disallow copy and assignment.
+ ScopedBytes(const ScopedBytes&);
+ void operator=(const ScopedBytes&);
+};
+
+class ScopedBytesRO : public ScopedBytes<true> {
+public:
+ ScopedBytesRO(JNIEnv* env, jobject object) : ScopedBytes<true>(env, object) {}
+ const jbyte* get() const {
+ return mPtr;
+ }
+};
+
+class ScopedBytesRW : public ScopedBytes<false> {
+public:
+ ScopedBytesRW(JNIEnv* env, jobject object) : ScopedBytes<false>(env, object) {}
+ jbyte* get() {
+ return mPtr;
+ }
+};
+
+#endif // SCOPED_BYTES_H_included
diff --git a/luni/src/main/java/java/io/Closeable.java b/luni/src/main/java/java/io/Closeable.java
index 56c2b3c..3929e7c 100644
--- a/luni/src/main/java/java/io/Closeable.java
+++ b/luni/src/main/java/java/io/Closeable.java
@@ -17,28 +17,17 @@
package java.io;
/**
- * Defines an interface for classes that can (or need to) be closed once they
- * are not used any longer. This usually includes all sorts of
- * {@link InputStream}s and {@link OutputStream}s. Calling the {@code close}
- * method releases resources that the object holds.
- * <p>
- * A common pattern for using a {@code Closeable} resource: <pre> {@code
- * Closable foo = new Foo();
- * try {
- * ...;
- * } finally {
- * foo.close();
- * }
- * }</pre>
+ * An {@code AutoCloseable} whose close method may throw an {@link IOException}.
*/
-public interface Closeable {
+public interface Closeable extends AutoCloseable {
/**
- * Closes the object and release any system resources it holds. If the
- * object has already been closed, then invoking this method has no effect.
+ * Closes the object and release any system resources it holds.
*
- * @throws IOException
- * if any error occurs when closing the object.
+ * <p>Although only the first call has any effect, it is safe to call close
+ * multiple times on the same object. This is more lenient than the
+ * overridden {@code AutoCloseable.close()}, which may be called at most
+ * once.
*/
- public void close() throws IOException;
+ void close() throws IOException;
}
diff --git a/luni/src/main/java/java/io/FilePermission.java b/luni/src/main/java/java/io/FilePermission.java
index 94263f1..3daeda1 100644
--- a/luni/src/main/java/java/io/FilePermission.java
+++ b/luni/src/main/java/java/io/FilePermission.java
@@ -24,26 +24,7 @@
import libcore.util.Objects;
/**
- * A permission for accessing a file or directory. The FilePermission is made up
- * of a pathname and a set of actions which are valid for the pathname.
- * <p>
- * The {@code File.separatorChar} must be used in all pathnames when
- * constructing a FilePermission. The following descriptions will assume the
- * char is {@code /}. A pathname that ends in {@code /*} includes all the files
- * and directories contained in that directory. If the pathname
- * ends in {@code /-}, it includes all the files and directories in that
- * directory <i>recursively</i>. The following pathnames have a special meaning:
- * <ul>
- * <li>
- * "*": all files in the current directory;
- * </li>
- * <li>
- * "-": recursively all files and directories in the current directory;
- * </li>
- * <li>
- * "<<ALL FILES>>": any file and directory in the file system.
- * </li>
- * </ul>
+ * Legacy security code; this class exists for compatibility only.
*/
public final class FilePermission extends Permission implements Serializable {
@@ -52,9 +33,7 @@
// canonical path of this permission
private transient String canonPath;
- // list of actions permitted for socket permission in order
- private static final String[] actionList = { "read", "write", "execute",
- "delete" };
+ private static final String[] actionList = { "read", "write", "execute", "delete" };
// "canonicalized" action list
private String actions;
diff --git a/luni/src/main/java/java/io/ObjectInput.java b/luni/src/main/java/java/io/ObjectInput.java
index a10ddfe..e892071 100644
--- a/luni/src/main/java/java/io/ObjectInput.java
+++ b/luni/src/main/java/java/io/ObjectInput.java
@@ -23,7 +23,7 @@
* @see ObjectInputStream
* @see ObjectOutput
*/
-public interface ObjectInput extends DataInput {
+public interface ObjectInput extends DataInput, AutoCloseable {
/**
* Indicates the number of bytes of primitive data that can be read without
* blocking.
diff --git a/luni/src/main/java/java/io/ObjectOutput.java b/luni/src/main/java/java/io/ObjectOutput.java
index e7d92c5..2e454ec 100644
--- a/luni/src/main/java/java/io/ObjectOutput.java
+++ b/luni/src/main/java/java/io/ObjectOutput.java
@@ -23,7 +23,7 @@
* @see ObjectOutputStream
* @see ObjectInput
*/
-public interface ObjectOutput extends DataOutput {
+public interface ObjectOutput extends DataOutput, AutoCloseable {
/**
* Closes the target stream. Implementations of this method should free any
* resources used by the stream.
diff --git a/luni/src/main/java/java/io/SerializablePermission.java b/luni/src/main/java/java/io/SerializablePermission.java
index 57d4421..a1465fe 100644
--- a/luni/src/main/java/java/io/SerializablePermission.java
+++ b/luni/src/main/java/java/io/SerializablePermission.java
@@ -20,21 +20,7 @@
import java.security.BasicPermission;
/**
- * Is used to enable access to potentially unsafe serialization operations. It
- * does have a name but no action list. The following table lists valid
- * permission names:
- * <table>
- * <tr>
- * <td>enableSubclassImplementation</td>
- * <td>Subclasses can override serialization behavior.</td>
- * </tr>
- * <tr>
- * <td>enableSubstitution</td>
- * <td>Object substitution is allowed.</td>
- * </tr>
- * </table>
- *
- * @see ObjectStreamConstants
+ * Legacy security code; this class exists for compatibility only.
*/
public final class SerializablePermission extends BasicPermission {
private static final long serialVersionUID = 8537212141160296410L;
diff --git a/luni/src/main/java/java/lang/AutoCloseable.java b/luni/src/main/java/java/lang/AutoCloseable.java
new file mode 100644
index 0000000..b3133a9
--- /dev/null
+++ b/luni/src/main/java/java/lang/AutoCloseable.java
@@ -0,0 +1,45 @@
+/*
+ * 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.lang;
+
+/**
+ * Defines an interface for classes that can (or need to) be closed once they
+ * are not used any longer. Calling the {@code close} method releases resources
+ * that the object holds.
+ *
+ * <p>A common pattern for using an {@code AutoCloseable} resource: <pre> {@code
+ * Closable foo = new Foo();
+ * try {
+ * ...;
+ * } finally {
+ * foo.close();
+ * }
+ * }</pre>
+ *
+ * @since 1.7
+ * @hide 1.7
+ */
+public interface AutoCloseable {
+
+ /**
+ * Closes the object and release any system resources it holds.
+ *
+ * <p>Unless the implementing class specifies otherwise, it is an error to
+ * call {@link #close} more than once.
+ */
+ void close() throws Exception;
+}
diff --git a/luni/src/main/java/java/lang/FinalizerThread.java b/luni/src/main/java/java/lang/FinalizerThread.java
index 8b6f337..5f478df 100644
--- a/luni/src/main/java/java/lang/FinalizerThread.java
+++ b/luni/src/main/java/java/lang/FinalizerThread.java
@@ -74,7 +74,8 @@
try {
obj.finalize();
} catch (Throwable ex) {
- // TODO: print a warning
+ // The RI silently swallows these, but Android has always logged.
+ System.logE("Uncaught exception thrown by finalizer", ex);
}
}
diff --git a/luni/src/main/java/java/lang/Package.java b/luni/src/main/java/java/lang/Package.java
index e3095ea..2e587dd 100644
--- a/luni/src/main/java/java/lang/Package.java
+++ b/luni/src/main/java/java/lang/Package.java
@@ -49,9 +49,15 @@
* @see java.lang.ClassLoader
*/
public class Package implements AnnotatedElement {
+ private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];
- private final String name, specTitle, specVersion, specVendor, implTitle,
- implVersion, implVendor;
+ private final String name;
+ private final String specTitle;
+ private final String specVersion;
+ private final String specVendor;
+ private final String implTitle;
+ private final String implVersion;
+ private final String implVendor;
private final URL sealBase;
Package(String name, String specTitle, String specVersion, String specVendor,
@@ -86,37 +92,19 @@
}
/**
- * Gets all annotations associated with this package, if any.
- *
- * @return an array of {@link Annotation} instances, which may be empty.
- * @see java.lang.reflect.AnnotatedElement#getAnnotations()
+ * Returns an empty array. Package annotations are not supported on Android.
*/
public Annotation[] getAnnotations() {
- return getDeclaredAnnotations(this, true);
+ return NO_ANNOTATIONS;
}
/**
- * Gets all annotations directly declared on this package, if any.
- *
- * @return an array of {@link Annotation} instances, which may be empty.
- * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotations()
+ * Returns an empty array. Package annotations are not supported on Android.
*/
public Annotation[] getDeclaredAnnotations() {
- return getDeclaredAnnotations(this, false);
+ return NO_ANNOTATIONS;
}
- /*
- * Returns the list of declared annotations of the given package.
- * If no annotations exist, an empty array is returned.
- *
- * @param pkg the package of interest
- * @param publicOnly reflects whether we want only public annotation or all
- * of them.
- * @return the list of annotations
- */
- // TODO(Google) Provide proper (native) implementation.
- private static native Annotation[] getDeclaredAnnotations(Package pkg, boolean publicOnly);
-
/**
* Indicates whether the specified annotation is present.
*
diff --git a/luni/src/main/java/java/lang/ProcessManager.java b/luni/src/main/java/java/lang/ProcessManager.java
index ce3474d..cdcbb8a 100644
--- a/luni/src/main/java/java/lang/ProcessManager.java
+++ b/luni/src/main/java/java/lang/ProcessManager.java
@@ -28,7 +28,10 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import libcore.io.ErrnoException;
import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import static libcore.io.OsConstants.*;
/**
* Manages child processes.
@@ -54,14 +57,6 @@
private static final int WAIT_STATUS_STRANGE_ERRNO = -3;
/**
- * Initializes native static state.
- */
- static native void staticInitialize();
- static {
- staticInitialize();
- }
-
- /**
* Map from pid to Process. We keep weak references to the Process objects
* and clean up the entries when no more external references are left. The
* process objects themselves don't require much memory, but file
@@ -77,9 +72,8 @@
private ProcessManager() {
// Spawn a thread to listen for signals from child processes.
Thread processThread = new Thread(ProcessManager.class.getName()) {
- @Override
- public void run() {
- watchChildren();
+ @Override public void run() {
+ watchChildren(ProcessManager.this);
}
};
processThread.setDaemon(true);
@@ -87,17 +81,10 @@
}
/**
- * Kills the process with the given ID.
- *
- * @parm pid ID of process to kill
- */
- private static native void kill(int pid) throws IOException;
-
- /**
* Cleans up after garbage collected processes. Requires the lock on the
* map.
*/
- void cleanUp() {
+ private void cleanUp() {
ProcessReference reference;
while ((reference = referenceQueue.poll()) != null) {
synchronized (processReferences) {
@@ -110,7 +97,7 @@
* Listens for signals from processes and calls back to
* {@link #onExit(int,int)}.
*/
- native void watchChildren();
+ private static native void watchChildren(ProcessManager manager);
/**
* Called by {@link #watchChildren()} when a child process exits.
@@ -118,7 +105,7 @@
* @param pid ID of process that exited
* @param exitValue value the process returned upon exit
*/
- void onExit(int pid, int exitValue) {
+ private void onExit(int pid, int exitValue) {
ProcessReference processReference = null;
synchronized (processReferences) {
@@ -164,14 +151,14 @@
* Executes a native process. Fills in in, out, and err and returns the
* new process ID upon success.
*/
- static native int exec(String[] command, String[] environment,
+ private static native int exec(String[] command, String[] environment,
String workingDirectory, FileDescriptor in, FileDescriptor out,
FileDescriptor err, boolean redirectErrorStream) throws IOException;
/**
* Executes a process and returns an object representing it.
*/
- Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory,
+ public Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory,
boolean redirectErrorStream) throws IOException {
// Make sure we throw the same exceptions as the RI.
if (taintedCommand == null) {
@@ -223,8 +210,7 @@
throw wrapper;
}
ProcessImpl process = new ProcessImpl(pid, in, out, err);
- ProcessReference processReference
- = new ProcessReference(process, referenceQueue);
+ ProcessReference processReference = new ProcessReference(process, referenceQueue);
processReferences.put(pid, processReference);
/*
@@ -238,25 +224,22 @@
}
static class ProcessImpl extends Process {
+ private final int pid;
- /** Process ID. */
- final int id;
-
- final InputStream errorStream;
+ private final InputStream errorStream;
/** Reads output from process. */
- final InputStream inputStream;
+ private final InputStream inputStream;
/** Sends output to process. */
- final OutputStream outputStream;
+ private final OutputStream outputStream;
/** The process's exit value. */
- Integer exitValue = null;
- final Object exitValueMutex = new Object();
+ private Integer exitValue = null;
+ private final Object exitValueMutex = new Object();
- ProcessImpl(int id, FileDescriptor in, FileDescriptor out,
- FileDescriptor err) {
- this.id = id;
+ ProcessImpl(int pid, FileDescriptor in, FileDescriptor out, FileDescriptor err) {
+ this.pid = pid;
this.errorStream = new ProcessInputStream(err);
this.inputStream = new ProcessInputStream(in);
@@ -265,9 +248,9 @@
public void destroy() {
try {
- kill(this.id);
- } catch (IOException e) {
- System.logI("Failed to destroy process " + id, e);
+ Libcore.os.kill(pid, SIGKILL);
+ } catch (ErrnoException e) {
+ System.logI("Failed to destroy process " + pid, e);
}
IoUtils.closeQuietly(inputStream);
IoUtils.closeQuietly(errorStream);
@@ -315,7 +298,7 @@
@Override
public String toString() {
- return "Process[id=" + id + "]";
+ return "Process[pid=" + pid + "]";
}
}
@@ -323,10 +306,9 @@
final int processId;
- public ProcessReference(ProcessImpl referent,
- ProcessReferenceQueue referenceQueue) {
+ public ProcessReference(ProcessImpl referent, ProcessReferenceQueue referenceQueue) {
super(referent, referenceQueue);
- this.processId = referent.id;
+ this.processId = referent.pid;
}
}
@@ -340,10 +322,10 @@
}
}
- static final ProcessManager instance = new ProcessManager();
+ private static final ProcessManager instance = new ProcessManager();
/** Gets the process manager. */
- static ProcessManager getInstance() {
+ public static ProcessManager getInstance() {
return instance;
}
diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java
index 14b5918..203e5e3 100644
--- a/luni/src/main/java/java/lang/System.java
+++ b/luni/src/main/java/java/lang/System.java
@@ -108,7 +108,7 @@
* the user defined output stream to set as the standard output
* stream.
*/
- public static void setOut(java.io.PrintStream newOut) {
+ public static void setOut(PrintStream newOut) {
setFieldImpl("out", "Ljava/io/PrintStream;", newOut);
}
@@ -120,7 +120,7 @@
* the user defined output stream to set as the standard error
* output stream.
*/
- public static void setErr(java.io.PrintStream newErr) {
+ public static void setErr(PrintStream newErr) {
setFieldImpl("err", "Ljava/io/PrintStream;", newErr);
}
@@ -281,8 +281,6 @@
p.put("java.io.tmpdir", "/tmp");
p.put("java.library.path", getenv("LD_LIBRARY_PATH"));
- p.put("java.net.preferIPv6Addresses", "true");
-
p.put("java.specification.name", "Dalvik Core Library");
p.put("java.specification.vendor", projectName);
p.put("java.specification.version", "0.9");
diff --git a/luni/src/main/java/java/lang/Throwable.java b/luni/src/main/java/java/lang/Throwable.java
index 05cb1d1..b561832 100644
--- a/luni/src/main/java/java/lang/Throwable.java
+++ b/luni/src/main/java/java/lang/Throwable.java
@@ -18,9 +18,13 @@
package java.lang;
import java.io.IOException;
+import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import libcore.util.EmptyArray;
/**
* The superclass of all classes which can be thrown by the VM. The
@@ -56,6 +60,12 @@
private Throwable cause = this;
/**
+ * Throwables suppressed by this throwable. Null when suppressed exceptions
+ * are disabled.
+ */
+ private List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
+
+ /**
* An intermediate representation of the stack trace. This field may
* be accessed by the VM; do not rename.
*/
@@ -114,6 +124,22 @@
}
/**
+ * Constructs a new {@code Throwable} with the current stack trace, the
+ * specified detail message and the specified cause.
+ *
+ * @param enableSuppression if false, throwables passed to {@link
+ * #addSuppressed(Throwable)} will be silently discarded.
+ * @since 1.7
+ * @hide 1.7
+ */
+ protected Throwable(String detailMessage, Throwable throwable, boolean enableSuppression) {
+ this(detailMessage, throwable);
+ if (!enableSuppression) {
+ this.suppressedExceptions = null;
+ }
+ }
+
+ /**
* Records the stack trace from the point where this method has been called
* to this {@code Throwable}. This method is invoked by the {@code Throwable} constructors.
*
@@ -257,31 +283,11 @@
* the stream to write the stack trace on.
*/
public void printStackTrace(PrintStream err) {
- err.println(toString());
- // Don't use getStackTrace() as it calls clone()
- // Get stackTrace, in case stackTrace is reassigned
- StackTraceElement[] stack = getInternalStackTrace();
- if (stack != null) {
- for (StackTraceElement element : stack) {
- err.println("\tat " + element);
- }
- }
-
- StackTraceElement[] parentStack = stack;
- Throwable throwable = getCause();
- while (throwable != null) {
- err.print("Caused by: ");
- err.println(throwable);
- StackTraceElement[] currentStack = throwable.getInternalStackTrace();
- int duplicates = countDuplicates(currentStack, parentStack);
- for (int i = 0; i < currentStack.length - duplicates; i++) {
- err.println("\tat " + currentStack[i]);
- }
- if (duplicates > 0) {
- err.println("\t... " + duplicates + " more");
- }
- parentStack = currentStack;
- throwable = throwable.getCause();
+ try {
+ printStackTrace(err, "", null);
+ } catch (IOException e) {
+ // Appendable.append throws IOException but PrintStream.append doesn't.
+ throw new AssertionError();
}
}
@@ -295,31 +301,57 @@
* the writer to write the stack trace on.
*/
public void printStackTrace(PrintWriter err) {
- err.println(toString());
- // Don't use getStackTrace() as it calls clone()
- // Get stackTrace, in case stackTrace is reassigned
+ try {
+ printStackTrace(err, "", null);
+ } catch (IOException e) {
+ // Appendable.append throws IOException, but PrintWriter.append doesn't.
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * @param indent additional indentation on each line of the stack trace.
+ * This is the empty string for all but suppressed throwables.
+ * @param parentStack the parent stack trace to suppress duplicates from, or
+ * null if this stack trace has no parent.
+ */
+ private void printStackTrace(Appendable err, String indent, StackTraceElement[] parentStack)
+ throws IOException {
+ err.append(toString());
+ err.append("\n");
+
StackTraceElement[] stack = getInternalStackTrace();
if (stack != null) {
- for (StackTraceElement element : stack) {
- err.println("\tat " + element);
+ int duplicates = parentStack != null ? countDuplicates(stack, parentStack) : 0;
+ for (int i = 0; i < stack.length - duplicates; i++) {
+ err.append(indent);
+ err.append("\tat ");
+ err.append(stack[i].toString());
+ err.append("\n");
+ }
+
+ if (duplicates > 0) {
+ err.append(indent);
+ err.append("\t... ");
+ err.append(Integer.toString(duplicates));
+ err.append(" more\n");
}
}
- StackTraceElement[] parentStack = stack;
- Throwable throwable = getCause();
- while (throwable != null) {
- err.print("Caused by: ");
- err.println(throwable);
- StackTraceElement[] currentStack = throwable.getInternalStackTrace();
- int duplicates = countDuplicates(currentStack, parentStack);
- for (int i = 0; i < currentStack.length - duplicates; i++) {
- err.println("\tat " + currentStack[i]);
+ // Print suppressed exceptions indented one level deeper.
+ if (suppressedExceptions != null) {
+ for (Throwable throwable : suppressedExceptions) {
+ err.append(indent);
+ err.append("\tSuppressed: ");
+ throwable.printStackTrace(err, indent + "\t", stack);
}
- if (duplicates > 0) {
- err.println("\t... " + duplicates + " more");
- }
- parentStack = currentStack;
- throwable = throwable.getCause();
+ }
+
+ Throwable cause = getCause();
+ if (cause != null) {
+ err.append(indent);
+ err.append("Caused by: ");
+ cause.printStackTrace(err, indent, stack);
}
}
@@ -346,14 +378,14 @@
* if the cause has already been initialized.
*/
public Throwable initCause(Throwable throwable) {
- if (cause == this) {
- if (throwable != this) {
- cause = throwable;
- return this;
- }
- throw new IllegalArgumentException("Cause cannot be the receiver");
+ if (cause != this) {
+ throw new IllegalStateException("Cause already initialized");
}
- throw new IllegalStateException("Cause already initialized");
+ if (throwable == this) {
+ throw new IllegalArgumentException("throwable == this");
+ }
+ cause = throwable;
+ return this;
}
/**
@@ -369,10 +401,52 @@
return cause;
}
- private void writeObject(ObjectOutputStream s) throws IOException {
+ /**
+ * Adds {@code throwable} to the list of throwables suppressed by this. The
+ * throwable will included when this exception's stack trace is printed.
+ *
+ * @throws IllegalArgumentException if {@code throwable == this}.
+ * @throws NullPointerException if {@code throwable == null}.
+ * @since 1.7
+ * @hide 1.7
+ */
+ public final void addSuppressed(Throwable throwable) {
+ if (throwable == this) {
+ throw new IllegalArgumentException("suppressed == this");
+ }
+ if (throwable == null) {
+ throw new NullPointerException("suppressed == null");
+ }
+ if (suppressedExceptions != null) {
+ suppressedExceptions.add(throwable);
+ }
+ }
+
+ /**
+ * Returns the throwables suppressed by this.
+ *
+ * @since 1.7
+ * @hide 1.7
+ */
+ public final Throwable[] getSuppressed() {
+ return (suppressedExceptions != null)
+ ? suppressedExceptions.toArray(new Throwable[suppressedExceptions.size()])
+ : EmptyArray.THROWABLE;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
// ensure the stackTrace field is initialized
getInternalStackTrace();
- s.defaultWriteObject();
+ out.defaultWriteObject();
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+
+ if (suppressedExceptions != null) {
+ // the deserialized list may be unmodifiable, so just create a mutable copy
+ suppressedExceptions = new ArrayList<Throwable>(suppressedExceptions);
+ }
}
/*
diff --git a/luni/src/main/java/java/lang/reflect/ReflectPermission.java b/luni/src/main/java/java/lang/reflect/ReflectPermission.java
index ce69dec..afc30a2 100644
--- a/luni/src/main/java/java/lang/reflect/ReflectPermission.java
+++ b/luni/src/main/java/java/lang/reflect/ReflectPermission.java
@@ -20,8 +20,7 @@
import java.security.BasicPermission;
/**
- * A {@code ReflectPermission} object represents a permission to access
- * operations in the reflection layer.
+ * Legacy security code; this class exists for compatibility only.
*/
public final class ReflectPermission extends BasicPermission {
diff --git a/luni/src/main/java/java/net/BindException.java b/luni/src/main/java/java/net/BindException.java
index 3c5151a..498ead6 100644
--- a/luni/src/main/java/java/net/BindException.java
+++ b/luni/src/main/java/java/net/BindException.java
@@ -26,16 +26,13 @@
private static final long serialVersionUID = -5945005768251722951L;
/**
- * Constructs a new instance with its walkback filled in.
+ * Constructs a new instance with the current stack trace.
*/
public BindException() {
}
/**
- * Constructs a new instance with its walkback and message filled in.
- *
- * @param detailMessage
- * detail message of the exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public BindException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/net/CookieManager.java b/luni/src/main/java/java/net/CookieManager.java
index 3b2669e..b1bd832 100644
--- a/luni/src/main/java/java/net/CookieManager.java
+++ b/luni/src/main/java/java/net/CookieManager.java
@@ -48,7 +48,7 @@
* The CookiePolicy and CookieStore used are customized. Third, use the
* customized CookiePolicy and the CookieStore.
*
- * This implementation conforms to RFC 2965, section 3.3.
+ * This implementation conforms to <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> section 3.3.
*
* @since 1.6
*/
@@ -248,4 +248,4 @@
public CookieStore getCookieStore() {
return store;
}
-}
\ No newline at end of file
+}
diff --git a/luni/src/main/java/java/net/CookiePolicy.java b/luni/src/main/java/java/net/CookiePolicy.java
index 03ea76e..44c8669 100644
--- a/luni/src/main/java/java/net/CookiePolicy.java
+++ b/luni/src/main/java/java/net/CookiePolicy.java
@@ -21,7 +21,7 @@
* and ACCEPT_ORIGINAL_SERVER respectively. They are used to decide which
* cookies should be accepted and which should not be.
*
- * See RFC 2965 sec. 3.3 & 7 for more detail.
+ * See <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> sections 3.3 and 7 for more detail.
*
* @since 1.6
*/
@@ -65,4 +65,4 @@
* @return true if this cookie should be accepted; false otherwise
*/
boolean shouldAccept(URI uri, HttpCookie cookie);
-}
\ No newline at end of file
+}
diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java
index 9c38cf8..95f1003 100644
--- a/luni/src/main/java/java/net/DatagramSocket.java
+++ b/luni/src/main/java/java/net/DatagramSocket.java
@@ -19,6 +19,9 @@
import java.io.IOException;
import java.nio.channels.DatagramChannel;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import static libcore.io.OsConstants.*;
/**
* This class implements a UDP socket for sending and receiving {@code
@@ -377,17 +380,19 @@
* via this socket are transmitted via the specified interface. Any
* packets received by this socket will come from the specified
* interface. Broadcast datagrams received on this interface will
- * be processed by this socket. {@see SocketOptions#SO_BINDTODEVICE}
+ * be processed by this socket. This corresponds to Linux's SO_BINDTODEVICE.
*
- * @hide
+ * @hide used by GoogleTV for DHCP
*/
public void setNetworkInterface(NetworkInterface netInterface) throws SocketException {
if (netInterface == null) {
throw new NullPointerException("networkInterface == null");
}
-
- impl.setOption(SocketOptions.SO_BINDTODEVICE,
- Integer.valueOf(netInterface.getIndex()));
+ try {
+ Libcore.os.setsockoptIfreq(impl.fd, SOL_SOCKET, SO_BINDTODEVICE, netInterface.getName());
+ } catch (ErrnoException errnoException) {
+ throw errnoException.rethrowAsSocketException();
+ }
}
/**
diff --git a/luni/src/main/java/java/net/HttpCookie.java b/luni/src/main/java/java/net/HttpCookie.java
index 88fcccb..057afef 100644
--- a/luni/src/main/java/java/net/HttpCookie.java
+++ b/luni/src/main/java/java/net/HttpCookie.java
@@ -16,9 +16,6 @@
package java.net;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -26,6 +23,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Set;
+import libcore.net.http.HttpDate;
import libcore.util.Objects;
/**
@@ -64,40 +62,6 @@
*/
public final class HttpCookie implements Cloneable {
- /**
- * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
- * cookies are on the fast path.
- */
- private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT
- = new ThreadLocal<DateFormat>() {
- @Override protected DateFormat initialValue() {
- return new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); // RFC 1123
- }
- };
-
- /**
- * If we fail to parse a date in a non-standard format, try each of these formats in sequence.
- */
- private static final String[] BROWSER_COMPATIBLE_DATE_FORMATS = new String[] {
- /* This list comes from {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */
- "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
- "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
- "EEE, dd-MMM-yyyy HH:mm:ss z",
- "EEE, dd-MMM-yyyy HH-mm-ss z",
- "EEE, dd MMM yy HH:mm:ss z",
- "EEE dd-MMM-yyyy HH:mm:ss z",
- "EEE dd MMM yyyy HH:mm:ss z",
- "EEE dd-MMM-yyyy HH-mm-ss z",
- "EEE dd-MMM-yy HH:mm:ss z",
- "EEE dd MMM yy HH:mm:ss z",
- "EEE,dd-MMM-yy HH:mm:ss z",
- "EEE,dd-MMM-yyyy HH:mm:ss z",
- "EEE, dd-MM-yyyy HH:mm:ss z",
-
- /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
- "EEE MMM d yyyy HH:mm:ss z",
- };
-
private static final Set<String> RESERVED_NAMES = new HashSet<String>();
static {
@@ -223,7 +187,8 @@
/**
* Constructs a cookie from a string. The string should comply with
- * set-cookie or set-cookie2 header format as specified in RFC 2965. Since
+ * set-cookie or set-cookie2 header format as specified in
+ * <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a>. Since
* set-cookies2 syntax allows more than one cookie definitions in one
* header, the returned object is a list.
*
@@ -359,7 +324,7 @@
} else if (name.equals("expires")) {
hasExpires = true;
if (cookie.maxAge == -1L) {
- Date date = parseHttpDate(value);
+ Date date = HttpDate.parse(value);
if (date != null) {
cookie.setExpires(date);
} else {
@@ -380,20 +345,6 @@
}
}
- private Date parseHttpDate(String value) {
- try {
- return STANDARD_DATE_FORMAT.get().parse(value);
- } catch (ParseException ignore) {
- }
- for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
- try {
- return new SimpleDateFormat(formatString, Locale.US).parse(value);
- } catch (ParseException ignore) {
- }
- }
- return null;
- }
-
/**
* Returns the next attribute name, or null if the input has been
* exhausted. Returns wth the cursor on the delimiter that follows.
diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java
index a717ae2..8376220 100644
--- a/luni/src/main/java/java/net/HttpURLConnection.java
+++ b/luni/src/main/java/java/net/HttpURLConnection.java
@@ -20,7 +20,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
-import libcore.net.http.HttpURLConnectionImpl;
+import libcore.net.http.HttpEngine;
/**
* An {@link URLConnection} for HTTP (<a
@@ -65,12 +65,18 @@
* }</pre>
*
* <h3>Secure Communication with HTTPS</h3>
- * Calling {@link URL#openConnection()} on a URL with the "https" scheme will
- * return an {@link javax.net.ssl.HttpsURLConnection HttpsURLConnection}.
- *
- * <p>This class attempts to create secure connections using common TLS
- * extensions and SSL deflate compression. Should that fail, the connection
- * will be retried with SSL3 only.
+
+ * Calling {@link URL#openConnection()} on a URL with the "https"
+ * scheme will return an {@code HttpsURLConnection}, which allows for
+ * overriding the default {@link javax.net.ssl.HostnameVerifier
+ * HostnameVerifier} and {@link javax.net.ssl.SSLSocketFactory
+ * SSLSocketFactory}. An application-supplied {@code SSLSocketFactory}
+ * created from an {@link javax.net.ssl.SSLContext SSLContext} can
+ * provide a custom {@link javax.net.ssl.X509TrustManager
+ * X509TrustManager} for verifying certificate chains and a custom
+ * {@link javax.net.ssl.X509KeyManager X509KeyManager} for supplying
+ * client certificates. See {@link javax.net.ssl.HttpsURLConnection
+ * HttpsURLConnection} for more details.
*
* <h3>Response Handling</h3>
* {@code HttpURLConnection} will follow up to five HTTP redirects. It will
@@ -244,10 +250,25 @@
public abstract class HttpURLConnection extends URLConnection {
/**
+ * The subset of HTTP methods that the user may select via {@link
+ * libcore.net.http.HttpURLConnectionImpl#setRequestMethod(String)}.
+ */
+ private static final String[] PERMITTED_USER_METHODS = {
+ HttpEngine.OPTIONS,
+ HttpEngine.GET,
+ HttpEngine.HEAD,
+ HttpEngine.POST,
+ HttpEngine.PUT,
+ HttpEngine.DELETE,
+ HttpEngine.TRACE
+ // Note: we don't allow users to specify "CONNECT"
+ };
+
+ /**
* The HTTP request method of this {@code HttpURLConnection}. The default
* value is {@code "GET"}.
*/
- protected String method = HttpURLConnectionImpl.GET;
+ protected String method = HttpEngine.GET;
/**
* The status code of the response obtained from the HTTP request. The
@@ -628,7 +649,7 @@
if (connected) {
throw new ProtocolException("Connection already established");
}
- for (String permittedUserMethod : HttpURLConnectionImpl.PERMITTED_USER_METHODS) {
+ for (String permittedUserMethod : PERMITTED_USER_METHODS) {
if (permittedUserMethod.equals(method)) {
// if there is a supported method that matches the desired
// method, then set the current method and return
@@ -638,7 +659,7 @@
}
// if none matches, then throw ProtocolException
throw new ProtocolException("Unknown method '" + method + "'; must be one of " +
- Arrays.toString(HttpURLConnectionImpl.PERMITTED_USER_METHODS));
+ Arrays.toString(PERMITTED_USER_METHODS));
}
/**
@@ -708,7 +729,7 @@
* @param contentLength
* the fixed length of the HTTP request body.
* @throws IllegalStateException
- * if already connected or an other mode already set.
+ * if already connected or another mode already set.
* @throws IllegalArgumentException
* if {@code contentLength} is less than zero.
*/
@@ -750,7 +771,7 @@
throw new IllegalStateException("Already in fixed-length mode");
}
if (chunkLength <= 0) {
- this.chunkLength = HttpURLConnectionImpl.DEFAULT_CHUNK_LENGTH;
+ this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH;
} else {
this.chunkLength = chunkLength;
}
diff --git a/luni/src/main/java/java/net/IDN.java b/luni/src/main/java/java/net/IDN.java
index 1ee1ca6..4e60209 100644
--- a/luni/src/main/java/java/net/IDN.java
+++ b/luni/src/main/java/java/net/IDN.java
@@ -33,7 +33,9 @@
public static final int ALLOW_UNASSIGNED = 1;
/**
- * When set, ASCII strings are checked against RFC 1122 & RFC 1123.
+ * When set, ASCII strings are checked against
+ * <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122</a> and
+ * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a>.
*/
public static final int USE_STD3_ASCII_RULES = 2;
@@ -42,7 +44,7 @@
/**
* Transform a Unicode String to ASCII Compatible Encoding String according
- * to the algorithm defined in RFC 3490.
+ * to the algorithm defined in <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
*
* <p>If the transformation fails (because the input is not a valid IDN), an
* exception will be thrown.
@@ -56,7 +58,7 @@
* @param flags 0, {@code ALLOW_UNASSIGNED}, {@code USE_STD3_ASCII_RULES},
* or {@code ALLOW_UNASSIGNED | USE_STD3_ASCII_RULES}
* @return the ACE name
- * @throws IllegalArgumentException if {@code input} does not conform to RFC 3490
+ * @throws IllegalArgumentException if {@code input} does not conform to <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>
*/
public static String toASCII(String input, int flags) {
return NativeIDN.toASCII(input, flags);
@@ -67,7 +69,7 @@
*
* @param input the Unicode name
* @return the ACE name
- * @throws IllegalArgumentException if {@code input} does not conform to RFC 3490
+ * @throws IllegalArgumentException if {@code input} does not conform to <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>
*/
public static String toASCII(String input) {
return toASCII(input, 0);
@@ -75,7 +77,7 @@
/**
* Translates a string from ASCII Compatible Encoding (ACE) to Unicode
- * according to the algorithm defined in RFC 3490.
+ * according to the algorithm defined in <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
*
* <p>Unlike {@code toASCII}, this transformation cannot fail.
*
diff --git a/luni/src/main/java/java/net/Inet4Address.java b/luni/src/main/java/java/net/Inet4Address.java
index 727328e..a12eacd 100644
--- a/luni/src/main/java/java/net/Inet4Address.java
+++ b/luni/src/main/java/java/net/Inet4Address.java
@@ -49,10 +49,8 @@
public static final InetAddress LOOPBACK =
new Inet4Address(new byte[] { 127, 0, 0, 1 }, "localhost");
- Inet4Address(byte[] address, String name) {
- family = AF_INET;
- ipaddress = address;
- hostName = name;
+ Inet4Address(byte[] ipaddress, String hostName) {
+ super(AF_INET, ipaddress, hostName);
}
/**
@@ -98,8 +96,8 @@
/**
* Returns whether this address has a link-local scope or not.
- * <p>
- * RFC 3484 <br>
+ *
+ * <p><a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a>
* Default Address Selection for Internet Protocol Version 6 (IPv6) states
* IPv4 auto-configuration addresses, prefix 169.254/16, IPv4 loopback
* addresses, prefix 127/8, are assigned link-local scope.
@@ -116,8 +114,8 @@
/**
* Returns whether this address has a site-local scope or not.
- * <p>
- * RFC 3484 <br>
+ *
+ * <p><a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a>
* Default Address Selection for Internet Protocol Version 6 (IPv6) states
* IPv4 private addresses, prefixes 10/8, 172.16/12, and 192.168/16, are
* assigned site-local scope.
diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java
index 0880f3f..c253a50 100644
--- a/luni/src/main/java/java/net/Inet6Address.java
+++ b/luni/src/main/java/java/net/Inet6Address.java
@@ -33,25 +33,25 @@
private static final int AF_INET6 = 10;
- static final InetAddress ANY =
+ /**
+ * @hide
+ */
+ public static final InetAddress ANY =
new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, null, 0);
- static final InetAddress LOOPBACK =
+
+ /**
+ * @hide
+ */
+ public static final InetAddress LOOPBACK =
new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
"localhost", 0);
+ boolean scope_id_set;
int scope_id;
- boolean scope_id_set;
-
boolean scope_ifname_set;
-
String ifname;
- /*
- * scoped interface.
- */
- transient NetworkInterface scopedIf;
-
/**
* Constructs an {@code InetAddress} representing the {@code address} and
* {@code name} and {@code scope_id}.
@@ -63,10 +63,8 @@
* @param scope_id
* the scope id for link- or site-local addresses.
*/
- Inet6Address(byte[] address, String name, int scope_id) {
- this.family = AF_INET6;
- this.hostName = name;
- this.ipaddress = address;
+ Inet6Address(byte[] ipaddress, String hostName, int scope_id) {
+ super(AF_INET6, ipaddress, hostName);
this.scope_id = scope_id;
this.scope_id_set = (scope_id != 0);
}
@@ -126,7 +124,7 @@
}
// find the first address which matches the type addr,
- // then set the scope_id, ifname and scopedIf.
+ // then set the scope_id and ifname.
Enumeration<InetAddress> addressList = nif.getInetAddresses();
while (addressList.hasMoreElements()) {
InetAddress ia = addressList.nextElement();
@@ -138,7 +136,6 @@
address.scope_id = v6ia.scope_id;
address.scope_ifname_set = true;
address.ifname = nif.getName();
- address.scopedIf = nif;
break;
}
}
@@ -174,84 +171,40 @@
}
/**
- * Returns whether this address is an IP multicast address or not. Valid
- * IPv6 multicast addresses are binary prefixed with 11111111 or FF (hex).
- *
- * @return {@code true} if this address is in the multicast group, {@code
- * false} otherwise.
+ * Returns whether this address is an IP multicast address or not.
*/
- @Override
- public boolean isMulticastAddress() {
+ @Override public boolean isMulticastAddress() {
// Multicast addresses are prefixed with 11111111 (255)
return ipaddress[0] == -1;
}
/**
- * Returns whether this address is a unspecified wildcard address "::" or
- * not.
- *
- * @return {@code true} if this instance represents a wildcard address,
- * {@code false} otherwise.
+ * Returns true if this address is the unspecified wildcard address "::".
*/
- @Override
- public boolean isAnyLocalAddress() {
- for (int i = 0; i < ipaddress.length; i++) {
- if (ipaddress[i] != 0) {
- return false;
- }
- }
- return true;
+ @Override public boolean isAnyLocalAddress() {
+ return Arrays.equals(ipaddress, Inet6Address.ANY.ipaddress);
}
/**
* Returns whether this address is the loopback address or not. The only
* valid IPv6 loopback address is "::1".
- *
- * @return {@code true} if this instance represents the loopback address,
- * {@code false} otherwise.
*/
- @Override
- public boolean isLoopbackAddress() {
-
- // The last word must be 1
- if (ipaddress[15] != 1) {
- return false;
- }
-
- // All other words must be 0
- for (int i = 0; i < 15; i++) {
- if (ipaddress[i] != 0) {
- return false;
- }
- }
-
- return true;
+ @Override public boolean isLoopbackAddress() {
+ return Arrays.equals(ipaddress, Inet6Address.LOOPBACK.ipaddress);
}
/**
- * Returns whether this address is a link-local address or not. A valid IPv6
- * link-local address is prefixed with 1111111010.
- *
- * @return {@code true} if this instance represents a link-local address,
- * {@code false} otherwise.
+ * Returns whether this address is a link-local address or not.
*/
- @Override
- public boolean isLinkLocalAddress() {
-
+ @Override public boolean isLinkLocalAddress() {
// the first 10 bits need to be 1111111010 (1018)
return (ipaddress[0] == -2) && ((ipaddress[1] & 255) >>> 6) == 2;
}
/**
- * Returns whether this address is a site-local address or not. A valid IPv6
- * site-local address is prefixed with 1111111011.
- *
- * @return {@code true} if this instance represents a site-local address,
- * {@code false} otherwise.
+ * Returns whether this address is a site-local address or not.
*/
- @Override
- public boolean isSiteLocalAddress() {
-
+ @Override public boolean isSiteLocalAddress() {
// the first 10 bits need to be 1111111011 (1019)
return (ipaddress[0] == -2) && ((ipaddress[1] & 255) >>> 6) == 3;
}
@@ -259,57 +212,35 @@
/**
* Returns whether this address is a global multicast address or not. A
* valid IPv6 global multicast address is 11111111xxxx1110 or FF0E hex.
- *
- * @return {@code true} if this instance represents a global multicast
- * address, {@code false} otherwise.
*/
- @Override
- public boolean isMCGlobal() {
+ @Override public boolean isMCGlobal() {
// the first byte should be 0xFF and the lower 4 bits
// of the second byte should be 0xE
return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 14;
}
/**
- * Returns whether this address is a node-local multicast address or not. A
- * valid IPv6 node-local multicast address is prefixed with
- * 11111111xxxx0001.
- *
- * @return {@code true} if this instance represents a node-local multicast
- * address, {@code false} otherwise.
+ * Returns whether this address is a node-local multicast address or not.
*/
- @Override
- public boolean isMCNodeLocal() {
+ @Override public boolean isMCNodeLocal() {
// the first byte should be 0xFF and the lower 4 bits
// of the second byte should be 0x1
return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 1;
}
/**
- * Returns whether this address is a link-local multicast address or not. A
- * valid IPv6 link-local multicast address is prefixed with
- * 11111111xxxx0010.
- *
- * @return {@code true} if this instance represents a link-local multicast
- * address, {@code false} otherwise.
+ * Returns whether this address is a link-local multicast address or not.
*/
- @Override
- public boolean isMCLinkLocal() {
+ @Override public boolean isMCLinkLocal() {
// the first byte should be 0xFF and the lower 4 bits
// of the second byte should be 0x2
return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 2;
}
/**
- * Returns whether this address is a site-local multicast address or not. A
- * valid IPv6 site-local multicast address is prefixed with
- * 11111111xxxx0101.
- *
- * @return {@code true} if this instance represents a site-local multicast
- * address, {@code false} otherwise.
+ * Returns whether this address is a site-local multicast address or not.
*/
- @Override
- public boolean isMCSiteLocal() {
+ @Override public boolean isMCSiteLocal() {
// the first byte should be 0xFF and the lower 4 bits
// of the second byte should be 0x5
return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 5;
@@ -317,53 +248,35 @@
/**
* Returns whether this address is a organization-local multicast address or
- * not. A valid IPv6 org-local multicast address is prefixed with
- * 11111111xxxx1000.
- *
- * @return {@code true} if this instance represents a org-local multicast
- * address, {@code false} otherwise.
+ * not.
*/
- @Override
- public boolean isMCOrgLocal() {
+ @Override public boolean isMCOrgLocal() {
// the first byte should be 0xFF and the lower 4 bits
// of the second byte should be 0x8
return (ipaddress[0] == -1) && (ipaddress[1] & 15) == 8;
}
/**
- * Gets the scope id as a number if this address is linked to an interface.
- * Otherwise returns {@code 0}.
- *
- * @return the scope_id of this address or 0 when not linked with an
- * interface.
+ * Returns the scope id if this address is scoped to an interface, 0 otherwise.
*/
public int getScopeId() {
- if (scope_id_set) {
- return scope_id;
- }
- return 0;
+ return scope_id_set ? scope_id : 0;
}
/**
- * Gets the network interface if this address is instanced with a scoped
- * network interface. Otherwise returns {@code null}.
- *
- * @return the scoped network interface of this address.
+ * Returns the network interface if this address is instanced with a scoped
+ * network interface, null otherwise.
*/
public NetworkInterface getScopedInterface() {
- if (scope_ifname_set) {
- return scopedIf;
+ try {
+ return scope_ifname_set ? NetworkInterface.getByName(ifname) : null;
+ } catch (SocketException ex) {
+ return null;
}
- return null;
}
/**
- * Returns whether this address is IPv4 compatible or not. An IPv4
- * compatible address is prefixed with 96 bits of 0's. The last 32-bits are
- * varied corresponding with the 32-bit IPv4 address space.
- *
- * @return {@code true} if this instance represents an IPv4 compatible
- * address, {@code false} otherwise.
+ * Returns whether this address is an IPv4-compatible address or not.
*/
public boolean isIPv4CompatibleAddress() {
for (int i = 0; i < 12; i++) {
@@ -397,27 +310,16 @@
stream.writeFields();
}
- private void readObject(ObjectInputStream stream) throws IOException,
- ClassNotFoundException {
+ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = stream.readFields();
ipaddress = (byte[]) fields.get("ipaddress", null);
scope_id = fields.get("scope_id", 0);
scope_id_set = fields.get("scope_id_set", false);
ifname = (String) fields.get("ifname", null);
scope_ifname_set = fields.get("scope_ifname_set", false);
- if (scope_ifname_set && ifname != null) {
- scopedIf = NetworkInterface.getByName(ifname);
- }
}
- /**
- * Returns a string containing a concise, human-readable description of this
- * IP address.
- *
- * @return the description, as host/address.
- */
- @Override
- public String toString() {
+ @Override public String toString() {
if (ifname != null) {
return super.toString() + "%" + ifname;
}
diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java
index f77a901..463bff4 100644
--- a/luni/src/main/java/java/net/InetAddress.java
+++ b/luni/src/main/java/java/net/InetAddress.java
@@ -31,10 +31,13 @@
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
+import libcore.io.GaiException;
import libcore.io.Libcore;
import libcore.io.IoUtils;
import libcore.io.Memory;
+import libcore.io.StructAddrinfo;
import org.apache.harmony.luni.platform.Platform;
+import static libcore.io.OsConstants.*;
/**
* An Internet Protocol (IP) address. This can be either an IPv4 address or an IPv6 address, and
@@ -144,21 +147,18 @@
private static final long serialVersionUID = 3286316764910316507L;
- String hostName;
-
- private static class WaitReachable {
- }
-
- private transient Object waitReachable = new WaitReachable();
+ private transient Object waitReachable = new Object();
private boolean reached;
private int addrCount;
- int family = 0;
+ private int family;
byte[] ipaddress;
+ String hostName;
+
/**
* Constructs an {@code InetAddress}.
*
@@ -171,7 +171,11 @@
* InetAddresses (e.g., getByAddress). That is why the API does not have
* public constructors for any of these classes.
*/
- InetAddress() {}
+ InetAddress(int family, byte[] ipaddress, String hostName) {
+ this.family = family;
+ this.ipaddress = ipaddress;
+ this.hostName = hostName;
+ }
/**
* Compares this {@code InetAddress} instance against the specified address
@@ -201,30 +205,16 @@
return ipaddress.clone();
}
- static final Comparator<byte[]> SHORTEST_FIRST = new Comparator<byte[]>() {
- public int compare(byte[] a1, byte[] a2) {
- return a1.length - a2.length;
- }
- };
-
/**
* Converts an array of byte arrays representing raw IP addresses of a host
- * to an array of InetAddress objects, sorting to respect the value of the
- * system property {@code "java.net.preferIPv6Addresses"}.
+ * to an array of InetAddress objects.
*
* @param rawAddresses the raw addresses to convert.
* @param hostName the hostname corresponding to the IP address.
* @return the corresponding InetAddresses, appropriately sorted.
*/
- static InetAddress[] bytesToInetAddresses(byte[][] rawAddresses, String hostName)
+ private static InetAddress[] bytesToInetAddresses(byte[][] rawAddresses, String hostName)
throws UnknownHostException {
- // If we prefer IPv4, ignore the RFC3484 ordering we get from getaddrinfo(3)
- // and always put IPv4 addresses first. Arrays.sort() is stable, so the
- // internal ordering will not be changed.
- if (!preferIPv6Addresses()) {
- Arrays.sort(rawAddresses, SHORTEST_FIRST);
- }
-
// Convert the byte arrays to InetAddresses.
InetAddress[] returnedAddresses = new InetAddress[rawAddresses.length];
for (int i = 0; i < rawAddresses.length; i++) {
@@ -253,23 +243,18 @@
* Returns the InetAddresses for {@code host}. The returned array is shared
* and must be cloned before it is returned to application code.
*/
- static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
+ private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException {
if (host == null || host.isEmpty()) {
return loopbackAddresses();
}
- // Special-case "0" for legacy IPv4 applications.
- if (host.equals("0")) {
- return new InetAddress[] { Inet4Address.ANY };
- }
-
// Is it a numeric address?
- byte[] bytes = ipStringToByteArray(host);
- if (bytes != null) {
- return new InetAddress[] { makeInetAddress(bytes, null) };
+ InetAddress result = parseNumericAddressNoThrow(host);
+ if (result != null) {
+ return new InetAddress[] { result };
}
- return lookupHostByName(host);
+ return lookupHostByName(host).clone();
}
private static InetAddress makeInetAddress(byte[] bytes, String hostName) throws UnknownHostException {
@@ -282,13 +267,26 @@
}
}
- private static native String byteArrayToIpString(byte[] address);
-
- static native byte[] ipStringToByteArray(String address);
-
- static boolean preferIPv6Addresses() {
- String propertyValue = System.getProperty("java.net.preferIPv6Addresses");
- return Boolean.parseBoolean(propertyValue);
+ private static InetAddress parseNumericAddressNoThrow(String address) {
+ // Accept IPv6 addresses (only) in square brackets for compatibility.
+ if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) {
+ address = address.substring(1, address.length() - 1);
+ }
+ StructAddrinfo hints = new StructAddrinfo();
+ hints.ai_flags = AI_NUMERICHOST;
+ InetAddress[] addresses = null;
+ try {
+ addresses = Libcore.os.getaddrinfo(address, hints);
+ } catch (GaiException ignored) {
+ }
+ if (addresses == null) {
+ // For backwards compatibility, deal with address formats that
+ // getaddrinfo does not support. For example, 1.2.3, 1.3, and even 3 are
+ // valid IPv4 addresses according to the Java API. If getaddrinfo fails,
+ // try to use inet_aton.
+ return Libcore.os.inet_aton(address);
+ }
+ return addresses[0];
}
/**
@@ -309,62 +307,37 @@
}
/**
- * Gets the textual representation of this IP address.
- *
- * @return the textual representation of host's IP address.
+ * Returns the numeric representation of this IP address (such as "127.0.0.1").
*/
public String getHostAddress() {
- return byteArrayToIpString(ipaddress);
+ return Libcore.os.getnameinfo(this, NI_NUMERICHOST); // Can't throw.
}
/**
- * Gets the host name of this IP address. If the IP address could not be
- * resolved, the textual representation in a dotted-quad-notation is
- * returned.
- *
- * @return the corresponding string name of this IP address.
+ * Returns the host name corresponding to this IP address. This may or may not be a
+ * fully-qualified name. If the IP address could not be resolved, the numeric representation
+ * is returned instead (see {@link #getHostAddress}).
*/
public String getHostName() {
- try {
- if (hostName == null) {
- int address = 0;
- if (ipaddress.length == 4) {
- address = Memory.peekInt(ipaddress, 0, ByteOrder.BIG_ENDIAN);
- if (address == 0) {
- return hostName = byteArrayToIpString(ipaddress);
- }
- }
- hostName = getHostByAddrImpl(ipaddress).hostName;
- if (hostName.equals("localhost") && ipaddress.length == 4
- && address != 0x7f000001) {
- return hostName = byteArrayToIpString(ipaddress);
- }
+ if (hostName == null) {
+ try {
+ hostName = getHostByAddrImpl(this).hostName;
+ } catch (UnknownHostException ex) {
+ hostName = getHostAddress();
}
- } catch (UnknownHostException e) {
- return hostName = byteArrayToIpString(ipaddress);
}
return hostName;
}
/**
- * Returns the fully qualified domain name for the host associated with this IP
- * address.
+ * Returns the fully qualified hostname corresponding to this IP address.
*/
public String getCanonicalHostName() {
- String canonicalName;
try {
- int address = 0;
- if (ipaddress.length == 4) {
- address = Memory.peekInt(ipaddress, 0, ByteOrder.BIG_ENDIAN);
- if (address == 0) {
- return byteArrayToIpString(ipaddress);
- }
- }
- canonicalName = getHostByAddrImpl(ipaddress).hostName;
- } catch (UnknownHostException e) {
- return byteArrayToIpString(ipaddress);
+ return getHostByAddrImpl(this).hostName;
+ } catch (UnknownHostException ex) {
+ return getHostAddress();
}
- return canonicalName;
}
/**
@@ -450,16 +423,31 @@
}
}
try {
- InetAddress[] addresses = bytesToInetAddresses(getaddrinfo(host), host);
+ StructAddrinfo hints = new StructAddrinfo();
+ hints.ai_flags = AI_ADDRCONFIG;
+ hints.ai_family = AF_UNSPEC;
+ // If we don't specify a socket type, every address will appear twice, once
+ // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
+ // anyway, just pick one.
+ hints.ai_socktype = SOCK_STREAM;
+ InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
+ // TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
+ for (InetAddress address : addresses) {
+ address.hostName = host;
+ }
addressCache.put(host, addresses);
return addresses;
- } catch (UnknownHostException e) {
- String detailMessage = e.getMessage();
+ } catch (GaiException gaiException) {
+ // TODO: bionic currently returns EAI_NODATA, which is indistinguishable from a real
+ // failure. We need to fix bionic before we can report a more useful error.
+ // if (gaiException.error == EAI_SYSTEM) {
+ // throw new SecurityException("Permission denied (missing INTERNET permission?)");
+ // }
+ String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
addressCache.putUnknownHost(host, detailMessage);
- throw new UnknownHostException(detailMessage);
+ throw gaiException.rethrowAsUnknownHostException(detailMessage);
}
}
- private static native byte[][] getaddrinfo(String name) throws UnknownHostException;
/**
* Removes all entries from the VM's DNS cache. This does not affect the C library's DNS
@@ -470,32 +458,14 @@
addressCache.clear();
}
- /**
- * Query the IP stack for the host address. The host is in address form.
- *
- * @param addr
- * the host address to lookup.
- * @throws UnknownHostException
- * if an error occurs during lookup.
- */
- static InetAddress getHostByAddrImpl(byte[] addr) throws UnknownHostException {
+ private static InetAddress getHostByAddrImpl(InetAddress address) throws UnknownHostException {
BlockGuard.getThreadPolicy().onNetwork();
- return makeInetAddress(addr, getnameinfo(addr));
- }
-
- /**
- * Resolves an IP address to a hostname. Thread safe.
- */
- private static native String getnameinfo(byte[] addr);
-
- static String getHostNameInternal(String host) throws UnknownHostException {
- if (host == null || host.isEmpty()) {
- return Inet4Address.LOOPBACK.getHostAddress();
+ try {
+ String hostname = Libcore.os.getnameinfo(address, NI_NAMEREQD);
+ return makeInetAddress(address.ipaddress.clone(), hostname);
+ } catch (GaiException gaiException) {
+ throw gaiException.rethrowAsUnknownHostException();
}
- if (!isNumeric(host)) {
- return lookupHostByName(host)[0].getHostAddress();
- }
- return host;
}
/**
@@ -517,7 +487,7 @@
* @hide used by frameworks/base to ensure that a getAllByName won't cause a DNS lookup.
*/
public static boolean isNumeric(String address) {
- return ipStringToByteArray(address) != null;
+ return parseNumericAddressNoThrow(address) != null;
}
/**
@@ -530,26 +500,17 @@
*/
public static InetAddress parseNumericAddress(String numericAddress) {
if (numericAddress == null || numericAddress.isEmpty()) {
- return loopbackAddresses()[0];
+ return Inet6Address.LOOPBACK;
}
- byte[] bytes = ipStringToByteArray(numericAddress);
- if (bytes == null) {
+ InetAddress result = parseNumericAddressNoThrow(numericAddress);
+ if (result == null) {
throw new IllegalArgumentException("Not a numeric address: " + numericAddress);
}
- try {
- return makeInetAddress(bytes, null);
- } catch (UnknownHostException ex) {
- // UnknownHostException can't be thrown if you pass null to makeInetAddress.
- throw new AssertionError(ex);
- }
+ return result;
}
private static InetAddress[] loopbackAddresses() {
- if (preferIPv6Addresses()) {
- return new InetAddress[] { Inet6Address.LOOPBACK, Inet4Address.LOOPBACK };
- } else {
- return new InetAddress[] { Inet4Address.LOOPBACK, Inet6Address.LOOPBACK };
- }
+ return new InetAddress[] { Inet6Address.LOOPBACK, Inet4Address.LOOPBACK };
}
/**
@@ -558,7 +519,7 @@
* @hide 1.7
*/
public static InetAddress getLoopbackAddress() {
- return loopbackAddresses()[0];
+ return Inet6Address.LOOPBACK;
}
/**
@@ -855,7 +816,7 @@
if (source != null) {
Platform.NETWORK.bind(fd, source, 0);
}
- Platform.NETWORK.connect(fd, destination, 7, timeout);
+ IoUtils.connect(fd, destination, 7, timeout);
reached = true;
} catch (IOException e) {
if (ERRMSG_CONNECTION_REFUSED.equals(e.getMessage())) {
@@ -881,17 +842,6 @@
}
/**
- * Equivalent to {@code getByAddress(null, ipAddress, scopeId)}. Handy for IPv6 addresses
- * with no associated hostname.
- *
- * <p>(Note that numeric addresses such as {@code "127.0.0.1"} are names for the
- * purposes of this API. Most callers probably want {@link #getAllByName} instead.)
- */
- static InetAddress getByAddress(byte[] ipAddress, int scopeId) throws UnknownHostException {
- return getByAddressInternal(null, ipAddress, scopeId);
- }
-
- /**
* Equivalent to {@code getByAddress(hostName, ipAddress, 0)}. Handy for IPv4 addresses
* with an associated hostname.
*
@@ -918,7 +868,7 @@
*
* @throws UnknownHostException if {@code ipAddress} is null or the wrong length.
*/
- static InetAddress getByAddressInternal(String hostName, byte[] ipAddress, int scopeId)
+ private static InetAddress getByAddressInternal(String hostName, byte[] ipAddress, int scopeId)
throws UnknownHostException {
if (ipAddress == null) {
throw new UnknownHostException("ipAddress == null");
@@ -999,7 +949,7 @@
/*
* The spec requires that if we encounter a generic InetAddress in
- * serialized form then we should interpret it as an Inet4 address.
+ * serialized form then we should interpret it as an Inet4Address.
*/
private Object readResolve() throws ObjectStreamException {
return new Inet4Address(ipaddress, hostName);
diff --git a/luni/src/main/java/java/net/InterfaceAddress.java b/luni/src/main/java/java/net/InterfaceAddress.java
index 2f8029e..612eeb4 100644
--- a/luni/src/main/java/java/net/InterfaceAddress.java
+++ b/luni/src/main/java/java/net/InterfaceAddress.java
@@ -25,21 +25,9 @@
*/
public class InterfaceAddress {
/**
- * The kernel's interface index for the network interface this address
- * is currently assigned to. Values start at 1, because 0 means "unknown"
- * or "any", depending on context.
- */
- final int index;
-
- /**
- * The network interface's name. "lo" or "eth0", for example.
- */
- final String name;
-
- /**
* An IPv4 or IPv6 address.
*/
- final InetAddress address;
+ private final InetAddress address;
/**
* The IPv4 broadcast address, or null for IPv6.
@@ -48,31 +36,25 @@
private final short prefixLength;
- InterfaceAddress(int index, String name, InetAddress address, InetAddress mask) {
- assert ((address instanceof Inet4Address) == (mask instanceof Inet4Address));
- this.index = index;
- this.name = name;
+ /**
+ * For IPv4.
+ */
+ InterfaceAddress(Inet4Address address, Inet4Address broadcastAddress, Inet4Address mask) {
this.address = address;
- this.broadcastAddress = makeBroadcastAddress(address, mask);
+ this.broadcastAddress = broadcastAddress;
this.prefixLength = countPrefixLength(mask);
}
- private static InetAddress makeBroadcastAddress(InetAddress address, InetAddress mask) {
- if (!(address instanceof Inet4Address)) {
- return null;
- }
- byte[] broadcast = new byte[4];
- byte[] maskBytes = mask.ipaddress;
- byte[] addrBytes = address.ipaddress;
- if (maskBytes[0] != 0) {
- for (int i = 0; i < broadcast.length; ++i) {
- broadcast[i] = (byte) (addrBytes[i] | ~maskBytes[i]);
- }
- }
- return new Inet4Address(broadcast, null);
+ /**
+ * For IPv6.
+ */
+ InterfaceAddress(Inet6Address address, short prefixLength) {
+ this.address = address;
+ this.broadcastAddress = null;
+ this.prefixLength = prefixLength;
}
- private static short countPrefixLength(InetAddress mask) {
+ private static short countPrefixLength(Inet4Address mask) {
short count = 0;
for (byte b : mask.ipaddress) {
for (int i = 0; i < 8; ++i) {
diff --git a/luni/src/main/java/java/net/MalformedURLException.java b/luni/src/main/java/java/net/MalformedURLException.java
index 66bbb54..77965e8 100644
--- a/luni/src/main/java/java/net/MalformedURLException.java
+++ b/luni/src/main/java/java/net/MalformedURLException.java
@@ -30,17 +30,13 @@
private static final long serialVersionUID = -182787522200415866L;
/**
- * Constructs a new instance of this class with its walkback filled in.
+ * Constructs a new instance with the current stack trace.
*/
public MalformedURLException() {
}
/**
- * Constructs a new instance of this class with its walkback and message
- * filled in.
- *
- * @param detailMessage
- * the detail message for this exception instance.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public MalformedURLException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/net/MulticastGroupRequest.java b/luni/src/main/java/java/net/MulticastGroupRequest.java
deleted file mode 100644
index 91c68f5..0000000
--- a/luni/src/main/java/java/net/MulticastGroupRequest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-/**
- * Used to pass an interface index and multicast address to native code.
- *
- * @hide PlainDatagramSocketImpl use only
- */
-public final class MulticastGroupRequest {
- private final int gr_interface;
- private final InetAddress gr_group;
-
- public MulticastGroupRequest(InetAddress groupAddress, NetworkInterface networkInterface) {
- gr_group = groupAddress;
- gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
- }
-}
diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java
index a04960f..1c422f2 100644
--- a/luni/src/main/java/java/net/MulticastSocket.java
+++ b/luni/src/main/java/java/net/MulticastSocket.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.util.Enumeration;
+import libcore.io.IoUtils;
/**
* This class implements a multicast socket for sending and receiving IP
@@ -27,133 +28,95 @@
* @see DatagramSocket
*/
public class MulticastSocket extends DatagramSocket {
- private static final int NO_INTERFACE_INDEX = 0;
- private static final int UNSET_INTERFACE_INDEX = -1;
-
- private InetAddress interfaceSet;
+ /**
+ * Stores the address supplied to setInterface so we can return it from getInterface. The
+ * translation to an interface index is lossy because an interface can have multiple addresses.
+ */
+ private InetAddress setAddress;
/**
* Constructs a multicast socket, bound to any available port on the
- * localhost.
+ * local host.
*
- * @throws IOException
- * if an error occurs creating or binding the socket.
+ * @throws IOException if an error occurs.
*/
public MulticastSocket() throws IOException {
setReuseAddress(true);
}
/**
- * Constructs a multicast socket, bound to the specified port on the
- * localhost.
+ * Constructs a multicast socket, bound to the specified {@code port} on the
+ * local host.
*
- * @param aPort
- * the port to bind on the localhost.
- * @throws IOException
- * if an error occurs creating or binding the socket.
+ * @throws IOException if an error occurs.
*/
- public MulticastSocket(int aPort) throws IOException {
- super(aPort);
+ public MulticastSocket(int port) throws IOException {
+ super(port);
setReuseAddress(true);
}
/**
- * Gets the network address used by this socket. This is useful on
- * multihomed machines.
+ * Constructs a {@code MulticastSocket} bound to the address and port specified by
+ * {@code localAddress}, or an unbound {@code MulticastSocket} if {@code localAddress == null}.
*
- * @return the address of the network interface through which the datagram
- * packets are sent or received.
- * @throws SocketException
- * if an error occurs while getting the interface address.
+ * @throws IllegalArgumentException if {@code localAddress} is not supported (because it's not
+ * an {@code InetSocketAddress}, say).
+ * @throws IOException if an error occurs.
+ */
+ public MulticastSocket(SocketAddress localAddress) throws IOException {
+ super(localAddress);
+ setReuseAddress(true);
+ }
+
+ /**
+ * Returns an address of the outgoing network interface used by this socket. To avoid
+ * inherent unpredictability, new code should use {@link #getNetworkInterface} instead.
+ *
+ * @throws SocketException if an error occurs.
*/
public InetAddress getInterface() throws SocketException {
checkClosedAndBind(false);
- if (interfaceSet == null) {
- InetAddress ipvXaddress = (InetAddress) impl
- .getOption(SocketOptions.IP_MULTICAST_IF);
- if (ipvXaddress.isAnyLocalAddress()) {
- // the address was not set at the IPv4 level so check the IPv6
- // level
- NetworkInterface theInterface = getNetworkInterface();
- if (theInterface != null) {
- Enumeration<InetAddress> addresses = theInterface
- .getInetAddresses();
- if (addresses != null) {
- while (addresses.hasMoreElements()) {
- InetAddress nextAddress = addresses.nextElement();
- if (nextAddress instanceof Inet6Address) {
- return nextAddress;
- }
+ if (setAddress != null) {
+ return setAddress;
+ }
+ InetAddress ipvXaddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF);
+ if (ipvXaddress.isAnyLocalAddress()) {
+ // the address was not set at the IPv4 level so check the IPv6
+ // level
+ NetworkInterface theInterface = getNetworkInterface();
+ if (theInterface != null) {
+ Enumeration<InetAddress> addresses = theInterface.getInetAddresses();
+ if (addresses != null) {
+ while (addresses.hasMoreElements()) {
+ InetAddress nextAddress = addresses.nextElement();
+ if (nextAddress instanceof Inet6Address) {
+ return nextAddress;
}
}
}
}
- return ipvXaddress;
}
- return interfaceSet;
+ return ipvXaddress;
}
/**
- * Gets the network interface used by this socket. This is useful on
- * multihomed machines.
+ * Returns the outgoing network interface used by this socket.
*
- * @return the network interface used by this socket or {@code null} if no
- * interface is set.
- * @throws SocketException
- * if an error occurs while getting the interface.
- * @since 1.4
+ * @throws SocketException if an error occurs.
*/
public NetworkInterface getNetworkInterface() throws SocketException {
checkClosedAndBind(false);
-
- // check if it is set at the IPv6 level. If so then use that. Otherwise
- // do it at the IPv4 level
- Integer theIndex = Integer.valueOf(0);
- try {
- theIndex = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2);
- } catch (SocketException e) {
- // we may get an exception if IPv6 is not enabled.
+ int index = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2);
+ if (index != 0) {
+ return NetworkInterface.getByIndex(index);
}
-
- if (theIndex.intValue() != 0) {
- Enumeration<NetworkInterface> theInterfaces = NetworkInterface.getNetworkInterfaces();
- while (theInterfaces.hasMoreElements()) {
- NetworkInterface nextInterface = theInterfaces.nextElement();
- if (nextInterface.getIndex() == theIndex.intValue()) {
- return nextInterface;
- }
- }
- }
-
- // ok it was not set at the IPv6 level so try at the IPv4 level
- InetAddress theAddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF);
- if (theAddress != null) {
- if (!theAddress.isAnyLocalAddress()) {
- return NetworkInterface.getByInetAddress(theAddress);
- }
-
- // not set as we got the any address so return a dummy network
- // interface with only the any address. We do this to be
- // compatible
- InetAddress theAddresses[] = new InetAddress[1];
- if (InetAddress.preferIPv6Addresses()) {
- theAddresses[0] = Inet6Address.ANY;
- } else {
- theAddresses[0] = Inet4Address.ANY;
- }
- return new NetworkInterface(null, null, theAddresses, UNSET_INTERFACE_INDEX);
- }
-
- // ok not set at all so return null
- return null;
+ return NetworkInterface.forUnboundMulticastSocket();
}
/**
- * Gets the time-to-live (TTL) for multicast packets sent on this socket.
+ * Returns the time-to-live (TTL) for multicast packets sent on this socket.
*
- * @return the default value for the time-to-life field.
- * @throws IOException
- * if an error occurs reading the default value.
+ * @throws IOException if an error occurs.
*/
public int getTimeToLive() throws IOException {
checkClosedAndBind(false);
@@ -161,13 +124,10 @@
}
/**
- * Gets the time-to-live (TTL) for multicast packets sent on this socket.
+ * Returns the time-to-live (TTL) for multicast packets sent on this socket.
*
- * @return the default value for the time-to-life field.
- * @throws IOException
- * if an error occurs reading the default value.
+ * @throws IOException if an error occurs.
* @deprecated Replaced by {@link #getTimeToLive}
- * @see #getTimeToLive()
*/
@Deprecated
public byte getTTL() throws IOException {
@@ -182,8 +142,7 @@
*
* @param groupAddr
* the multicast group to be joined.
- * @throws IOException
- * if an error occurs while joining a group.
+ * @throws IOException if an error occurs.
*/
public void joinGroup(InetAddress groupAddr) throws IOException {
checkJoinOrLeave(groupAddr);
@@ -204,7 +163,6 @@
* if the specified address is not a multicast address.
* @throws IllegalArgumentException
* if no multicast group is specified.
- * @since 1.4
*/
public void joinGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
checkJoinOrLeave(groupAddress, netInterface);
@@ -238,7 +196,6 @@
* if the specified group address is not a multicast address.
* @throws IllegalArgumentException
* if {@code groupAddress} is {@code null}.
- * @since 1.4
*/
public void leaveGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
checkJoinOrLeave(groupAddress, netInterface);
@@ -251,7 +208,7 @@
throw new IllegalArgumentException("groupAddress == null");
}
- if ((netInterface != null) && (netInterface.getFirstAddress() == null)) {
+ if (netInterface != null && !netInterface.getInetAddresses().hasMoreElements()) {
throw new SocketException("No address associated with interface: " + netInterface);
}
@@ -278,182 +235,71 @@
}
/**
- * Send the packet on this socket. The packet must satisfy the security
- * policy before it may be sent.
+ * Sends the given {@code packet} on this socket, using the given {@code ttl}. This method is
+ * deprecated because it modifies the TTL socket option for this socket twice on each call.
*
- * @param pack
- * the {@code DatagramPacket} to send
- * @param ttl
- * the TTL setting for this transmission, overriding the socket
- * default
- * @throws IOException
- * if an error occurs while sending data or setting options.
+ * @throws IOException if an error occurs.
* @deprecated use {@link #setTimeToLive}.
*/
@Deprecated
- public void send(DatagramPacket pack, byte ttl) throws IOException {
+ public void send(DatagramPacket packet, byte ttl) throws IOException {
checkClosedAndBind(false);
- InetAddress packAddr = pack.getAddress();
+ InetAddress packAddr = packet.getAddress();
int currTTL = getTimeToLive();
if (packAddr.isMulticastAddress() && (byte) currTTL != ttl) {
try {
setTimeToLive(ttl & 0xff);
- impl.send(pack);
+ impl.send(packet);
} finally {
setTimeToLive(currTTL);
}
} else {
- impl.send(pack);
+ impl.send(packet);
}
}
/**
- * Sets the interface address used by this socket. This allows to send
- * multicast packets on a different interface than the default interface of
- * the local system. This is useful on multihomed machines.
+ * Sets the outgoing network interface used by this socket. The interface used is the first
+ * interface found to have the given {@code address}. To avoid inherent unpredictability,
+ * new code should use {@link #getNetworkInterface} instead.
*
- * @param addr
- * the multicast interface network address to set.
- * @throws SocketException
- * if an error occurs while setting the network interface
- * address option.
+ * @throws SocketException if an error occurs.
*/
- public void setInterface(InetAddress addr) throws SocketException {
+ public void setInterface(InetAddress address) throws SocketException {
checkClosedAndBind(false);
- if (addr == null) {
- throw new NullPointerException();
- }
- if (addr.isAnyLocalAddress()) {
- impl.setOption(SocketOptions.IP_MULTICAST_IF, Inet4Address.ANY);
- } else if (addr instanceof Inet4Address) {
- impl.setOption(SocketOptions.IP_MULTICAST_IF, addr);
- // keep the address used to do the set as we must return the same
- // value and for IPv6 we may not be able to get it back uniquely
- interfaceSet = addr;
+ if (address == null) {
+ throw new NullPointerException("address == null");
}
- /*
- * now we should also make sure this works for IPv6 get the network
- * interface for the address and set the interface using its index
- * however if IPv6 is not enabled then we may get an exception. if IPv6
- * is not enabled
- */
- NetworkInterface theInterface = NetworkInterface.getByInetAddress(addr);
- if ((theInterface != null) && (theInterface.getIndex() != 0)) {
- try {
- impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer
- .valueOf(theInterface.getIndex()));
- } catch (SocketException e) {
- // Ignored
- }
- } else if (addr.isAnyLocalAddress()) {
- try {
- impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer
- .valueOf(0));
- } catch (SocketException e) {
- // Ignored
- }
- } else if (addr instanceof Inet6Address) {
- throw new SocketException("Address not associated with an interface: " + addr);
+ NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
+ if (networkInterface == null) {
+ throw new SocketException("Address not associated with an interface: " + address);
}
+ impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
+ this.setAddress = address;
}
/**
- * Sets the network interface used by this socket. This is useful for
- * multihomed machines.
+ * Sets the outgoing network interface used by this socket to the given
+ * {@code networkInterface}.
*
- * @param netInterface
- * the multicast network interface to set.
- * @throws SocketException
- * if an error occurs while setting the network interface
- * option.
- * @since 1.4
+ * @throws SocketException if an error occurs.
*/
- public void setNetworkInterface(NetworkInterface netInterface) throws SocketException {
+ public void setNetworkInterface(NetworkInterface networkInterface) throws SocketException {
checkClosedAndBind(false);
-
- if (netInterface == null) {
- // throw a socket exception indicating that we do not support this
- throw new SocketException("netInterface == null");
+ if (networkInterface == null) {
+ throw new SocketException("networkInterface == null");
}
- InetAddress firstAddress = netInterface.getFirstAddress();
- if (firstAddress == null) {
- throw new SocketException("No address associated with interface: " + netInterface);
- }
-
- if (netInterface.getIndex() == UNSET_INTERFACE_INDEX) {
- // set the address using IP_MULTICAST_IF to make sure this
- // works for both IPv4 and IPv6
- impl.setOption(SocketOptions.IP_MULTICAST_IF, Inet4Address.ANY);
-
- try {
- // we have the index so now we pass set the interface
- // using IP_MULTICAST_IF2. This is what is used to set
- // the interface on systems which support IPv6
- impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer.valueOf(NO_INTERFACE_INDEX));
- } catch (SocketException e) {
- // for now just do this, -- could be narrowed?
- }
- }
-
- /*
- * Now try to set using IPv4 way. However, if interface passed in has no
- * IP addresses associated with it then we cannot do it. first we have
- * to make sure there is an IPv4 address that we can use to call set
- * interface otherwise we will not set it
- */
- Enumeration<InetAddress> theAddresses = netInterface.getInetAddresses();
- boolean found = false;
- firstAddress = null;
- while ((theAddresses.hasMoreElements()) && (found != true)) {
- InetAddress theAddress = theAddresses.nextElement();
- if (theAddress instanceof Inet4Address) {
- firstAddress = theAddress;
- found = true;
- }
- }
- if (netInterface.getIndex() == NO_INTERFACE_INDEX) {
- // the system does not support IPv6 and does not provide
- // indexes for the network interfaces. Just pass in the
- // first address for the network interface
- if (firstAddress != null) {
- impl.setOption(SocketOptions.IP_MULTICAST_IF, firstAddress);
- } else {
- /*
- * we should never get here as there should not be any network
- * interfaces which have no IPv4 address and which does not have
- * the network interface index not set correctly
- */
- throw new SocketException("No address associated with interface: " + netInterface);
- }
- } else {
- // set the address using IP_MULTICAST_IF to make sure this
- // works for both IPv4 and IPv6
- if (firstAddress != null) {
- impl.setOption(SocketOptions.IP_MULTICAST_IF, firstAddress);
- }
-
- try {
- // we have the index so now we pass set the interface
- // using IP_MULTICAST_IF2. This is what is used to set
- // the interface on systems which support IPv6
- impl.setOption(SocketOptions.IP_MULTICAST_IF2, Integer
- .valueOf(netInterface.getIndex()));
- } catch (SocketException e) {
- // for now just do this -- could be narrowed?
- }
- }
-
- interfaceSet = null;
+ impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
+ this.setAddress = null;
}
/**
* Sets the time-to-live (TTL) for multicast packets sent on this socket.
* Valid TTL values are between 0 and 255 inclusive.
*
- * @throws IOException
- * if an error occurs while setting the TTL option value.
+ * @throws IOException if an error occurs.
*/
public void setTimeToLive(int ttl) throws IOException {
checkClosedAndBind(false);
@@ -467,10 +313,8 @@
* Sets the time-to-live (TTL) for multicast packets sent on this socket.
* Valid TTL values are between 0 and 255 inclusive.
*
- * @throws IOException
- * if an error occurs while setting the TTL option value.
+ * @throws IOException if an error occurs.
* @deprecated Replaced by {@link #setTimeToLive}
- * @see #setTimeToLive(int)
*/
@Deprecated
public void setTTL(byte ttl) throws IOException {
@@ -480,8 +324,7 @@
@Override
synchronized void createSocket(int aPort, InetAddress addr) throws SocketException {
- impl = factory != null ? factory.createDatagramSocketImpl()
- : new PlainDatagramSocketImpl();
+ impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl();
impl.create();
try {
impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.TRUE);
@@ -494,46 +337,23 @@
}
/**
- * Constructs a {@code MulticastSocket} bound to the host/port specified by
- * the {@code SocketAddress}, or an unbound {@code DatagramSocket} if the
- * {@code SocketAddress} is {@code null}.
+ * Returns true if multicast loopback is <i>disabled</i>.
+ * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
+ * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
*
- * @param localAddr
- * the local machine address and port to bind to.
- * @throws IllegalArgumentException
- * if the {@code SocketAddress} is not supported.
- * @throws IOException
- * if an error occurs creating or binding the socket.
- * @since 1.4
- */
- public MulticastSocket(SocketAddress localAddr) throws IOException {
- super(localAddr);
- setReuseAddress(true);
- }
-
- /**
- * Gets the state of the {@code SocketOptions.IP_MULTICAST_LOOP}.
- *
- * @return {@code true} if the IP multicast loop is enabled, {@code false}
- * otherwise.
- * @throws SocketException
- * if the socket is closed or the option is invalid.
- * @since 1.4
+ * @throws SocketException if an error occurs.
*/
public boolean getLoopbackMode() throws SocketException {
checkClosedAndBind(false);
- return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP))
- .booleanValue();
+ return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP)).booleanValue();
}
/**
- * Sets the {@link SocketOptions#IP_MULTICAST_LOOP}.
+ * Disables multicast loopback if {@code disable == true}.
+ * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
+ * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
*
- * @param disable
- * true to <i>disable</i> loopback
- * @throws SocketException
- * if the socket is closed or the option is invalid.
- * @since 1.4
+ * @throws SocketException if an error occurs.
*/
public void setLoopbackMode(boolean disable) throws SocketException {
checkClosedAndBind(false);
diff --git a/luni/src/main/java/java/net/NetPermission.java b/luni/src/main/java/java/net/NetPermission.java
index e12a3bf..d9f57fe 100644
--- a/luni/src/main/java/java/net/NetPermission.java
+++ b/luni/src/main/java/java/net/NetPermission.java
@@ -18,21 +18,7 @@
package java.net;
/**
- * This class represents permissions to configure the access to network
- * resources.
- * <p>
- * There are three valid target names:
- * <dl>
- * <dt>setDefaultAuthenticator</dt>
- * <dd>Allows the default authenticator to be set.</dd>
- * <dt>requestPasswordAuthentication</dt>
- * <dd>Allows the default authenticator to be retrieved.</dd>
- * <dt>specifyStreamHandler</dt>
- * <dd>Allows a stream (protocol) handler to be set when constructing an URL
- * object</dd>
- * </dl>
- *
- * @see java.security.BasicPermission
+ * Legacy security code; this class exists for compatibility only.
*/
public final class NetPermission extends java.security.BasicPermission {
diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java
index 6ef248f..9aa20a0 100644
--- a/luni/src/main/java/java/net/NetworkInterface.java
+++ b/luni/src/main/java/java/net/NetworkInterface.java
@@ -17,14 +17,22 @@
package java.net;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
-import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import libcore.util.EmptyArray;
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import static libcore.io.OsConstants.*;
/**
* This class is used to represent a network interface of the local device. An
@@ -33,159 +41,184 @@
* system or to identify the local interface of a joined multicast group.
*/
public final class NetworkInterface extends Object {
-
- private static final int CHECK_CONNECT_NO_PORT = -1;
-
private final String name;
- private final String displayName;
- private final List<InterfaceAddress> interfaceAddresses = new LinkedList<InterfaceAddress>();
-
private final int interfaceIndex;
- private final List<InetAddress> addresses = new LinkedList<InetAddress>();
+ private final List<InterfaceAddress> interfaceAddresses;
+ private final List<InetAddress> addresses;
private final List<NetworkInterface> children = new LinkedList<NetworkInterface>();
private NetworkInterface parent = null;
- private static NetworkInterface[] getNetworkInterfacesImpl() throws SocketException {
- Map<String, NetworkInterface> networkInterfaces = new LinkedHashMap<String, NetworkInterface>();
- for (InterfaceAddress ia : getAllInterfaceAddressesImpl()) {
- if (ia != null) { // The array may contain harmless null elements.
- String name = ia.name;
- NetworkInterface ni = networkInterfaces.get(name);
- if (ni == null) {
- ni = new NetworkInterface(name, name, new InetAddress[] { ia.address }, ia.index);
- ni.interfaceAddresses.add(ia);
- networkInterfaces.put(name, ni);
- } else {
- ni.addresses.add(ia.address);
- ni.interfaceAddresses.add(ia);
- }
- }
- }
- return networkInterfaces.values().toArray(new NetworkInterface[networkInterfaces.size()]);
- }
- private static native InterfaceAddress[] getAllInterfaceAddressesImpl() throws SocketException;
-
- /**
- * This constructor is used by the native method in order to construct the
- * NetworkInterface objects in the array that it returns.
- *
- * @param name
- * internal name associated with the interface.
- * @param displayName
- * a user interpretable name for the interface.
- * @param addresses
- * the Internet addresses associated with the interface.
- * @param interfaceIndex
- * an index for the interface. Only set for platforms that
- * support IPv6.
- */
- NetworkInterface(String name, String displayName, InetAddress[] addresses,
- int interfaceIndex) {
+ private NetworkInterface(String name, int interfaceIndex,
+ List<InetAddress> addresses, List<InterfaceAddress> interfaceAddresses) {
this.name = name;
- this.displayName = displayName;
this.interfaceIndex = interfaceIndex;
- if (addresses != null) {
- for (InetAddress address : addresses) {
- this.addresses.add(address);
- }
- }
+ this.addresses = addresses;
+ this.interfaceAddresses = interfaceAddresses;
+ }
+
+ static NetworkInterface forUnboundMulticastSocket() {
+ // This is what the RI returns for a MulticastSocket that hasn't been constrained
+ // to a specific interface.
+ return new NetworkInterface(null, -1,
+ Arrays.asList(Inet6Address.ANY), Collections.<InterfaceAddress>emptyList());
}
/**
- * Returns the index for the network interface. Unless the system supports
- * IPv6 this will be 0.
+ * Returns the index for the network interface, or -1 if unknown.
*
- * @return the index
+ * @hide 1.7
*/
- int getIndex() {
+ public int getIndex() {
return interfaceIndex;
}
/**
- * Returns the first address for the network interface. This is used in the
- * natives when we need one of the addresses for the interface and any one
- * will do
- *
- * @return the first address if one exists, otherwise null.
- */
- InetAddress getFirstAddress() {
- if (addresses.size() >= 1) {
- return addresses.get(0);
- }
- return null;
- }
-
- /**
- * Gets the name associated with this network interface.
- *
- * @return the name of this {@code NetworkInterface} instance.
+ * Returns the name of this network interface (such as "eth0" or "lo").
*/
public String getName() {
return name;
}
/**
- * Gets a list of addresses bound to this network interface.
- *
- * @return the address list of the represented network interface.
+ * Returns an enumeration of the addresses bound to this network interface.
*/
public Enumeration<InetAddress> getInetAddresses() {
return Collections.enumeration(addresses);
}
/**
- * Gets the human-readable name associated with this network interface.
- *
- * @return the display name of this network interface or the name if the
- * display name is not available.
+ * Returns a human-readable name for this network interface. On Android, this is the same
+ * string as returned by {@link #getName}.
*/
public String getDisplayName() {
- /*
- * we should return the display name unless it is blank in this case
- * return the name so that something is displayed.
- */
- return displayName.isEmpty() ? name : displayName;
+ return name;
}
/**
- * Gets the specific network interface according to a given name.
+ * Returns the {@code NetworkInterface} corresponding to the named network interface, or null
+ * if no interface has this name.
*
- * @param interfaceName
- * the name to identify the searched network interface.
- * @return the network interface with the specified name if one exists or
- * {@code null} otherwise.
- * @throws SocketException
- * if an error occurs while getting the network interface
- * information.
- * @throws NullPointerException
- * if the given interface's name is {@code null}.
+ * @throws SocketException if an error occurs.
+ * @throws NullPointerException if {@code interfaceName == null}.
*/
public static NetworkInterface getByName(String interfaceName) throws SocketException {
if (interfaceName == null) {
- throw new NullPointerException();
+ throw new NullPointerException("interfaceName == null");
}
- for (NetworkInterface networkInterface : getNetworkInterfacesList()) {
- if (networkInterface.name.equals(interfaceName)) {
- return networkInterface;
+ if (!isValidInterfaceName(interfaceName)) {
+ return null;
+ }
+
+ int interfaceIndex = readIntFile("/sys/class/net/" + interfaceName + "/ifindex");
+ List<InetAddress> addresses = new ArrayList<InetAddress>();
+ List<InterfaceAddress> interfaceAddresses = new ArrayList<InterfaceAddress>();
+ collectIpv6Addresses(interfaceName, interfaceIndex, addresses, interfaceAddresses);
+ collectIpv4Address(interfaceName, addresses, interfaceAddresses);
+
+ return new NetworkInterface(interfaceName, interfaceIndex, addresses, interfaceAddresses);
+ }
+
+ private static void collectIpv6Addresses(String interfaceName, int interfaceIndex,
+ List<InetAddress> addresses, List<InterfaceAddress> interfaceAddresses) throws SocketException {
+ // Format of /proc/net/if_inet6 (all numeric fields are implicit hex).
+ // 1. IPv6 address
+ // 2. interface index
+ // 3. prefix length
+ // 4. scope
+ // 5. flags
+ // 6. interface name
+ // "00000000000000000000000000000001 01 80 10 80 lo"
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new FileReader("/proc/net/if_inet6"));
+ String suffix = " " + interfaceName;
+ String line;
+ while ((line = in.readLine()) != null) {
+ if (!line.endsWith(suffix)) {
+ continue;
+ }
+ byte[] addressBytes = new byte[16];
+ for (int i = 0; i < addressBytes.length; ++i) {
+ addressBytes[i] = (byte) Integer.parseInt(line.substring(2*i, 2*i + 2), 16);
+ }
+ short prefixLength = Short.parseShort(line.substring(36, 38), 16);
+ Inet6Address inet6Address = new Inet6Address(addressBytes, null, interfaceIndex);
+
+ addresses.add(inet6Address);
+ interfaceAddresses.add(new InterfaceAddress(inet6Address, prefixLength));
+ }
+ } catch (Exception ex) {
+ throw rethrowAsSocketException(ex);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private static void collectIpv4Address(String interfaceName, List<InetAddress> addresses,
+ List<InterfaceAddress> interfaceAddresses) throws SocketException {
+ FileDescriptor fd = null;
+ try {
+ fd = Libcore.os.socket(AF_INET, SOCK_DGRAM, 0);
+ InetAddress address = Libcore.os.ioctlInetAddress(fd, SIOCGIFADDR, interfaceName);
+ InetAddress broadcast = Libcore.os.ioctlInetAddress(fd, SIOCGIFBRDADDR, interfaceName);
+ InetAddress netmask = Libcore.os.ioctlInetAddress(fd, SIOCGIFNETMASK, interfaceName);
+ if (broadcast.equals(Inet4Address.ANY)) {
+ broadcast = null;
+ }
+
+ addresses.add(address);
+ interfaceAddresses.add(new InterfaceAddress((Inet4Address) address,
+ (Inet4Address) broadcast, (Inet4Address) netmask));
+ } catch (ErrnoException errnoException) {
+ if (errnoException.errno != EADDRNOTAVAIL) {
+ // EADDRNOTAVAIL just means no IPv4 address for this interface.
+ // Anything else is a real error.
+ throw rethrowAsSocketException(errnoException);
+ }
+ } catch (Exception ex) {
+ throw rethrowAsSocketException(ex);
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ }
+
+ private static boolean isValidInterfaceName(String interfaceName) {
+ // Don't just stat because a crafty user might have / or .. in the supposed interface name.
+ for (String validName : new File("/sys/class/net").list()) {
+ if (interfaceName.equals(validName)) {
+ return true;
}
}
- return null;
+ return false;
+ }
+
+ private static int readIntFile(String path) throws SocketException {
+ try {
+ String s = IoUtils.readFileAsString(path).trim();
+ if (s.startsWith("0x")) {
+ return Integer.parseInt(s.substring(2), 16);
+ } else {
+ return Integer.parseInt(s);
+ }
+ } catch (Exception ex) {
+ throw rethrowAsSocketException(ex);
+ }
+ }
+
+ private static SocketException rethrowAsSocketException(Exception ex) throws SocketException {
+ SocketException result = new SocketException();
+ result.initCause(ex);
+ throw result;
}
/**
- * Gets the specific network interface according to the given address.
+ * Returns the {@code NetworkInterface} corresponding to the given address, or null if no
+ * interface has this address.
*
- * @param address
- * the address to identify the searched network interface.
- * @return the network interface with the specified address if one exists or
- * {@code null} otherwise.
- * @throws SocketException
- * if an error occurs while getting the network interface
- * information.
- * @throws NullPointerException
- * if the given interface address is invalid.
+ * @throws SocketException if an error occurs.
+ * @throws NullPointerException if {@code address == null}.
*/
public static NetworkInterface getByInetAddress(InetAddress address) throws SocketException {
if (address == null) {
@@ -200,6 +233,21 @@
}
/**
+ * Returns the NetworkInterface corresponding to the given interface index, or null if no
+ * interface has this index.
+ *
+ * @throws SocketException if an error occurs.
+ * @hide 1.7
+ */
+ public static NetworkInterface getByIndex(int index) throws SocketException {
+ String name = Libcore.os.if_indextoname(index);
+ if (name == null) {
+ return null;
+ }
+ return NetworkInterface.getByName(name);
+ }
+
+ /**
* Gets a list of all network interfaces available on the local system or
* {@code null} if no interface is available.
*
@@ -214,20 +262,10 @@
}
private static List<NetworkInterface> getNetworkInterfacesList() throws SocketException {
- NetworkInterface[] interfaces = getNetworkInterfacesImpl();
-
- for (NetworkInterface netif : interfaces) {
- // Ensure that current NetworkInterface is bound to at least
- // one InetAddress before processing
- for (InetAddress addr : netif.addresses) {
- if (addr.ipaddress.length == 16) {
- if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress()) {
- ((Inet6Address) addr).scopedIf = netif;
- ((Inet6Address) addr).ifname = netif.name;
- ((Inet6Address) addr).scope_ifname_set = true;
- }
- }
- }
+ String[] interfaceNames = new File("/sys/class/net").list();
+ NetworkInterface[] interfaces = new NetworkInterface[interfaceNames.length];
+ for (int i = 0; i < interfaceNames.length; ++i) {
+ interfaces[i] = NetworkInterface.getByName(interfaceNames[i]);
}
List<NetworkInterface> result = new ArrayList<NetworkInterface>();
@@ -261,8 +299,8 @@
/**
* Compares the specified object to this {@code NetworkInterface} and
* returns whether they are equal or not. The object must be an instance of
- * {@code NetworkInterface} with the same name, {@code displayName} and list
- * of network interfaces to be equal.
+ * {@code NetworkInterface} with the same name, display name, and list
+ * of interface addresses.
*
* @param obj
* the object to compare with this instance.
@@ -281,51 +319,32 @@
NetworkInterface rhs = (NetworkInterface) obj;
// TODO: should the order of the addresses matter (we use List.equals)?
return interfaceIndex == rhs.interfaceIndex &&
- name.equals(rhs.name) && displayName.equals(rhs.displayName) &&
+ name.equals(rhs.name) &&
addresses.equals(rhs.addresses);
}
/**
* Returns the hash code for this {@code NetworkInterface}. Since the
* name should be unique for each network interface the hash code is
- * generated using this name.
+ * generated using the name.
*/
- @Override
- public int hashCode() {
+ @Override public int hashCode() {
return name.hashCode();
}
- /**
- * Gets a string containing a concise, human-readable description of this
- * network interface.
- *
- * @return the textual representation for this network interface.
- */
- @Override
- public String toString() {
- StringBuilder string = new StringBuilder(25);
- string.append("[");
- string.append(name);
- string.append("][");
- string.append(displayName);
- string.append("][");
- string.append(interfaceIndex);
- string.append("]");
-
- /*
- * get the addresses through this call to make sure we only reveal those
- * that we should
- */
- Enumeration<InetAddress> theAddresses = getInetAddresses();
- if (theAddresses != null) {
- while (theAddresses.hasMoreElements()) {
- InetAddress nextAddress = theAddresses.nextElement();
- string.append("[");
- string.append(nextAddress.toString());
- string.append("]");
- }
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder(25);
+ sb.append("[");
+ sb.append(name);
+ sb.append("][");
+ sb.append(interfaceIndex);
+ sb.append("]");
+ for (InetAddress address : addresses) {
+ sb.append("[");
+ sb.append(address.toString());
+ sb.append("]");
}
- return string.toString();
+ return sb.toString();
}
/**
@@ -337,10 +356,10 @@
}
/**
- * Returns an {@code Enumeration} of all the sub-interfaces of this network interface.
+ * Returns an enumeration of all the sub-interfaces of this network interface.
* Sub-interfaces are also known as virtual interfaces.
- * <p>
- * For example, {@code eth0:1} would be a sub-interface of {@code eth0}.
+ *
+ * <p>For example, {@code eth0:1} would be a sub-interface of {@code eth0}.
*
* @return an Enumeration of all the sub-interfaces of this network interface
* @since 1.6
@@ -368,12 +387,8 @@
* @since 1.6
*/
public boolean isUp() throws SocketException {
- if (addresses.isEmpty()) {
- return false;
- }
- return isUpImpl(name);
+ return hasFlag(IFF_UP);
}
- private static native boolean isUpImpl(String n) throws SocketException;
/**
* Returns true if this network interface is a loopback interface.
@@ -383,12 +398,8 @@
* @since 1.6
*/
public boolean isLoopback() throws SocketException {
- if (addresses.isEmpty()) {
- return false;
- }
- return isLoopbackImpl(name);
+ return hasFlag(IFF_LOOPBACK);
}
- private static native boolean isLoopbackImpl(String n) throws SocketException;
/**
* Returns true if this network interface is a point-to-point interface.
@@ -399,12 +410,8 @@
* @since 1.6
*/
public boolean isPointToPoint() throws SocketException {
- if (addresses.isEmpty()) {
- return false;
- }
- return isPointToPointImpl(name);
+ return hasFlag(IFF_POINTOPOINT);
}
- private static native boolean isPointToPointImpl(String n) throws SocketException;
/**
* Returns true if this network interface supports multicast.
@@ -413,29 +420,39 @@
* @since 1.6
*/
public boolean supportsMulticast() throws SocketException {
- if (addresses.isEmpty()) {
- return false;
- }
- return supportsMulticastImpl(name);
+ return hasFlag(IFF_MULTICAST);
}
- private static native boolean supportsMulticastImpl(String n) throws SocketException;
+
+ private boolean hasFlag(int mask) throws SocketException {
+ int flags = readIntFile("/sys/class/net/" + name + "/flags");
+ return (flags & mask) != 0;
+ }
/**
- * Returns the hardware address of the interface, if it has one, and the
- * user has the necessary privileges to access the address.
+ * Returns the hardware address of the interface, if it has one, or null otherwise.
*
- * @return a byte array containing the address or null if the address
- * doesn't exist or is not accessible.
* @throws SocketException if an I/O error occurs.
* @since 1.6
*/
public byte[] getHardwareAddress() throws SocketException {
- if (addresses.isEmpty()) {
- return EmptyArray.BYTE;
+ try {
+ // Parse colon-separated bytes with a trailing newline: "aa:bb:cc:dd:ee:ff\n".
+ String s = IoUtils.readFileAsString("/sys/class/net/" + name + "/address");
+ byte[] result = new byte[s.length()/3];
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = (byte) Integer.parseInt(s.substring(3*i, 3*i + 2), 16);
+ }
+ // We only want to return non-zero hardware addresses.
+ for (int i = 0; i < result.length; ++i) {
+ if (result[i] != 0) {
+ return result;
+ }
+ }
+ return null;
+ } catch (Exception ex) {
+ throw rethrowAsSocketException(ex);
}
- return getHardwareAddressImpl(name);
}
- private static native byte[] getHardwareAddressImpl(String n) throws SocketException;
/**
* Returns the Maximum Transmission Unit (MTU) of this interface.
@@ -445,12 +462,8 @@
* @since 1.6
*/
public int getMTU() throws SocketException {
- if (addresses.isEmpty()) {
- return 0;
- }
- return getMTUImpl(name);
+ return readIntFile("/sys/class/net/" + name + "/mtu");
}
- private static native int getMTUImpl(String n) throws SocketException;
/**
* Returns true if this interface is a virtual interface (also called
diff --git a/luni/src/main/java/java/net/NoRouteToHostException.java b/luni/src/main/java/java/net/NoRouteToHostException.java
index 64b20b1..3d46821 100644
--- a/luni/src/main/java/java/net/NoRouteToHostException.java
+++ b/luni/src/main/java/java/net/NoRouteToHostException.java
@@ -27,17 +27,13 @@
private static final long serialVersionUID = -1897550894873493790L;
/**
- * Constructs a new instance of this exception with its walkback filled in.
+ * Constructs a new instance with the current stack trace.
*/
public NoRouteToHostException() {
}
/**
- * Constructs a new instance of this exception with its walkback and message
- * filled in.
- *
- * @param detailMessage
- * the detail message for this exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public NoRouteToHostException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
index e3ef565..0eb413f 100644
--- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -24,12 +24,12 @@
import java.net.DatagramSocketImpl;
import java.net.InetAddress;
import java.net.InetSocketAddress;
-import java.net.MulticastGroupRequest;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import libcore.io.IoUtils;
+import libcore.io.StructGroupReq;
import libcore.util.EmptyArray;
import org.apache.harmony.luni.platform.Platform;
@@ -108,7 +108,7 @@
@Override
public int getTimeToLive() throws IOException {
- return (Integer) getOption(IoUtils.IP_MULTICAST_TTL);
+ return (Integer) getOption(IoUtils.JAVA_IP_MULTICAST_TTL);
}
@Override
@@ -116,29 +116,34 @@
return (byte) getTimeToLive();
}
+ private static StructGroupReq makeGroupReq(InetAddress gr_group, NetworkInterface networkInterface) {
+ int gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
+ return new StructGroupReq(gr_interface, gr_group);
+ }
+
@Override
public void join(InetAddress addr) throws IOException {
- setOption(IoUtils.MCAST_JOIN_GROUP, new MulticastGroupRequest(addr, null));
+ setOption(IoUtils.JAVA_MCAST_JOIN_GROUP, makeGroupReq(addr, null));
}
@Override
public void joinGroup(SocketAddress addr, NetworkInterface netInterface) throws IOException {
if (addr instanceof InetSocketAddress) {
InetAddress groupAddr = ((InetSocketAddress) addr).getAddress();
- setOption(IoUtils.MCAST_JOIN_GROUP, new MulticastGroupRequest(groupAddr, netInterface));
+ setOption(IoUtils.JAVA_MCAST_JOIN_GROUP, makeGroupReq(groupAddr, netInterface));
}
}
@Override
public void leave(InetAddress addr) throws IOException {
- setOption(IoUtils.MCAST_LEAVE_GROUP, new MulticastGroupRequest(addr, null));
+ setOption(IoUtils.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(addr, null));
}
@Override
public void leaveGroup(SocketAddress addr, NetworkInterface netInterface) throws IOException {
if (addr instanceof InetSocketAddress) {
InetAddress groupAddr = ((InetSocketAddress) addr).getAddress();
- setOption(IoUtils.MCAST_LEAVE_GROUP, new MulticastGroupRequest(groupAddr, netInterface));
+ setOption(IoUtils.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(groupAddr, netInterface));
}
}
@@ -179,13 +184,13 @@
port, address);
}
- public void setOption(int optID, Object val) throws SocketException {
- Platform.NETWORK.setSocketOption(fd, optID, val);
+ public void setOption(int option, Object value) throws SocketException {
+ IoUtils.setSocketOption(fd, option, value);
}
@Override
public void setTimeToLive(int ttl) throws IOException {
- setOption(IoUtils.IP_MULTICAST_TTL, Integer.valueOf(ttl));
+ setOption(IoUtils.JAVA_IP_MULTICAST_TTL, Integer.valueOf(ttl));
}
@Override
@@ -195,9 +200,7 @@
@Override
public void connect(InetAddress inetAddr, int port) throws SocketException {
- Platform.NETWORK.connect(fd, inetAddr, port, 0);
-
- // if we get here then we are connected at the native level
+ IoUtils.connect(fd, inetAddr, port); // Throws on failure.
try {
connectedAddress = InetAddress.getByAddress(inetAddr.getAddress());
} catch (UnknownHostException e) {
diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java
index 937193e..ccdbb5b 100644
--- a/luni/src/main/java/java/net/PlainSocketImpl.java
+++ b/luni/src/main/java/java/net/PlainSocketImpl.java
@@ -171,10 +171,10 @@
if (streaming && usingSocks()) {
socksConnect(anAddr, aPort, 0);
} else {
- Platform.NETWORK.connect(fd, normalAddr, aPort, timeout);
+ IoUtils.connect(fd, normalAddr, aPort, timeout);
}
} catch (ConnectException e) {
- throw new ConnectException(anAddr + ":" + aPort + " - " + e.getMessage());
+ throw new ConnectException(anAddr + " (port " + aPort + "): " + e.getMessage());
}
super.address = normalAddr;
super.port = aPort;
@@ -270,8 +270,8 @@
}
@Override
- public void setOption(int optID, Object val) throws SocketException {
- Platform.NETWORK.setSocketOption(fd, optID, val);
+ public void setOption(int option, Object value) throws SocketException {
+ IoUtils.setSocketOption(fd, option, value);
}
/**
@@ -304,12 +304,11 @@
/**
* Connect using a SOCKS server.
*/
- private void socksConnect(InetAddress applicationServerAddress,
- int applicationServerPort, int timeout) throws IOException {
+ private void socksConnect(InetAddress applicationServerAddress, int applicationServerPort, int timeout) throws IOException {
try {
- Platform.NETWORK.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
+ IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
} catch (Exception e) {
- throw new SocketException("SOCKS connection failed: " + e);
+ throw new SocketException("SOCKS connection failed", e);
}
socksRequestConnection(applicationServerAddress, applicationServerPort);
@@ -372,13 +371,12 @@
*/
private void socksBind() throws IOException {
try {
- Platform.NETWORK.connect(fd, socksGetServerAddress(), socksGetServerPort(), 0);
+ IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort());
} catch (Exception e) {
- throw new IOException("Unable to connect to SOCKS server: " + e);
+ throw new IOException("Unable to connect to SOCKS server", e);
}
- // There must be a connection to an application host for the bind to
- // work.
+ // There must be a connection to an application host for the bind to work.
if (lastConnectedAddress == null) {
throw new SocketException("Invalid SOCKS client");
}
diff --git a/luni/src/main/java/java/net/PortUnreachableException.java b/luni/src/main/java/java/net/PortUnreachableException.java
index c59706e..b8e8fa9 100644
--- a/luni/src/main/java/java/net/PortUnreachableException.java
+++ b/luni/src/main/java/java/net/PortUnreachableException.java
@@ -26,17 +26,13 @@
private static final long serialVersionUID = 8462541992376507323L;
/**
- * Constructs a new instance of this class with its walkback filled in.
+ * Constructs a new instance with the current stack trace.
*/
public PortUnreachableException() {
}
/**
- * Constructs a new instance of this class with its walkback and message
- * filled in.
- *
- * @param detailMessage
- * the detail message for this exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public PortUnreachableException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/net/ProtocolException.java b/luni/src/main/java/java/net/ProtocolException.java
index 4d7dd35..979f7d9 100644
--- a/luni/src/main/java/java/net/ProtocolException.java
+++ b/luni/src/main/java/java/net/ProtocolException.java
@@ -27,17 +27,13 @@
private static final long serialVersionUID = -6098449442062388080L;
/**
- * Constructs a new instance of this class with its walkback filled in.
+ * Constructs a new instance with the current stack trace.
*/
public ProtocolException() {
}
/**
- * Constructs a new instance of this class with its walkback and message
- * filled in.
- *
- * @param detailMessage
- * the detail message for this exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public ProtocolException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java
index e3733b2..06e1ab3 100644
--- a/luni/src/main/java/java/net/Socket.java
+++ b/luni/src/main/java/java/net/Socket.java
@@ -131,10 +131,10 @@
* Creates a new streaming socket connected to the target host specified by
* the parameters {@code dstName} and {@code dstPort}. The socket is bound
* to any available port on the local host.
- * <p><strong>Implementation note:</strong> this implementation tries each
- * IP address for the given hostname until it either connects successfully
- * or it exhausts the set. It will try both IPv4 and IPv6 addresses in the
- * order specified by the system property {@code "java.net.preferIPv6Addresses"}.
+ *
+ * <p>This implementation tries each IP address for the given hostname (in
+ * <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order)
+ * until it either connects successfully or it exhausts the set.
*
* @param dstName
* the target host name or IP address to connect to.
@@ -153,13 +153,11 @@
* Creates a new streaming socket connected to the target host specified by
* the parameters {@code dstName} and {@code dstPort}. On the local endpoint
* the socket is bound to the given address {@code localAddress} on port
- * {@code localPort}.
+ * {@code localPort}. If {@code host} is {@code null} a loopback address is used to connect to.
*
- * If {@code host} is {@code null} a loopback address is used to connect to.
- * <p><strong>Implementation note:</strong> this implementation tries each
- * IP address for the given hostname until it either connects successfully
- * or it exhausts the set. It will try both IPv4 and IPv6 addresses in the
- * order specified by the system property {@code "java.net.preferIPv6Addresses"}.
+ * <p>This implementation tries each IP address for the given hostname (in
+ * <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order)
+ * until it either connects successfully or it exhausts the set.
*
* @param dstName
* the target host name or IP address to connect to.
@@ -183,10 +181,10 @@
* Creates a new streaming or datagram socket connected to the target host
* specified by the parameters {@code hostName} and {@code port}. The socket
* is bound to any available port on the local host.
- * <p><strong>Implementation note:</strong> this implementation tries each
- * IP address for the given hostname until it either connects successfully
- * or it exhausts the set. It will try both IPv4 and IPv6 addresses in the
- * order specified by the system property {@code "java.net.preferIPv6Addresses"}.
+ *
+ * <p>This implementation tries each IP address for the given hostname (in
+ * <a href="http://www.ietf.org/rfc/rfc3484.txt">RFC 3484</a> order)
+ * until it either connects successfully or it exhausts the set.
*
* @param hostName
* the target host name or IP address to connect to.
diff --git a/luni/src/main/java/java/net/SocketException.java b/luni/src/main/java/java/net/SocketException.java
index 0f930e0..b7366b3 100644
--- a/luni/src/main/java/java/net/SocketException.java
+++ b/luni/src/main/java/java/net/SocketException.java
@@ -28,20 +28,30 @@
private static final long serialVersionUID = -5935874303556886934L;
/**
- * Constructs a new {@code SocketException} instance with its walkback
- * filled in.
+ * Constructs a new instance with the current stack trace.
*/
public SocketException() {
}
/**
- * Constructs a new {@code SocketException} instance with its walkback and
- * message filled in.
- *
- * @param detailMessage
- * the detail message of this exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public SocketException(String detailMessage) {
super(detailMessage);
}
+ /**
+ * Constructs a new instance with the current stack trace and given cause.
+ * @hide internal use only
+ */
+ public SocketException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructs a new instance with the current stack trace, given detail message and given cause.
+ * @hide internal use only
+ */
+ public SocketException(String detailMessage, Throwable cause) {
+ super(detailMessage, cause);
+ }
}
diff --git a/luni/src/main/java/java/net/SocketOptions.java b/luni/src/main/java/java/net/SocketOptions.java
index ea4a2d7..e23fc97 100644
--- a/luni/src/main/java/java/net/SocketOptions.java
+++ b/luni/src/main/java/java/net/SocketOptions.java
@@ -45,7 +45,7 @@
public static final int SO_LINGER = 128;
/**
- * Timeout in milliseconds for blocking accept or read/receive operations (but not
+ * Integer timeout in milliseconds for blocking accept or read/receive operations (but not
* write/send operations). A timeout of 0 means no timeout. Negative
* timeouts are not allowed.
*
@@ -54,39 +54,31 @@
public static final int SO_TIMEOUT = 4102;
/**
- * Whether data is sent immediately on this socket.
+ * This boolean option specifies whether data is sent immediately on this socket.
* As a side-effect this could lead to low packet efficiency. The
* socket implementation uses the Nagle's algorithm to try to reach a higher
* packet efficiency if this option is disabled.
*/
public static final int TCP_NODELAY = 1;
- // For 5 and 6 see MulticastSocket
-
- // For 7 see PlainDatagramSocketImpl
-
/**
- * The interface used to send multicast packets.
- * This option is only available on a {@link MulticastSocket}.
+ * This is an IPv4-only socket option whose functionality is subsumed by
+ * {@link #IP_MULTICAST_IF} and not implemented on Android.
*/
public static final int IP_MULTICAST_IF = 16;
/**
- * This option can be used to set one specific interface on a multihomed
- * host on which incoming connections are accepted. It's only available on
- * server-side sockets.
+ * This option does not correspond to any Unix socket option and is not implemented on Android.
*/
public static final int SO_BINDADDR = 15;
/**
- * This option specifies whether a reuse of a local address is allowed even
- * if an other socket is not yet removed by the operating system. It's only
+ * This boolean option specifies whether a reuse of a local address is allowed even
+ * if another socket is not yet removed by the operating system. It's only
* available on a {@code MulticastSocket}.
*/
public static final int SO_REUSEADDR = 4;
- // 10 not currently used
-
/**
* The size in bytes of a socket's send buffer. This must be an integer greater than 0.
* This is a hint to the kernel; the kernel may use a larger buffer.
@@ -105,27 +97,13 @@
public static final int SO_RCVBUF = 4098;
/**
- * This option can be used to bind a datagram socket to a
- * particular network interface. When this is done, only packets
- * received on the specified interface will be processed by the
- * socket. Packets sent via this socket will be transmitted by
- * the specified interface. The argument to this operation is the
- * network-interface index.
- * @hide
- */
- public static final int SO_BINDTODEVICE = 8192;
-
- // For 13, see DatagramSocket
-
- /**
- * This option specifies whether socket implementations can send keepalive
- * messages if no data has been sent for a longer time.
+ * This boolean option specifies whether the kernel sends keepalive messages.
*/
public static final int SO_KEEPALIVE = 8;
/**
- * This option specifies the value for the type-of-service field of the IPv4 header, or the
- * traffic class field of the IPv6 header. These correspond to the IP_TOS and IPV6_TCLASS
+ * This integer option specifies the value for the type-of-service field of the IPv4 header,
+ * or the traffic class field of the IPv6 header. These correspond to the IP_TOS and IPV6_TCLASS
* socket options. These may be ignored by the underlying OS. Values must be between 0 and 255
* inclusive.
*
@@ -135,14 +113,16 @@
public static final int IP_TOS = 3;
/**
- * This option specifies whether the local loopback of multicast packets is
+ * This boolean option specifies whether the local loopback of multicast packets is
* enabled or disabled. This option is enabled by default on multicast
- * sockets.
+ * sockets. Note that the sense of this option in Java is the
+ * <i>opposite</i> of the underlying Unix {@code IP_MULTICAST_LOOP}.
+ * See {@link MulticastSocket#setLoopbackMode}.
*/
public static final int IP_MULTICAST_LOOP = 18;
/**
- * This option can be used to enable broadcasting on datagram sockets.
+ * This boolean option can be used to enable broadcasting on datagram sockets.
*/
public static final int SO_BROADCAST = 32;
@@ -153,10 +133,8 @@
public static final int SO_OOBINLINE = 4099;
/**
- * This option can be used to set one specific interface on a multihomed
- * host on which incoming connections are accepted. It's only available on
- * server-side sockets. This option supports setting outgoing interfaces
- * with either IPv4 or IPv6 addresses.
+ * This integer option sets the outgoing interface for multicast packets
+ * using an interface index.
*/
public static final int IP_MULTICAST_IF2 = 31;
diff --git a/luni/src/main/java/java/net/SocketPermission.java b/luni/src/main/java/java/net/SocketPermission.java
index c63872e..92168c5 100644
--- a/luni/src/main/java/java/net/SocketPermission.java
+++ b/luni/src/main/java/java/net/SocketPermission.java
@@ -26,39 +26,7 @@
import java.util.Locale;
/**
- * Regulates the access to network operations available through sockets through
- * permissions. A permission consists of a target (a host), and an associated
- * action list. The target should identify the host by either indicating the
- * (possibly wildcarded (eg. {@code .company.com})) DNS style name of the host
- * or its IP address in standard {@code nn.nn.nn.nn} ("dot") notation. The
- * action list can be made up of one or more of the following actions separated
- * by a comma:
- * <dl>
- * <dt>connect</dt>
- * <dd>requests permission to connect to the host</dd>
- * <dt>listen</dt>
- * <dd>requests permission to listen for connections from the host</dd>
- * <dt>accept</dt>
- * <dd>requests permission to accept connections from the host</dd>
- * <dt>resolve</dt>
- * <dd>requests permission to resolve the hostname</dd>
- * </dl>
- * Note that {@code resolve} is implied when any (or none) of the others are
- * present.
- * <p>
- * Access to a particular port can be requested by appending a colon and a
- * single digit to the name (eg. {@code .company.com:7000}). A range of port
- * numbers can also be specified, by appending a pattern of the form
- * <i>LOW-HIGH</i> where <i>LOW</i> and <i>HIGH</i> are valid port numbers. If
- * either <i>LOW</i> or <i>HIGH</i> is omitted it is equivalent to entering the
- * lowest or highest possible value respectively. For example:
- *
- * <pre>
- * {@code SocketPermission("www.company.com:7000-", "connect,accept")}
- * </pre>
- *
- * represents the permission to connect to and accept connections from {@code
- * www.company.com} on ports in the range {@code 7000} to {@code 65535}.
+ * Legacy security code; this class exists for compatibility only.
*/
public final class SocketPermission extends Permission implements Serializable {
@@ -376,13 +344,12 @@
private String getIPString(boolean isCheck) {
if (!resolved) {
try {
- ipString = InetAddress.getHostNameInternal(hostName);
- } catch (UnknownHostException e) {
- // ignore
+ return InetAddress.getAllByName(hostName)[0].getHostAddress();
+ } catch (UnknownHostException ignored) {
}
resolved = true;
}
- return ipString;
+ return null;
}
/**
diff --git a/luni/src/main/java/java/net/SocketPermissionCollection.java b/luni/src/main/java/java/net/SocketPermissionCollection.java
index 545782b..1611308 100644
--- a/luni/src/main/java/java/net/SocketPermissionCollection.java
+++ b/luni/src/main/java/java/net/SocketPermissionCollection.java
@@ -23,9 +23,7 @@
import java.util.Vector;
/**
- * This class represents a list of {@code SocketPermission} objects and provides
- * a method to check whether or not a specific permission is implied by this
- * {@code SocketPermissionCollection}.
+ * Legacy security code; this class exists for compatibility only.
*/
final class SocketPermissionCollection extends PermissionCollection {
diff --git a/luni/src/main/java/java/net/SocketTimeoutException.java b/luni/src/main/java/java/net/SocketTimeoutException.java
index 8ce324a9..62e8731 100644
--- a/luni/src/main/java/java/net/SocketTimeoutException.java
+++ b/luni/src/main/java/java/net/SocketTimeoutException.java
@@ -28,18 +28,13 @@
private static final long serialVersionUID = -8846654841826352300L;
/**
- * Creates a new {@code SocketTimeoutException} instance with its walkback
- * filled in.
+ * Constructs a new instance with the current stack trace.
*/
public SocketTimeoutException() {
}
/**
- * Creates a new {@code SocketTimeoutException} instance with its walkback
- * and message filled in.
- *
- * @param detailMessage
- * the detail message of this exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public SocketTimeoutException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/net/URI.java b/luni/src/main/java/java/net/URI.java
index 0001236..3fcfb21 100644
--- a/luni/src/main/java/java/net/URI.java
+++ b/luni/src/main/java/java/net/URI.java
@@ -25,7 +25,8 @@
import libcore.net.UriCodec;
/**
- * This class represents an instance of a URI as defined by RFC 2396.
+ * This class represents an instance of a URI as defined by
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
*/
public final class URI implements Comparable<URI>, Serializable {
@@ -55,7 +56,7 @@
* Encodes the unescaped characters of {@code s} that are not permitted.
* Permitted characters are:
* <ul>
- * <li>Unreserved characters in RFC 2396.
+ * <li>Unreserved characters in <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
* <li>{@code extraOkayChars},
* <li>non-ASCII, non-control, non-whitespace characters
* </ul>
@@ -99,8 +100,9 @@
* @param uri
* the textual URI representation to be parsed into a URI object.
* @throws URISyntaxException
- * if the given string {@code uri} doesn't fit to the
- * specification RFC2396 or could not be parsed correctly.
+ * if the given {@code uri} isn't an
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+ * or could not be parsed correctly.
*/
public URI(String uri) throws URISyntaxException {
parseURI(uri, false);
@@ -120,11 +122,11 @@
* @param frag
* the fragment part of the URI.
* @throws URISyntaxException
- * if the temporary created string doesn't fit to the
- * specification RFC2396 or could not be parsed correctly.
+ * if the resulting URI isn't an
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+ * or could not be parsed correctly.
*/
- public URI(String scheme, String ssp, String frag)
- throws URISyntaxException {
+ public URI(String scheme, String ssp, String frag) throws URISyntaxException {
StringBuilder uri = new StringBuilder();
if (scheme != null) {
uri.append(scheme);
@@ -165,8 +167,9 @@
* @param fragment
* the fragment part of the URI.
* @throws URISyntaxException
- * if the temporary created string doesn't fit to the
- * specification RFC2396 or could not be parsed correctly.
+ * if the resulting URI isn't an
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+ * or could not be parsed correctly.
*/
public URI(String scheme, String userInfo, String host, int port,
String path, String query, String fragment)
@@ -246,11 +249,11 @@
* @param fragment
* the fragment part of the URI.
* @throws URISyntaxException
- * if the temporary created string doesn't fit to the
- * specification RFC2396 or could not be parsed correctly.
+ * if the resulting URI isn't an
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+ * or could not be parsed correctly.
*/
- public URI(String scheme, String host, String path, String fragment)
- throws URISyntaxException {
+ public URI(String scheme, String host, String path, String fragment) throws URISyntaxException {
this(scheme, null, host, -1, path, null, fragment);
}
@@ -273,8 +276,9 @@
* @param fragment
* the fragment part of the URI.
* @throws URISyntaxException
- * if the temporary created string doesn't fit to the
- * specification RFC2396 or could not be parsed correctly.
+ * if the resulting URI isn't an
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a> URI
+ * or could not be parsed correctly.
*/
public URI(String scheme, String authority, String path, String query,
String fragment) throws URISyntaxException {
@@ -569,13 +573,9 @@
throw new URISyntaxException(host,
"Expected a closing square bracket for IPv6 address", 0);
}
- byte[] bytes = InetAddress.ipStringToByteArray(host);
- /*
- * The native IP parser may return 4 bytes for addresses like
- * "[::FFFF:127.0.0.1]". This is allowed, but we must not accept
- * IPv4-formatted addresses in square braces like "[127.0.0.1]".
- */
- if (bytes != null && (bytes.length == 16 || bytes.length == 4 && host.contains(":"))) {
+ if (InetAddress.isNumeric(host)) {
+ // If it's numeric, the presence of square brackets guarantees
+ // that it's a numeric IPv6 address.
return true;
}
throw new URISyntaxException(host, "Malformed IPv6 address");
@@ -600,10 +600,13 @@
return false;
}
- // IPv4 address
- byte[] bytes = InetAddress.ipStringToByteArray(host);
- if (bytes != null && bytes.length == 4) {
- return true;
+ // IPv4 address?
+ try {
+ InetAddress ia = InetAddress.parseNumericAddress(host);
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ } catch (IllegalArgumentException ex) {
}
if (forceServer) {
diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java
index 8db1bdd..8779cca 100644
--- a/luni/src/main/java/java/net/URL.java
+++ b/luni/src/main/java/java/net/URL.java
@@ -30,7 +30,8 @@
/**
* A URL instance specifies the location of a resource on the internet as
- * specified by RFC 1738. Such a resource can be a simple file or a service
+ * specified by <a href="http://www.ietf.org/rfc/rfc1738.txt">RFC 1738</a>.
+ * Such a resource can be a simple file or a service
* which generates the output dynamically. A URL is divided in its parts
* protocol, host name, port, path, file, user-info, query, reference and
* authority. However, not each of this parts has to be defined.
diff --git a/luni/src/main/java/java/net/URLConnection.java b/luni/src/main/java/java/net/URLConnection.java
index 61253f3..26b805f 100644
--- a/luni/src/main/java/java/net/URLConnection.java
+++ b/luni/src/main/java/java/net/URLConnection.java
@@ -58,8 +58,8 @@
* Resources from the local file system can be loaded using {@code file:}
* URIs. File connections can only be used for input.
* <li><strong>FTP</strong><br>
- * File Transfer Protocol (<a href="http://www.ietf.org/rfc/rfc959.txt">RFC
- * 959</a>) is supported, but with no public subclass. FTP connections can
+ * File Transfer Protocol (<a href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</a>)
+ * is supported, but with no public subclass. FTP connections can
* be used for input or output but not both.
* <p>By default, FTP connections will be made using {@code anonymous} as
* the username and the empty string as the password. Specify alternate
@@ -444,7 +444,7 @@
}
/**
- * Returns an unchangeable map of the response-header fields and values. The
+ * Returns an unmodifiable map of the response-header fields and values. The
* response-header field names are the key values of the map. The map values
* are lists of header field values associated with a particular key name.
*
@@ -460,7 +460,7 @@
}
/**
- * Returns an unchangeable map of general request properties used by this
+ * Returns an unmodifiable map of general request properties used by this
* connection. The request property names are the key values of the map. The
* map values are lists of property values of the corresponding key name.
*
@@ -535,7 +535,7 @@
return defaultValue;
}
try {
- return Date.parse(date);
+ return Date.parse(date); // TODO: use HttpDate.parse()
} catch (Exception e) {
return defaultValue;
}
diff --git a/luni/src/main/java/java/net/UnknownServiceException.java b/luni/src/main/java/java/net/UnknownServiceException.java
index 5daec4b..a8afa41 100644
--- a/luni/src/main/java/java/net/UnknownServiceException.java
+++ b/luni/src/main/java/java/net/UnknownServiceException.java
@@ -30,18 +30,13 @@
private static final long serialVersionUID = -4169033248853639508L;
/**
- * Constructs a new {@code UnknownServiceException} instance with its
- * walkback filled in.
+ * Constructs a new instance with the current stack trace.
*/
public UnknownServiceException() {
}
/**
- * Constructs a new {@code UnknownServiceException} instance with its
- * walkback and message filled in.
- *
- * @param detailMessage
- * the detail message for this exception.
+ * Constructs a new instance with the current stack trace and given detail message.
*/
public UnknownServiceException(String detailMessage) {
super(detailMessage);
diff --git a/luni/src/main/java/java/nio/ByteBuffer.java b/luni/src/main/java/java/nio/ByteBuffer.java
index c539f76..ef725c1 100644
--- a/luni/src/main/java/java/nio/ByteBuffer.java
+++ b/luni/src/main/java/java/nio/ByteBuffer.java
@@ -18,6 +18,7 @@
package java.nio;
import java.util.Arrays;
+import libcore.io.Memory;
/**
* A buffer for bytes.
@@ -761,14 +762,30 @@
*/
public ByteBuffer put(ByteBuffer src) {
if (src == this) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("src == this");
}
- if (src.remaining() > remaining()) {
+ int srcByteCount = src.remaining();
+ if (srcByteCount > remaining()) {
throw new BufferOverflowException();
}
- byte[] contents = new byte[src.remaining()];
- src.get(contents);
- put(contents);
+
+ Object srcObject = src.isDirect() ? src : NioUtils.unsafeArray(src);
+ int srcOffset = src.position();
+ if (!src.isDirect()) {
+ srcOffset += NioUtils.unsafeArrayOffset(src);
+ }
+
+ ByteBuffer dst = this;
+ Object dstObject = dst.isDirect() ? dst : NioUtils.unsafeArray(dst);
+ int dstOffset = dst.position();
+ if (!dst.isDirect()) {
+ dstOffset += NioUtils.unsafeArrayOffset(dst);
+ }
+
+ Memory.memmove(dstObject, dstOffset, srcObject, srcOffset, srcByteCount);
+ src.position(src.limit());
+ dst.position(dst.position() + srcByteCount);
+
return this;
}
diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java
index 9887394..970c116 100644
--- a/luni/src/main/java/java/nio/DatagramChannelImpl.java
+++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java
@@ -124,11 +124,9 @@
// check the address
InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address);
-
try {
begin();
- Platform.NETWORK.connect(fd,
- inetSocketAddress.getAddress(), inetSocketAddress.getPort(), 0);
+ IoUtils.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort());
} catch (ConnectException e) {
// ConnectException means connect fail, not exception
} finally {
diff --git a/luni/src/main/java/java/nio/HeapByteBuffer.java b/luni/src/main/java/java/nio/HeapByteBuffer.java
index 8ba9980..6b65ec1 100644
--- a/luni/src/main/java/java/nio/HeapByteBuffer.java
+++ b/luni/src/main/java/java/nio/HeapByteBuffer.java
@@ -35,7 +35,7 @@
abstract class HeapByteBuffer extends BaseByteBuffer {
/**
- * These fields are non-private for NioUtils.unsafeByteArray.
+ * These fields are non-private for NioUtils.unsafeArray.
*/
final byte[] backingArray;
final int offset;
diff --git a/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java b/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
index a746bd1..5edce30 100644
--- a/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
+++ b/luni/src/main/java/java/nio/ReadWriteDirectByteBuffer.java
@@ -62,8 +62,7 @@
@Override
public ByteBuffer compact() {
- int addr = effectiveDirectAddress;
- Memory.memmove(addr, addr + position, remaining());
+ Memory.memmove(this, 0, this, position, remaining());
position = limit - position;
limit = capacity;
mark = UNSET_MARK;
diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java
index 35f5490..51cda16 100644
--- a/luni/src/main/java/java/nio/SocketChannelImpl.java
+++ b/luni/src/main/java/java/nio/SocketChannelImpl.java
@@ -164,27 +164,17 @@
InetAddress normalAddr = inetSocketAddress.getAddress();
int port = inetSocketAddress.getPort();
- // When connecting, map ANY address to Localhost
+ // When connecting, map ANY address to localhost
if (normalAddr.isAnyLocalAddress()) {
normalAddr = InetAddress.getLocalHost();
}
- // connect result
- int result = EOF;
boolean finished = false;
-
try {
if (isBlocking()) {
begin();
- Platform.NETWORK.connect(fd, normalAddr, port, 0);
- finished = true; // Or we'd have thrown an exception.
- } else {
- finished = Platform.NETWORK.connectNonBlocking(fd, normalAddr, port);
- // set back to nonblocking to work around with a bug in portlib
- if (!isBlocking()) {
- IoUtils.setBlocking(fd, false);
- }
}
+ finished = IoUtils.connect(fd, normalAddr, port);
isBound = finished;
} catch (IOException e) {
if (e instanceof ConnectException && !isBlocking()) {
@@ -231,7 +221,6 @@
@Override
public boolean finishConnect() throws IOException {
- // status check
synchronized (this) {
if (!isOpen()) {
throw new ClosedChannelException();
@@ -247,11 +236,8 @@
boolean finished = false;
try {
begin();
- final int WAIT_FOREVER = -1;
- final int POLL = 0;
- finished = Platform.NETWORK.isConnected(fd, isBlocking() ? WAIT_FOREVER : POLL);
+ finished = Platform.NETWORK.isConnected(fd, 0);
isBound = finished;
- initLocalAddressAndPort();
} catch (ConnectException e) {
if (isOpen()) {
close();
@@ -265,8 +251,6 @@
synchronized (this) {
status = (finished ? SOCKET_STATUS_CONNECTED : status);
isBound = finished;
- // TPE: Workaround for bug that turns socket back to blocking
- if (!isBlocking()) implConfigureBlocking(false);
}
return finished;
}
diff --git a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
index 8104f22..8e98a02 100644
--- a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
+++ b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java
@@ -88,8 +88,7 @@
*/
@Override
synchronized public final SelectionKey keyFor(Selector selector) {
- for (int i = 0; i < keyList.size(); i++) {
- SelectionKey key = keyList.get(i);
+ for (SelectionKey key : keyList) {
if (key != null && key.selector() == selector) {
return key;
}
@@ -173,8 +172,7 @@
@Override
synchronized protected final void implCloseChannel() throws IOException {
implCloseSelectableChannel();
- for (int i = 0; i < keyList.size(); i++) {
- SelectionKey key = keyList.get(i);
+ for (SelectionKey key : keyList) {
if (key != null) {
key.cancel();
}
@@ -235,20 +233,20 @@
*/
@Override
public final SelectableChannel configureBlocking(boolean blockingMode) throws IOException {
- if (isOpen()) {
- synchronized (blockingLock) {
- if (isBlocking == blockingMode) {
- return this;
- }
- if (blockingMode && containsValidKeys()) {
- throw new IllegalBlockingModeException();
- }
- implConfigureBlocking(blockingMode);
- isBlocking = blockingMode;
- }
- return this;
+ if (!isOpen()) {
+ throw new ClosedChannelException();
}
- throw new ClosedChannelException();
+ synchronized (blockingLock) {
+ if (isBlocking == blockingMode) {
+ return this;
+ }
+ if (blockingMode && containsValidKeys()) {
+ throw new IllegalBlockingModeException();
+ }
+ implConfigureBlocking(blockingMode);
+ isBlocking = blockingMode;
+ }
+ return this;
}
/**
@@ -260,8 +258,7 @@
* @throws IOException
* if an I/O error occurs.
*/
- protected abstract void implConfigureBlocking(boolean blockingMode)
- throws IOException;
+ protected abstract void implConfigureBlocking(boolean blockingMode) throws IOException;
/*
* package private for deregister method in AbstractSelector.
@@ -277,8 +274,7 @@
* otherwise.
*/
private synchronized boolean containsValidKeys() {
- for (int i = 0; i < keyList.size(); i++) {
- SelectionKey key = keyList.get(i);
+ for (SelectionKey key : keyList) {
if (key != null && key.isValid()) {
return true;
}
diff --git a/luni/src/main/java/java/security/AllPermission.java b/luni/src/main/java/java/security/AllPermission.java
index 1d2841d..c05a230 100644
--- a/luni/src/main/java/java/security/AllPermission.java
+++ b/luni/src/main/java/java/security/AllPermission.java
@@ -19,9 +19,7 @@
/**
- * {@code AllPermission} represents the permission to perform any operation.
- * Since its {@link #implies(Permission)} method always returns {@code true},
- * granting this permission is equivalent to disabling security.
+ * Legacy security code; this class exists for compatibility only.
*/
public final class AllPermission extends Permission {
diff --git a/luni/src/main/java/java/security/SecurityPermission.java b/luni/src/main/java/java/security/SecurityPermission.java
index 599ec6f..c2dfc56 100644
--- a/luni/src/main/java/java/security/SecurityPermission.java
+++ b/luni/src/main/java/java/security/SecurityPermission.java
@@ -18,8 +18,7 @@
package java.security;
/**
- * {@code SecurityPermission} objects guard access to the mechanisms which
- * implement security. Security permissions have names, but not actions.
+ * Legacy security code; this class exists for compatibility only.
*/
public final class SecurityPermission extends BasicPermission {
diff --git a/luni/src/main/java/java/security/UnresolvedPermission.java b/luni/src/main/java/java/security/UnresolvedPermission.java
index 0865a96..2884421 100644
--- a/luni/src/main/java/java/security/UnresolvedPermission.java
+++ b/luni/src/main/java/java/security/UnresolvedPermission.java
@@ -30,11 +30,7 @@
import org.apache.harmony.security.fortress.PolicyUtils;
/**
- * An {@code UnresolvedPermission} represents a {@code Permission} whose type
- * should be resolved lazy and not during initialization time of the {@code
- * Policy}. {@code UnresolvedPermission}s contain all information to be replaced
- * by a concrete typed {@code Permission} right before the access checks are
- * performed.
+ * Legacy security code; this class exists for compatibility only.
*/
public final class UnresolvedPermission extends Permission
implements Serializable {
diff --git a/luni/src/main/java/java/security/acl/Permission.java b/luni/src/main/java/java/security/acl/Permission.java
index 1b452d9..12391a9 100644
--- a/luni/src/main/java/java/security/acl/Permission.java
+++ b/luni/src/main/java/java/security/acl/Permission.java
@@ -18,10 +18,7 @@
package java.security.acl;
/**
- * The interface that represents a permission.
- * <p>
- * It can be granted or denied to a {@link java.security.Principal Principal}
- * using an {@link Acl}.
+ * Legacy security code; this class exists for compatibility only.
*/
public interface Permission {
diff --git a/luni/src/main/java/java/sql/Connection.java b/luni/src/main/java/java/sql/Connection.java
index ff534fc..38b4b49 100644
--- a/luni/src/main/java/java/sql/Connection.java
+++ b/luni/src/main/java/java/sql/Connection.java
@@ -40,7 +40,7 @@
* SQL {@code WHERE} clause</br></li>
* </ul>
*/
-public interface Connection extends Wrapper {
+public interface Connection extends Wrapper, AutoCloseable {
/**
* A constant indicating that transactions are not supported.
diff --git a/luni/src/main/java/java/sql/ResultSet.java b/luni/src/main/java/java/sql/ResultSet.java
index 3b7c76e..64ecb59 100644
--- a/luni/src/main/java/java/sql/ResultSet.java
+++ b/luni/src/main/java/java/sql/ResultSet.java
@@ -81,7 +81,7 @@
* statement is executed again, or the same statement's next {@code ResultSet}
* is retrieved (if the statement returned of multiple results).
*/
-public interface ResultSet extends Wrapper {
+public interface ResultSet extends Wrapper, AutoCloseable {
/**
* A constant used to indicate that a {@code ResultSet} object must be
diff --git a/luni/src/main/java/java/sql/SQLPermission.java b/luni/src/main/java/java/sql/SQLPermission.java
index c254929..0418648 100644
--- a/luni/src/main/java/java/sql/SQLPermission.java
+++ b/luni/src/main/java/java/sql/SQLPermission.java
@@ -22,17 +22,7 @@
import java.security.Guard;
/**
- * A Permission relating to security access control in the {@code java.sql}
- * package.
- * <p>
- * Currently, the only permission supported has the name " {@code setLog}". The
- * {@code setLog} permission controls whether a Java application or applet can
- * open a logging stream using the {@code DriverManager.setLogWriter} method or
- * the {@code DriverManager.setLogStream} method. This is a potentially
- * dangerous operation since the logging stream can contain sensitive
- * information such as usernames and passwords.
- *
- * @see DriverManager
+ * Legacy security code; this class exists for compatibility only.
*/
public final class SQLPermission extends BasicPermission implements Guard,
Serializable {
diff --git a/luni/src/main/java/java/sql/Statement.java b/luni/src/main/java/java/sql/Statement.java
index 71ad154..1f7e297 100644
--- a/luni/src/main/java/java/sql/Statement.java
+++ b/luni/src/main/java/java/sql/Statement.java
@@ -34,7 +34,7 @@
* @see ResultSet
* @see Connection#createStatement
*/
-public interface Statement extends Wrapper {
+public interface Statement extends Wrapper, AutoCloseable {
/**
* Passing this constant to {@link #getMoreResults} implies that all {@code
diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java
index 13f7443..5bc7f0c 100644
--- a/luni/src/main/java/java/text/BreakIterator.java
+++ b/luni/src/main/java/java/text/BreakIterator.java
@@ -477,10 +477,7 @@
public abstract void setText(CharacterIterator newText);
/**
- * Creates a copy of this iterator, all status information including the
- * current position are kept the same.
- *
- * @return a copy of this iterator.
+ * Returns a copy of this iterator.
*/
@Override
public Object clone() {
diff --git a/luni/src/main/java/java/text/StringCharacterIterator.java b/luni/src/main/java/java/text/StringCharacterIterator.java
index 13c3543..c8b11f5 100644
--- a/luni/src/main/java/java/text/StringCharacterIterator.java
+++ b/luni/src/main/java/java/text/StringCharacterIterator.java
@@ -81,8 +81,7 @@
* start}, {@code location > end} or if {@code end} is greater
* than the length of {@code value}.
*/
- public StringCharacterIterator(String value, int start, int end,
- int location) {
+ public StringCharacterIterator(String value, int start, int end, int location) {
string = value;
if (start < 0 || end > string.length() || start > end
|| location < start || location > end) {
diff --git a/luni/src/main/java/java/util/PropertyPermission.java b/luni/src/main/java/java/util/PropertyPermission.java
index a5a4738..b33a1a7 100644
--- a/luni/src/main/java/java/util/PropertyPermission.java
+++ b/luni/src/main/java/java/util/PropertyPermission.java
@@ -26,17 +26,7 @@
import java.security.PermissionCollection;
/**
- * {@code PropertyPermission} objects represent a permission to access system
- * properties.
- * <p>
- * A permission is one of the possible permission strings like "user.name" or
- * "java.version". It's also possible to use a wildcard to define the permission
- * to several properties at once. For example "user.*" will define the
- * permission for "user.home", "user.name", "user.dir", ... "*" defines the
- * permission for all available properties.
- * <p>
- * There are two possible permission action types: read and write. Possible
- * actions are "read", "write", or "read,write"/"write,read".
+ * Legacy security code; this class exists for compatibility only.
*/
public final class PropertyPermission extends BasicPermission {
private static final long serialVersionUID = 885438825399942851L;
diff --git a/luni/src/main/java/java/util/regex/PatternSyntaxException.java b/luni/src/main/java/java/util/regex/PatternSyntaxException.java
index a8dec47..0c1de4b 100644
--- a/luni/src/main/java/java/util/regex/PatternSyntaxException.java
+++ b/luni/src/main/java/java/util/regex/PatternSyntaxException.java
@@ -88,31 +88,34 @@
*/
@Override
public String getMessage() {
- StringBuilder builder = new StringBuilder("Syntax error");
-
+ StringBuilder sb = new StringBuilder();
if (desc != null) {
- builder.append(' ');
- builder.append(desc);
+ sb.append(desc);
}
if (index >= 0) {
- builder.append(" near index " + index + ":");
+ if (desc != null) {
+ sb.append(' ');
+ }
+ sb.append("near index ");
+ sb.append(index);
+ sb.append(':');
}
if (pattern != null) {
- builder.append('\n');
- builder.append(pattern);
+ sb.append('\n');
+ sb.append(pattern);
if (index >= 0) {
char[] spaces = new char[index];
Arrays.fill(spaces, ' ');
- builder.append('\n');
- builder.append(spaces);
- builder.append('^');
+ sb.append('\n');
+ sb.append(spaces);
+ sb.append('^');
}
}
- return builder.toString();
+ return sb.toString();
}
/**
diff --git a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
index 8c49690..0f22f38 100644
--- a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
+++ b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java
@@ -24,8 +24,82 @@
import java.security.cert.X509Certificate;
/**
- * This abstract subclass of {@code HttpURLConnection} defines methods for
- * managing HTTPS connections according to the description given by RFC 2818.
+ * An {@link HttpURLConnection} for HTTPS (<a
+ * href="http://tools.ietf.org/html/rfc2818">RFC 2818</a>). A
+ * connected {@code HttpsURLConnection} allows access to the
+ * negotiated cipher suite, the server certificate chain, and the
+ * client certificate chain if any.
+ *
+ * <h3>Providing an application specific X509TrustManager</h3>
+ *
+ * If an application wants to trust Certificate Authority (CA)
+ * certificates that are not part of the system, it should specify its
+ * own {@code X509TrustManager} via a {@code SSLSocketFactory} set on
+ * the {@code HttpsURLConnection}. The {@code X509TrustManager} can be
+ * created based on a {@code KeyStore} using a {@code
+ * TrustManagerFactory} to supply trusted CA certificates. Note that
+ * self-signed certificates are effectively their own CA and can be
+ * trusted by including them in a {@code KeyStore}.
+ *
+ * <p>For example, to trust a set of certificates specified by a {@code KeyStore}:
+ * <pre> {@code
+ * KeyStore keyStore = ...;
+ * TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ * tmf.init(keyStore);
+ *
+ * SSLContext context = SSLContext.getInstance("TLS");
+ * context.init(null, tmf.getTrustManagers(), null);
+ *
+ * URL url = new URL("https://www.example.com/");
+ * HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+ * urlConnection.setSSLSocketFactory(context.getSocketFactory());
+ * InputStream in = urlConnection.getInputStream();
+ * }</pre>
+ *
+ * <p>It is possible to implement {@code X509TrustManager} directly
+ * instead of using one created by a {@code
+ * TrustManagerFactory}. While this is straightforward in the insecure
+ * case of allowing all certificate chains to pass verification,
+ * writing a proper implementation will usually want to take advantage
+ * of {@link java.security.cert.CertPathValidator
+ * CertPathValidator}. In general, it might be better to write a
+ * custom {@code KeyStore} implementation to pass to the {@code
+ * TrustManagerFactory} than to try and write a custom {@code
+ * X509TrustManager}.
+ *
+ * <h3>Providing an application specific X509KeyManager</h3>
+ *
+ * A custom {@code X509KeyManager} can be used to supply a client
+ * certificate and its associated private key to authenticate a
+ * connection to the server. The {@code X509KeyManager} can be created
+ * based on a {@code KeyStore} using a {@code KeyManagerFactory}.
+ *
+ * <p>For example, to supply client certificates from a {@code KeyStore}:
+ * <pre> {@code
+ * KeyStore keyStore = ...;
+ * KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
+ * kmf.init(keyStore);
+ *
+ * SSLContext context = SSLContext.getInstance("TLS");
+ * context.init(kmf.getKeyManagers(), null, null);
+ *
+ * URL url = new URL("https://www.example.com/");
+ * HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
+ * urlConnection.setSSLSocketFactory(context.getSocketFactory());
+ * InputStream in = urlConnection.getInputStream();
+ * }</pre>
+ *
+ * <p>A {@code X509KeyManager} can also be implemented directly. This
+ * can allow an application to return a certificate and private key
+ * from a non-{@code KeyStore} source or to specify its own logic for
+ * selecting a specific credential to use when many may be present in
+ * a single {@code KeyStore}.
+ *
+ * <h3>TLS Intolerance Support</h3>
+ *
+ * This class attempts to create secure connections using common TLS
+ * extensions and SSL deflate compression. Should that fail, the
+ * connection will be retried with SSLv3 only.
*/
public abstract class HttpsURLConnection extends HttpURLConnection {
diff --git a/luni/src/main/java/javax/net/ssl/SSLPermission.java b/luni/src/main/java/javax/net/ssl/SSLPermission.java
index 5b5c76f..e881cc4 100644
--- a/luni/src/main/java/javax/net/ssl/SSLPermission.java
+++ b/luni/src/main/java/javax/net/ssl/SSLPermission.java
@@ -20,15 +20,7 @@
import java.security.BasicPermission;
/**
- * The class representing a network permission.
- * <p>
- * The following permissions are defined, allowing the specified action:
- * <dl>
- * <dt> {@code "setHostnameVerifier"} </dt>
- * <dd> setting a callback object for additional verification of a hostname mismatch.</dd>
- * <dt> {@code "getSSLSessionContext"} </dt>
- * <dd> getting the {@code SSLSessionContext} of an {@code SSLSession}.</dd>
- * </dl>
+ * Legacy security code; this class exists for compatibility only.
*/
public final class SSLPermission extends BasicPermission {
diff --git a/luni/src/main/java/javax/security/auth/AuthPermission.java b/luni/src/main/java/javax/security/auth/AuthPermission.java
index 196979d..173f679 100644
--- a/luni/src/main/java/javax/security/auth/AuthPermission.java
+++ b/luni/src/main/java/javax/security/auth/AuthPermission.java
@@ -20,35 +20,7 @@
import java.security.BasicPermission;
/**
- * Governs the use of methods in this package and also its subpackages. A
- * <i>target name</i> of the permission specifies which methods are allowed
- * without specifying the concrete action lists. Possible target names and
- * associated authentication permissions are:
- *
- * <pre>
- * doAs invoke Subject.doAs methods.
- * doAsPrivileged invoke the Subject.doAsPrivileged methods.
- * getSubject invoke Subject.getSubject().
- * getSubjectFromDomainCombiner invoke SubjectDomainCombiner.getSubject().
- * setReadOnly invoke Subject.setReadonly().
- * modifyPrincipals modify the set of principals
- * associated with a Subject.
- * modifyPublicCredentials modify the set of public credentials
- * associated with a Subject.
- * modifyPrivateCredentials modify the set of private credentials
- * associated with a Subject.
- * refreshCredential invoke the refresh method on a credential of a
- * refreshable credential class.
- * destroyCredential invoke the destroy method on a credential of a
- * destroyable credential class.
- * createLoginContext.<i>name</i> instantiate a LoginContext with the
- * specified name. The wildcard name ('*')
- * allows to a LoginContext of any name.
- * getLoginConfiguration invoke the getConfiguration method of
- * javax.security.auth.login.Configuration.
- * refreshLoginConfiguration Invoke the refresh method of
- * javax.security.auth.login.Configuration.
- * </pre>
+ * Legacy security code; this class exists for compatibility only.
*/
public final class AuthPermission extends BasicPermission {
diff --git a/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java b/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java
index f86b2fa..7a9903d 100644
--- a/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java
+++ b/luni/src/main/java/javax/security/auth/PrivateCredentialPermission.java
@@ -26,26 +26,7 @@
import java.util.Set;
/**
- * Protects private credential objects belonging to a {@code Subject}. It has
- * only one action which is "read". The target name of this permission has a
- * special syntax:
- *
- * <pre>
- * targetName = CredentialClass {PrincipalClass "PrincipalName"}*
- * </pre>
- *
- * First it states a credential class and is followed then by a list of one or
- * more principals identifying the subject.
- * <p>
- * The principals on their part are specified as the name of the {@code
- * Principal} class followed by the principal name in quotes. For example, the
- * following file may define permission to read the private credentials of a
- * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\""
- * <p>
- * The syntax also allows the use of the wildcard "*" in place of {@code
- * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}.
- *
- * @see Principal
+ * Legacy security code; this class exists for compatibility only.
*/
public final class PrivateCredentialPermission extends Permission {
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index 352a41e..9e2b2c9 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -45,15 +45,22 @@
public StructStatFs fstatfs(FileDescriptor fd) throws ErrnoException { return os.fstatfs(fd); }
public void fsync(FileDescriptor fd) throws ErrnoException { os.fsync(fd); }
public void ftruncate(FileDescriptor fd, long length) throws ErrnoException { os.ftruncate(fd, length); }
+ public String gai_strerror(int error) { return os.gai_strerror(error); }
+ public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException { return os.getaddrinfo(node, hints); }
public String getenv(String name) { return os.getenv(name); }
+ public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); }
public SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return os.getsockname(fd); }
public int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptByte(fd, level, option); }
public InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptInAddr(fd, level, option); }
public int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptInt(fd, level, option); }
public StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptLinger(fd, level, option); }
public StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return os.getsockoptTimeval(fd, level, option); }
+ public String if_indextoname(int index) { return os.if_indextoname(index); }
+ public InetAddress inet_aton(String address) { return os.inet_aton(address); }
+ public InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return os.ioctlInetAddress(fd, cmd, interfaceName); }
public int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return os.ioctlInt(fd, cmd, arg); }
public boolean isatty(FileDescriptor fd) { return os.isatty(fd); }
+ public void kill(int pid, int signal) throws ErrnoException { os.kill(pid, signal); }
public void listen(FileDescriptor fd, int backlog) throws ErrnoException { os.listen(fd, backlog); }
public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return os.lseek(fd, offset, whence); }
public StructStat lstat(String path) throws ErrnoException { return os.lstat(path); }
@@ -72,7 +79,13 @@
public void remove(String path) throws ErrnoException { os.remove(path); }
public void rename(String oldPath, String newPath) throws ErrnoException { os.rename(oldPath, newPath); }
public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return os.sendfile(outFd, inFd, inOffset, byteCount); }
+ public void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptByte(fd, level, option, value); }
+ public void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { os.setsockoptIfreq(fd, level, option, value); }
public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptInt(fd, level, option, value); }
+ public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptIpMreqn(fd, level, option, value); }
+ public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { os.setsockoptGroupReq(fd, level, option, value); }
+ public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { os.setsockoptLinger(fd, level, option, value); }
+ public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { os.setsockoptTimeval(fd, level, option, value); }
public void shutdown(FileDescriptor fd, int how) throws ErrnoException { os.shutdown(fd, how); }
public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return os.socket(domain, type, protocol); }
public StructStat stat(String path) throws ErrnoException { return os.stat(path); }
diff --git a/luni/src/main/java/libcore/io/GaiException.java b/luni/src/main/java/libcore/io/GaiException.java
new file mode 100644
index 0000000..08143dc
--- /dev/null
+++ b/luni/src/main/java/libcore/io/GaiException.java
@@ -0,0 +1,66 @@
+/*
+ * 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.io;
+
+import java.net.UnknownHostException;
+import libcore.io.OsConstants;
+
+/**
+ * An unchecked exception thrown when the {@link Os} {@code getaddrinfo} or {@code getnameinfo}
+ * methods fail. This exception contains the native error value, for comparison against the
+ * {@code GAI_} constants in {@link OsConstants}, should sophisticated
+ * callers need to adjust their behavior based on the exact failure.
+ */
+public final class GaiException extends RuntimeException {
+ private final String functionName;
+ public final int error;
+
+ public GaiException(String functionName, int error) {
+ this.functionName = functionName;
+ this.error = error;
+ }
+
+ public GaiException(String functionName, int error, Throwable cause) {
+ super(cause);
+ this.functionName = functionName;
+ this.error = error;
+ }
+
+ /**
+ * Converts the stashed function name and error value to a human-readable string.
+ * We do this here rather than in the constructor so that callers only pay for
+ * this if they need it.
+ */
+ @Override public String getMessage() {
+ String gaiName = OsConstants.gaiName(error);
+ if (gaiName == null) {
+ gaiName = "GAI_ error " + error;
+ }
+ String description = Libcore.os.gai_strerror(error);
+ return functionName + " failed: " + gaiName + " (" + description + ")";
+ }
+
+ public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException {
+ UnknownHostException newException = new UnknownHostException(detailMessage);
+ newException.initCause(this);
+ throw newException;
+ }
+
+ public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException {
+ throw rethrowAsUnknownHostException(getMessage());
+ }
+}
diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java
index 0a4de9f..eeb8d99 100644
--- a/luni/src/main/java/libcore/io/IoUtils.java
+++ b/luni/src/main/java/libcore/io/IoUtils.java
@@ -28,12 +28,17 @@
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOptions;
+import java.net.SocketTimeoutException;
+import java.nio.charset.Charsets;
import java.util.Arrays;
import libcore.io.ErrnoException;
import libcore.io.Libcore;
import libcore.util.MutableInt;
import static libcore.io.OsConstants.*;
+// TODO: kill this!
+import org.apache.harmony.luni.platform.Platform;
+
public final class IoUtils {
private IoUtils() {
}
@@ -191,6 +196,67 @@
}
/**
+ * Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout
+ * means this method won't throw SocketTimeoutException.
+ */
+ public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
+ try {
+ return IoUtils.connect(fd, inetAddress, port, 0);
+ } catch (SocketTimeoutException ex) {
+ throw new AssertionError(ex); // Can't happen for a connect without a timeout.
+ }
+ }
+
+ /**
+ * Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'.
+ * Use timeoutMs == 0 for a blocking connect with no timeout.
+ */
+ public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
+ try {
+ return connectErrno(fd, inetAddress, port, timeoutMs);
+ } catch (SocketException ex) {
+ throw ex; // We don't want to doubly wrap these.
+ } catch (SocketTimeoutException ex) {
+ throw ex; // We don't want to doubly wrap these.
+ } catch (IOException ex) {
+ throw new SocketException(ex);
+ }
+ }
+
+ // TODO: this is the wrong name now, but when this gets rewritten without Platform.NETWORK...
+ private static boolean connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws IOException {
+ // With no timeout, just call connect(2) directly.
+ if (timeoutMs == 0) {
+ return Platform.NETWORK.connect(fd, inetAddress, port);
+ }
+
+ // With a timeout, we set the socket to non-blocking, connect(2), and then loop
+ // using select(2) to decide whether we're connected, whether we should keep waiting,
+ // or whether we've seen a permanent failure and should give up.
+ long finishTimeMs = System.currentTimeMillis() + timeoutMs;
+ IoUtils.setBlocking(fd, false);
+ try {
+ if (Platform.NETWORK.connect(fd, inetAddress, port)) {
+ return true;
+ }
+ int remainingTimeoutMs;
+ do {
+ remainingTimeoutMs = (int) (finishTimeMs - System.currentTimeMillis());
+ if (remainingTimeoutMs <= 0) {
+ String detail = "failed to connect to " + inetAddress + " (port " + port + ")";
+ if (timeoutMs > 0) {
+ detail += " after " + timeoutMs + "ms";
+ }
+ throw new SocketTimeoutException(detail);
+ }
+ } while (!Platform.NETWORK.isConnected(fd, remainingTimeoutMs));
+ return true; // Or we'd have thrown.
+ } finally {
+ IoUtils.setBlocking(fd, true);
+ }
+ }
+
+ /**
* Sets 'fd' to be blocking or non-blocking, according to the state of 'blocking'.
*/
public static void setBlocking(FileDescriptor fd, boolean blocking) throws IOException {
@@ -229,6 +295,11 @@
}
}
+ // Socket options used by java.net but not exposed in SocketOptions.
+ public static final int JAVA_MCAST_JOIN_GROUP = 19;
+ public static final int JAVA_MCAST_LEAVE_GROUP = 20;
+ public static final int JAVA_IP_MULTICAST_TTL = 17;
+
/**
* java.net has its own socket options similar to the underlying Unix ones. We paper over the
* differences here.
@@ -241,45 +312,26 @@
}
}
- // Socket options used by java.net but not exposed in SocketOptions.
- public static final int MCAST_JOIN_GROUP = 19;
- public static final int MCAST_LEAVE_GROUP = 20;
- public static final int IP_MULTICAST_TTL = 17;
-
private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException {
switch (option) {
- case SocketOptions.IP_MULTICAST_IF2:
- if (boundIPv4(fd)) {
- // The caller's asking for an interface index, but that's not how IPv4 works.
- // Our Java should never get here, because we'll try IP_MULTICAST_IF first and
- // that will satisfy us.
- throw new SocketException("no interface index for IPv4");
- } else {
- return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
- }
case SocketOptions.IP_MULTICAST_IF:
+ // This is IPv4-only.
return Libcore.os.getsockoptInAddr(fd, IPPROTO_IP, IP_MULTICAST_IF);
+ case SocketOptions.IP_MULTICAST_IF2:
+ // This is IPv6-only.
+ return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
case SocketOptions.IP_MULTICAST_LOOP:
- if (boundIPv4(fd)) {
- // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
- // IPv4 multicast TTL uses a byte.
- return booleanFromInt(Libcore.os.getsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP));
- } else {
- return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
- }
- case IoUtils.IP_MULTICAST_TTL:
- if (boundIPv4(fd)) {
- // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
- return Libcore.os.getsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL);
- } else {
- return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
- }
+ // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
+ // it doesn't matter which we return.
+ return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
+ case IoUtils.JAVA_IP_MULTICAST_TTL:
+ // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
+ // it doesn't matter which we return.
+ return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
case SocketOptions.IP_TOS:
- if (boundIPv4(fd)) {
- return Libcore.os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
- } else {
- return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
- }
+ // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
+ // it doesn't matter which we return.
+ return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
case SocketOptions.SO_BROADCAST:
return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_BROADCAST));
case SocketOptions.SO_KEEPALIVE:
@@ -303,23 +355,102 @@
case SocketOptions.TCP_NODELAY:
return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
default:
- throw new SocketException("unknown socket option " + option);
+ throw new SocketException("Unknown socket option: " + option);
}
}
- private static boolean boundIPv4(FileDescriptor fd) {
- SocketAddress sa = Libcore.os.getsockname(fd);
- if (!(sa instanceof InetSocketAddress)) {
- return false;
- }
- InetSocketAddress isa = (InetSocketAddress) sa;
- return (isa.getAddress() instanceof Inet4Address);
- }
-
private static boolean booleanFromInt(int i) {
return (i != 0);
}
+ private static int booleanToInt(boolean b) {
+ return b ? 1 : 0;
+ }
+
+ /**
+ * java.net has its own socket options similar to the underlying Unix ones. We paper over the
+ * differences here.
+ */
+ public static void setSocketOption(FileDescriptor fd, int option, Object value) throws SocketException {
+ try {
+ setSocketOptionErrno(fd, option, value);
+ } catch (ErrnoException errnoException) {
+ throw errnoException.rethrowAsSocketException();
+ }
+ }
+
+ private static void setSocketOptionErrno(FileDescriptor fd, int option, Object value) throws SocketException {
+ switch (option) {
+ case SocketOptions.IP_MULTICAST_IF:
+ throw new UnsupportedOperationException("Use IP_MULTICAST_IF2 on Android");
+ case SocketOptions.IP_MULTICAST_IF2:
+ // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
+ Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, (Integer) value);
+ Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (Integer) value);
+ return;
+ case SocketOptions.IP_MULTICAST_LOOP:
+ // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
+ Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, booleanToInt((Boolean) value));
+ Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, booleanToInt((Boolean) value));
+ return;
+ case IoUtils.JAVA_IP_MULTICAST_TTL:
+ // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
+ // IPv4 multicast TTL uses a byte.
+ Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL, (Integer) value);
+ Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (Integer) value);
+ return;
+ case SocketOptions.IP_TOS:
+ Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_TOS, (Integer) value);
+ Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS, (Integer) value);
+ return;
+ case SocketOptions.SO_BROADCAST:
+ Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_BROADCAST, booleanToInt((Boolean) value));
+ return;
+ case SocketOptions.SO_KEEPALIVE:
+ Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE, booleanToInt((Boolean) value));
+ return;
+ case SocketOptions.SO_LINGER:
+ boolean on = false;
+ int seconds = 0;
+ if (value instanceof Integer) {
+ on = true;
+ seconds = Math.min((Integer) value, 65535);
+ }
+ StructLinger linger = new StructLinger(booleanToInt(on), seconds);
+ Libcore.os.setsockoptLinger(fd, SOL_SOCKET, SO_LINGER, linger);
+ return;
+ case SocketOptions.SO_OOBINLINE:
+ Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE, booleanToInt((Boolean) value));
+ return;
+ case SocketOptions.SO_RCVBUF:
+ Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, (Integer) value);
+ return;
+ case SocketOptions.SO_REUSEADDR:
+ Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR, booleanToInt((Boolean) value));
+ return;
+ case SocketOptions.SO_SNDBUF:
+ Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_SNDBUF, (Integer) value);
+ return;
+ case SocketOptions.SO_TIMEOUT:
+ int millis = (Integer) value;
+ StructTimeval tv = StructTimeval.fromMillis(millis);
+ Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv);
+ return;
+ case SocketOptions.TCP_NODELAY:
+ Libcore.os.setsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY, booleanToInt((Boolean) value));
+ return;
+ case IoUtils.JAVA_MCAST_JOIN_GROUP:
+ case IoUtils.JAVA_MCAST_LEAVE_GROUP:
+ StructGroupReq groupReq = (StructGroupReq) value;
+ int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
+ int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
+ Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
+ return;
+ default:
+ throw new SocketException("Unknown socket option: " + option);
+ }
+ }
+
public static InetAddress getSocketLocalAddress(FileDescriptor fd) {
SocketAddress sa = Libcore.os.getsockname(fd);
InetSocketAddress isa = (InetSocketAddress) sa;
@@ -336,12 +467,29 @@
* Returns the contents of 'path' as a byte array.
*/
public static byte[] readFileAsByteArray(String path) throws IOException {
+ return readFileAsBytes(path).toByteArray();
+ }
+
+ /**
+ * Returns the contents of 'path' as a string. The contents are assumed to be UTF-8.
+ */
+ public static String readFileAsString(String path) throws IOException {
+ return readFileAsBytes(path).toString(Charsets.UTF_8);
+ }
+
+ private static UnsafeByteSequence readFileAsBytes(String path) throws IOException {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(path, "r");
- byte[] buf = new byte[(int) f.length()];
- f.readFully(buf);
- return buf;
+ UnsafeByteSequence bytes = new UnsafeByteSequence((int) f.length());
+ byte[] buffer = new byte[8192];
+ while (true) {
+ int byteCount = f.read(buffer);
+ if (byteCount == -1) {
+ return bytes;
+ }
+ bytes.write(buffer, 0, byteCount);
+ }
} finally {
IoUtils.closeQuietly(f);
}
diff --git a/luni/src/main/java/libcore/io/Memory.java b/luni/src/main/java/libcore/io/Memory.java
index ddf0d52..9bf1acb 100644
--- a/luni/src/main/java/libcore/io/Memory.java
+++ b/luni/src/main/java/libcore/io/Memory.java
@@ -19,6 +19,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
@@ -137,7 +138,17 @@
}
}
- public static native void memmove(int destAddress, int srcAddress, long byteCount);
+ /**
+ * Copies 'byteCount' bytes from the source to the destination. The objects are either
+ * instances of DirectByteBuffer or byte[]. The offsets in the byte[] case must include
+ * the Buffer.arrayOffset if the array came from a Buffer.array call. We could make this
+ * private and provide the four type-safe variants, but then ByteBuffer.put(ByteBuffer)
+ * would need to work out which to call based on whether the source and destination buffers
+ * are direct or not.
+ *
+ * @hide make type-safe before making public?
+ */
+ public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount);
public static native byte peekByte(int address);
public static native int peekInt(int address, boolean swap);
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index 0f10584..7fceedc 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -36,15 +36,23 @@
public StructStatFs fstatfs(FileDescriptor fd) throws ErrnoException;
public void fsync(FileDescriptor fd) throws ErrnoException;
public void ftruncate(FileDescriptor fd, long length) throws ErrnoException;
+ public String gai_strerror(int error);
+ public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
public String getenv(String name);
+ /* TODO: break into getnameinfoHost and getnameinfoService? */
+ public String getnameinfo(InetAddress address, int flags) throws GaiException;
public SocketAddress getsockname(FileDescriptor fd) throws ErrnoException;
public int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException;
public InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException;
public int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException;
public StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException;
public StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException;
+ public String if_indextoname(int index);
+ public InetAddress inet_aton(String address);
+ public InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException;
public int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException;
public boolean isatty(FileDescriptor fd);
+ public void kill(int pid, int signal) throws ErrnoException;
public void listen(FileDescriptor fd, int backlog) throws ErrnoException;
public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException;
public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
@@ -63,7 +71,13 @@
public void remove(String path) throws ErrnoException;
public void rename(String oldPath, String newPath) throws ErrnoException;
public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException;
+ public void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+ public void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException;
public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+ public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+ public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+ public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
+ public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
public void shutdown(FileDescriptor fd, int how) throws ErrnoException;
public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException;
public StructStat stat(String path) throws ErrnoException;
diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/libcore/io/OsConstants.java
index 616e60a..34a931c 100644
--- a/luni/src/main/java/libcore/io/OsConstants.java
+++ b/luni/src/main/java/libcore/io/OsConstants.java
@@ -39,12 +39,30 @@
public static final int AF_INET6 = placeholder();
public static final int AF_UNIX = placeholder();
public static final int AF_UNSPEC = placeholder();
+ public static final int AI_ADDRCONFIG = placeholder();
+ public static final int AI_ALL = placeholder();
+ public static final int AI_CANONNAME = placeholder();
+ public static final int AI_NUMERICHOST = placeholder();
+ public static final int AI_NUMERICSERV = placeholder();
+ public static final int AI_PASSIVE = placeholder();
+ public static final int AI_V4MAPPED = placeholder();
public static final int E2BIG = placeholder();
public static final int EACCES = placeholder();
public static final int EADDRINUSE = placeholder();
public static final int EADDRNOTAVAIL = placeholder();
public static final int EAFNOSUPPORT = placeholder();
public static final int EAGAIN = placeholder();
+ public static final int EAI_AGAIN = placeholder();
+ public static final int EAI_BADFLAGS = placeholder();
+ public static final int EAI_FAIL = placeholder();
+ public static final int EAI_FAMILY = placeholder();
+ public static final int EAI_MEMORY = placeholder();
+ public static final int EAI_NODATA = placeholder();
+ public static final int EAI_NONAME = placeholder();
+ public static final int EAI_OVERFLOW = placeholder();
+ public static final int EAI_SERVICE = placeholder();
+ public static final int EAI_SOCKTYPE = placeholder();
+ public static final int EAI_SYSTEM = placeholder();
public static final int EALREADY = placeholder();
public static final int EBADF = placeholder();
public static final int EBADMSG = placeholder();
@@ -139,6 +157,22 @@
public static final int F_SETOWN = placeholder();
public static final int F_UNLCK = placeholder();
public static final int F_WRLCK = placeholder();
+ public static final int IFF_ALLMULTI = placeholder();
+ public static final int IFF_AUTOMEDIA = placeholder();
+ public static final int IFF_BROADCAST = placeholder();
+ public static final int IFF_DEBUG = placeholder();
+ public static final int IFF_DYNAMIC = placeholder();
+ public static final int IFF_LOOPBACK = placeholder();
+ public static final int IFF_MASTER = placeholder();
+ public static final int IFF_MULTICAST = placeholder();
+ public static final int IFF_NOARP = placeholder();
+ public static final int IFF_NOTRAILERS = placeholder();
+ public static final int IFF_POINTOPOINT = placeholder();
+ public static final int IFF_PORTSEL = placeholder();
+ public static final int IFF_PROMISC = placeholder();
+ public static final int IFF_RUNNING = placeholder();
+ public static final int IFF_SLAVE = placeholder();
+ public static final int IFF_UP = placeholder();
public static final int IPPROTO_ICMP = placeholder();
public static final int IPPROTO_IP = placeholder();
public static final int IPPROTO_IPV6 = placeholder();
@@ -180,6 +214,11 @@
public static final int MS_ASYNC = placeholder();
public static final int MS_INVALIDATE = placeholder();
public static final int MS_SYNC = placeholder();
+ public static final int NI_DGRAM = placeholder();
+ public static final int NI_NAMEREQD = placeholder();
+ public static final int NI_NOFQDN = placeholder();
+ public static final int NI_NUMERICHOST = placeholder();
+ public static final int NI_NUMERICSERV = placeholder();
public static final int O_ACCMODE = placeholder();
public static final int O_APPEND = placeholder();
public static final int O_CREAT = placeholder();
@@ -202,11 +241,49 @@
public static final int SHUT_RD = placeholder();
public static final int SHUT_RDWR = placeholder();
public static final int SHUT_WR = placeholder();
+ public static final int SIGABRT = placeholder();
+ public static final int SIGALRM = placeholder();
+ public static final int SIGBUS = placeholder();
+ public static final int SIGCHLD = placeholder();
+ public static final int SIGCONT = placeholder();
+ public static final int SIGFPE = placeholder();
+ public static final int SIGHUP = placeholder();
+ public static final int SIGILL = placeholder();
+ public static final int SIGINT = placeholder();
+ public static final int SIGIO = placeholder();
+ public static final int SIGKILL = placeholder();
+ public static final int SIGPIPE = placeholder();
+ public static final int SIGPROF = placeholder();
+ public static final int SIGPWR = placeholder();
+ public static final int SIGQUIT = placeholder();
+ public static final int SIGRTMAX = placeholder();
+ public static final int SIGRTMIN = placeholder();
+ public static final int SIGSEGV = placeholder();
+ public static final int SIGSTKFLT = placeholder();
+ public static final int SIGSTOP = placeholder();
+ public static final int SIGSYS = placeholder();
+ public static final int SIGTERM = placeholder();
+ public static final int SIGTRAP = placeholder();
+ public static final int SIGTSTP = placeholder();
+ public static final int SIGTTIN = placeholder();
+ public static final int SIGTTOU = placeholder();
+ public static final int SIGURG = placeholder();
+ public static final int SIGUSR1 = placeholder();
+ public static final int SIGUSR2 = placeholder();
+ public static final int SIGVTALRM = placeholder();
+ public static final int SIGWINCH = placeholder();
+ public static final int SIGXCPU = placeholder();
+ public static final int SIGXFSZ = placeholder();
+ public static final int SIOCGIFADDR = placeholder();
+ public static final int SIOCGIFBRDADDR = placeholder();
+ public static final int SIOCGIFDSTADDR = placeholder();
+ public static final int SIOCGIFNETMASK = placeholder();
public static final int SOCK_DGRAM = placeholder();
public static final int SOCK_RAW = placeholder();
public static final int SOCK_SEQPACKET = placeholder();
public static final int SOCK_STREAM = placeholder();
public static final int SOL_SOCKET = placeholder();
+ public static final int SO_BINDTODEVICE = placeholder();
public static final int SO_BROADCAST = placeholder();
public static final int SO_DEBUG = placeholder();
public static final int SO_DONTROUTE = placeholder();
@@ -348,6 +425,43 @@
public static final int _SC_XOPEN_VERSION = placeholder();
public static final int _SC_XOPEN_XCU_VERSION = placeholder();
+ public static String gaiName(int error) {
+ if (error == EAI_AGAIN) {
+ return "EAI_AGAIN";
+ }
+ if (error == EAI_BADFLAGS) {
+ return "EAI_BADFLAGS";
+ }
+ if (error == EAI_FAIL) {
+ return "EAI_FAIL";
+ }
+ if (error == EAI_FAMILY) {
+ return "EAI_FAMILY";
+ }
+ if (error == EAI_MEMORY) {
+ return "EAI_MEMORY";
+ }
+ if (error == EAI_NODATA) {
+ return "EAI_NODATA";
+ }
+ if (error == EAI_NONAME) {
+ return "EAI_NONAME";
+ }
+ if (error == EAI_OVERFLOW) {
+ return "EAI_OVERFLOW";
+ }
+ if (error == EAI_SERVICE) {
+ return "EAI_SERVICE";
+ }
+ if (error == EAI_SOCKTYPE) {
+ return "EAI_SOCKTYPE";
+ }
+ if (error == EAI_SYSTEM) {
+ return "EAI_SYSTEM";
+ }
+ return null;
+ }
+
public static String errnoName(int errno) {
if (errno == E2BIG) {
return "E2BIG";
diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java
index 6c49e51..4e23dc0 100644
--- a/luni/src/main/java/libcore/io/Posix.java
+++ b/luni/src/main/java/libcore/io/Posix.java
@@ -39,15 +39,22 @@
public native StructStatFs fstatfs(FileDescriptor fd) throws ErrnoException;
public native void fsync(FileDescriptor fd) throws ErrnoException;
public native void ftruncate(FileDescriptor fd, long length) throws ErrnoException;
+ public native String gai_strerror(int error);
+ public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
public native String getenv(String name);
+ public native String getnameinfo(InetAddress address, int flags) throws GaiException;
public native SocketAddress getsockname(FileDescriptor fd) throws ErrnoException;
public native int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException;
public native InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException;
public native int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException;
public native StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException;
public native StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException;
+ public native InetAddress inet_aton(String address);
+ public native String if_indextoname(int index);
+ public native InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException;
public native int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException;
public native boolean isatty(FileDescriptor fd);
+ public native void kill(int pid, int signal) throws ErrnoException;
public native void listen(FileDescriptor fd, int backlog) throws ErrnoException;
public native long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException;
public native void mincore(long address, long byteCount, byte[] vector) throws ErrnoException;
@@ -62,17 +69,27 @@
public native StructStat lstat(String path) throws ErrnoException;
public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
if (buffer.isDirect()) {
- return readDirectBuffer(fd, buffer, buffer.position(), buffer.remaining());
+ return readBytes(fd, buffer, buffer.position(), buffer.remaining());
+ } else {
+ return readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
- return read(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
- private native int readDirectBuffer(FileDescriptor fd, ByteBuffer buffer, int position, int remaining) throws ErrnoException;
- public native int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
+ public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
+ return readBytes(fd, bytes, byteOffset, byteCount);
+ }
+ private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
public native void remove(String path) throws ErrnoException;
public native void rename(String oldPath, String newPath) throws ErrnoException;
public native long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException;
+ public native void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+ public native void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException;
public native void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+ public native void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException;
+ public native void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException;
+ public native void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException;
+ public native void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException;
public native void shutdown(FileDescriptor fd, int how) throws ErrnoException;
public native FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException;
public native StructStat stat(String path) throws ErrnoException;
@@ -83,11 +100,15 @@
public native StructUtsname uname() throws ErrnoException;
public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException {
if (buffer.isDirect()) {
- return writeDirectBuffer(fd, buffer, buffer.position(), buffer.remaining());
+ return writeBytes(fd, buffer, buffer.position(), buffer.remaining());
+ } else {
+ return writeBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
- return write(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining());
}
- private native int writeDirectBuffer(FileDescriptor fd, ByteBuffer buffer, int position, int remaining) throws ErrnoException;
- public native int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException;
+ public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException {
+ // This indirection isn't strictly necessary, but ensures that our public interface is type safe.
+ return writeBytes(fd, bytes, byteOffset, byteCount);
+ }
+ private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException;
public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException;
}
diff --git a/luni/src/main/java/libcore/io/StructAddrinfo.java b/luni/src/main/java/libcore/io/StructAddrinfo.java
new file mode 100644
index 0000000..8c8181d
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructAddrinfo.java
@@ -0,0 +1,51 @@
+/*
+ * 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.io;
+
+import java.net.InetAddress;
+
+/**
+ * Information returned/taken by getaddrinfo(3). Corresponds to C's {@code struct addrinfo} from
+ * <a href="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/netdb.h.html"><netdb.h></a>
+ *
+ * TODO: we currently only _take_ a StructAddrinfo; getaddrinfo returns an InetAddress[].
+ */
+public final class StructAddrinfo {
+ /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */
+ public int ai_flags;
+
+ /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */
+ public int ai_family;
+
+ /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */
+ public int ai_socktype;
+
+ /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */
+ public int ai_protocol;
+
+ /** Address length. (Not useful in Java.) */
+ // public int ai_addrlen;
+
+ /** Address. */
+ public InetAddress ai_addr;
+
+ /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */
+ // public String ai_canonname;
+
+ /** Next element in linked list. */
+ public StructAddrinfo ai_next;
+}
diff --git a/luni/src/main/java/libcore/io/StructFlock.java b/luni/src/main/java/libcore/io/StructFlock.java
index 6d63064..11c29df 100644
--- a/luni/src/main/java/libcore/io/StructFlock.java
+++ b/luni/src/main/java/libcore/io/StructFlock.java
@@ -16,8 +16,6 @@
package libcore.io;
-import static libcore.io.OsConstants.*;
-
/**
* Information returned/taken by fcntl(2) F_GETFL and F_SETFL. Corresponds to C's
* {@code struct flock} from
diff --git a/luni/src/main/java/libcore/io/StructGroupReq.java b/luni/src/main/java/libcore/io/StructGroupReq.java
new file mode 100644
index 0000000..0bdf783
--- /dev/null
+++ b/luni/src/main/java/libcore/io/StructGroupReq.java
@@ -0,0 +1,36 @@
+/*
+ * 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.io;
+
+import java.net.InetAddress;
+
+/**
+ * Corresponds to C's {@code struct group_req}.
+ */
+public final class StructGroupReq {
+ public final int gr_interface;
+ public final InetAddress gr_group;
+
+ public StructGroupReq(int gr_interface, InetAddress gr_group) {
+ this.gr_interface = gr_interface;
+ this.gr_group = gr_group;
+ }
+
+ @Override public String toString() {
+ return "StructGroupReq[gr_interface=" + gr_interface + ",gr_group=" + gr_group + "]";
+ }
+}
diff --git a/luni/src/main/java/libcore/io/StructStat.java b/luni/src/main/java/libcore/io/StructStat.java
index 9d1a877..05ecca7 100644
--- a/luni/src/main/java/libcore/io/StructStat.java
+++ b/luni/src/main/java/libcore/io/StructStat.java
@@ -16,8 +16,6 @@
package libcore.io;
-import static libcore.io.OsConstants.*;
-
/**
* File information returned by fstat(2), lstat(2), and stat(2). Corresponds to C's
* {@code struct stat} from
diff --git a/luni/src/main/java/libcore/io/StructStatFs.java b/luni/src/main/java/libcore/io/StructStatFs.java
index f853c9a..603dc86 100644
--- a/luni/src/main/java/libcore/io/StructStatFs.java
+++ b/luni/src/main/java/libcore/io/StructStatFs.java
@@ -16,8 +16,6 @@
package libcore.io;
-import static libcore.io.OsConstants.*;
-
/**
* File information returned by fstatfs(2) and statfs(2).
*
diff --git a/luni/src/main/java/libcore/io/StructUtsname.java b/luni/src/main/java/libcore/io/StructUtsname.java
index 4bbde16..e6a8e42 100644
--- a/luni/src/main/java/libcore/io/StructUtsname.java
+++ b/luni/src/main/java/libcore/io/StructUtsname.java
@@ -16,11 +16,9 @@
package libcore.io;
-import static libcore.io.OsConstants.*;
-
/**
* Information returned by uname(2). Corresponds to C's
- * {@code struct utname} from
+ * {@code struct utsname} from
* <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_utsname.h.html"><sys/utsname.h></a>
*/
public final class StructUtsname {
diff --git a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
index 40bc554..755bc96 100644
--- a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
+++ b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java
@@ -34,15 +34,15 @@
*/
abstract class AbstractHttpInputStream extends InputStream {
protected final InputStream in;
- protected final HttpURLConnectionImpl httpURLConnection;
+ protected final HttpEngine httpEngine;
private final CacheRequest cacheRequest;
private final OutputStream cacheBody;
protected boolean closed;
- AbstractHttpInputStream(InputStream in, HttpURLConnectionImpl httpURLConnection,
+ AbstractHttpInputStream(InputStream in, HttpEngine httpEngine,
CacheRequest cacheRequest) throws IOException {
this.in = in;
- this.httpURLConnection = httpURLConnection;
+ this.httpEngine = httpEngine;
OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null;
@@ -83,7 +83,7 @@
if (cacheRequest != null) {
cacheBody.close();
}
- httpURLConnection.releaseSocket(reuseSocket);
+ httpEngine.releaseSocket(reuseSocket);
}
/**
@@ -102,6 +102,6 @@
if (cacheRequest != null) {
cacheRequest.abort();
}
- httpURLConnection.releaseSocket(false);
+ httpEngine.releaseSocket(false);
}
}
diff --git a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
index 7a53728..274f03c 100644
--- a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
+++ b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java
@@ -31,8 +31,8 @@
private boolean hasMoreChunks = true;
ChunkedInputStream(InputStream is, CacheRequest cacheRequest,
- HttpURLConnectionImpl httpURLConnection) throws IOException {
- super(is, httpURLConnection, cacheRequest);
+ HttpEngine httpEngine) throws IOException {
+ super(is, httpEngine, cacheRequest);
}
@Override public int read(byte[] buffer, int offset, int count) throws IOException {
@@ -73,9 +73,9 @@
private void readChunkSize() throws IOException {
// read the suffix of the previous chunk
if (bytesRemainingInChunk != NO_CHUNK_YET) {
- HttpURLConnectionImpl.readLine(in);
+ HttpEngine.readLine(in);
}
- String chunkSizeString = HttpURLConnectionImpl.readLine(in);
+ String chunkSizeString = HttpEngine.readLine(in);
int index = chunkSizeString.indexOf(";");
if (index != -1) {
chunkSizeString = chunkSizeString.substring(0, index);
@@ -87,7 +87,7 @@
}
if (bytesRemainingInChunk == 0) {
hasMoreChunks = false;
- httpURLConnection.readHeaders(); // actually trailers!
+ httpEngine.readTrailers();
endOfInput(true);
}
}
diff --git a/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java b/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
index 3596cd1..1091af7 100644
--- a/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
+++ b/luni/src/main/java/libcore/net/http/FixedLengthInputStream.java
@@ -28,8 +28,8 @@
private int bytesRemaining;
public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest,
- HttpURLConnectionImpl httpURLConnection, int length) throws IOException {
- super(is, httpURLConnection, cacheRequest);
+ HttpEngine httpEngine, int length) throws IOException {
+ super(is, httpEngine, cacheRequest);
bytesRemaining = length;
if (bytesRemaining == 0) {
endOfInput(true);
diff --git a/luni/src/main/java/libcore/net/http/HeaderParser.java b/luni/src/main/java/libcore/net/http/HeaderParser.java
new file mode 100644
index 0000000..31e09f9
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HeaderParser.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+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);
+ }
+ }
+
+ /**
+ * 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
index 13e0971..b5441e6 100644
--- a/luni/src/main/java/libcore/net/http/HttpConnection.java
+++ b/luni/src/main/java/libcore/net/http/HttpConnection.java
@@ -24,11 +24,13 @@
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.SocketTimeoutException;
import java.net.URI;
+import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
@@ -45,7 +47,7 @@
* <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
* which isn't so much a connection as a single request/response pair.
*/
-public final class HttpConnection {
+final class HttpConnection {
private final Address address;
private final Socket socket;
@@ -85,6 +87,47 @@
this.socket = socketCandidate;
}
+ public static HttpConnection connect(URI uri, 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)
+ : new Address(uri, 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, 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), connectTimeout);
+ }
+
public void closeSocketAndStreams() {
IoUtils.closeQuietly(sslOutputStream);
IoUtils.closeQuietly(sslInputStream);
@@ -280,6 +323,10 @@
this.socketPort = proxySocketAddress.getPort();
}
+ public Proxy getProxy() {
+ return proxy;
+ }
+
@Override public boolean equals(Object other) {
if (other instanceof Address) {
Address that = (Address) other;
diff --git a/luni/src/main/java/libcore/net/http/HttpConnectionPool.java b/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
index 0fb5261..7b7f74c 100644
--- a/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
+++ b/luni/src/main/java/libcore/net/http/HttpConnectionPool.java
@@ -36,7 +36,7 @@
* are changed. This assumes that the applications that set these parameters do
* so before making HTTP connections, and that this class is initialized lazily.
*/
-public final class HttpConnectionPool {
+final class HttpConnectionPool {
public static final HttpConnectionPool INSTANCE = new HttpConnectionPool();
diff --git a/luni/src/main/java/libcore/net/http/HttpDate.java b/luni/src/main/java/libcore/net/http/HttpDate.java
new file mode 100644
index 0000000..a41cf81
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HttpDate.java
@@ -0,0 +1,91 @@
+/*
+ * 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.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Best-effort parser for HTTP dates.
+ */
+public final class HttpDate {
+
+ /**
+ * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such
+ * cookies are on the fast path.
+ */
+ private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT
+ = new ThreadLocal<DateFormat>() {
+ @Override protected DateFormat initialValue() {
+ DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+ rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return rfc1123;
+ }
+ };
+
+ /**
+ * If we fail to parse a date in a non-standard format, try each of these formats in sequence.
+ */
+ private static final String[] BROWSER_COMPATIBLE_DATE_FORMATS = new String[] {
+ /* This list comes from {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */
+ "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
+ "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
+ "EEE, dd-MMM-yyyy HH:mm:ss z",
+ "EEE, dd-MMM-yyyy HH-mm-ss z",
+ "EEE, dd MMM yy HH:mm:ss z",
+ "EEE dd-MMM-yyyy HH:mm:ss z",
+ "EEE dd MMM yyyy HH:mm:ss z",
+ "EEE dd-MMM-yyyy HH-mm-ss z",
+ "EEE dd-MMM-yy HH:mm:ss z",
+ "EEE dd MMM yy HH:mm:ss z",
+ "EEE,dd-MMM-yy HH:mm:ss z",
+ "EEE,dd-MMM-yyyy HH:mm:ss z",
+ "EEE, dd-MM-yyyy HH:mm:ss z",
+
+ /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
+ "EEE MMM d yyyy HH:mm:ss z",
+ };
+
+ /**
+ * Returns the date for {@code value}. Returns null if the value couldn't be
+ * parsed.
+ */
+ public static Date parse(String value) {
+ try {
+ return STANDARD_DATE_FORMAT.get().parse(value);
+ } catch (ParseException ignore) {
+ }
+ for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
+ try {
+ return new SimpleDateFormat(formatString, Locale.US).parse(value);
+ } catch (ParseException ignore) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the string for {@code value}.
+ */
+ public static String format(Date value) {
+ return STANDARD_DATE_FORMAT.get().format(value);
+ }
+}
diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java
new file mode 100644
index 0000000..d023cbc
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/HttpEngine.java
@@ -0,0 +1,852 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.Charsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+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 released to the pool when the
+ * last byte of the response is consumed. To prevent the connection from being
+ * released to the pool, use {@link #dontReleaseSocketToPool()}.
+ */
+public class HttpEngine {
+ private static final CacheResponse BAD_GATEWAY_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 502 Bad Gateway"));
+ // TODO: other required fields?
+ 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 RawHeaders rawRequestHeaders;
+
+ /** Null until a response is received from the network or the cache */
+ private RawHeaders rawResponseHeaders;
+
+ /*
+ * 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 responseHeadersToValidate;
+ private InputStream responseBodyToValidate;
+
+ /**
+ * True if the socket connection should be released to the connection pool
+ * when the response has been fully read.
+ */
+ private boolean dontReleaseSocketToPool;
+
+ /**
+ * @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 {
+ if (policy.getDoOutput()) {
+ if (method == GET) {
+ // they are requesting a stream to write to. This implies a POST method
+ method = POST;
+ } else if (method != PUT && method != POST) {
+ // If the request method is neither PUT or POST, then you're not writing
+ throw new ProtocolException(method + " does not support writing");
+ }
+ }
+
+ this.policy = policy;
+ this.method = method;
+ this.rawRequestHeaders = new RawHeaders(requestHeaders);
+ this.connection = connection;
+ this.requestBodyOut = requestBodyOut;
+
+ try {
+ uri = policy.getURL().toURILenient();
+ } catch (URISyntaxException e) {
+ throw new IOException(e);
+ }
+ }
+
+ public final void dontReleaseSocketToPool() {
+ this.dontReleaseSocketToPool = true;
+ }
+
+ /**
+ * 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();
+ RequestHeaders cacheRequestHeaders = new RequestHeaders(uri, rawRequestHeaders);
+ initResponseSource(cacheRequestHeaders);
+
+ /*
+ * 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 BAD_GATEWAY response instead.
+ */
+ if (cacheRequestHeaders.onlyIfCached && responseSource.requiresConnection()) {
+ if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+ this.responseHeadersToValidate = null;
+ IoUtils.closeQuietly(responseBodyToValidate);
+ this.responseBodyToValidate = null;
+ }
+ this.responseSource = ResponseSource.CACHE;
+ this.cacheResponse = BAD_GATEWAY_RESPONSE;
+ setResponse(RawHeaders.fromMultimap(cacheResponse.getHeaders()),
+ cacheResponse.getBody());
+ }
+
+ if (responseSource.requiresConnection()) {
+ sendSocketRequest();
+ } else {
+ // TODO: release 'connection' if it is non-null
+ }
+ }
+
+ /**
+ * Initialize the source for this response. It may be corrected later if the
+ * request headers forbids network use.
+ */
+ private void initResponseSource(RequestHeaders cacheRequestHeaders) throws IOException {
+ responseSource = ResponseSource.NETWORK;
+ if (!policy.getUseCaches() || responseCache == null) {
+ return;
+ }
+
+ CacheResponse candidate = responseCache.get(uri, method, rawRequestHeaders.toMultimap());
+ if (candidate == null || !acceptCacheResponseType(candidate)) {
+ return;
+ }
+ Map<String, List<String>> responseHeaders = candidate.getHeaders();
+ if (responseHeaders == null) {
+ return;
+ }
+ InputStream cacheBodyIn = candidate.getBody(); // must be closed
+ if (cacheBodyIn == null) {
+ return;
+ }
+
+ RawHeaders headers = RawHeaders.fromMultimap(responseHeaders);
+ ResponseHeaders cacheResponseHeaders = new ResponseHeaders(uri, headers);
+ long now = System.currentTimeMillis();
+ this.responseSource = cacheResponseHeaders.chooseResponseSource(now, cacheRequestHeaders);
+ if (responseSource == ResponseSource.CACHE) {
+ this.cacheResponse = candidate;
+ setResponse(headers, cacheBodyIn);
+ } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+ this.cacheResponse = candidate;
+ this.responseHeadersToValidate = cacheResponseHeaders;
+ this.responseBodyToValidate = cacheBodyIn;
+ } else if (responseSource == ResponseSource.NETWORK) {
+ IoUtils.closeQuietly(cacheBodyIn);
+ } 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 (policy.getDoOutput() && method != CONNECT) {
+ 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, 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 contentLength = -1;
+ String contentLengthString = rawRequestHeaders.get("Content-Length");
+ if (contentLengthString != null) {
+ contentLength = Integer.parseInt(contentLengthString);
+ }
+
+ String encoding = rawRequestHeaders.get("Transfer-Encoding");
+ int chunkLength = policy.getChunkLength();
+ if (chunkLength > 0 || "chunked".equalsIgnoreCase(encoding)) {
+ sendChunked = true;
+ contentLength = -1;
+ 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 (contentLength != -1) {
+ writeRequestHeaders(contentLength);
+ requestBodyOut = new RetryableOutputStream(contentLength);
+ } else {
+ requestBodyOut = new RetryableOutputStream();
+ }
+ }
+
+ /**
+ * @param body the response body, or null if it doesn't exist or isn't
+ * available.
+ */
+ private void setResponse(RawHeaders headers, InputStream body) throws IOException {
+ if (this.responseBodyIn != null) {
+ throw new IllegalStateException();
+ }
+ this.rawResponseHeaders = headers;
+ this.httpMinorVersion = rawResponseHeaders.getHttpMinorVersion();
+ if (body != null) {
+ initContentStream(body);
+ }
+ }
+
+ /**
+ * 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 rawResponseHeaders != null;
+ }
+
+ public final RawHeaders getRequestHeaders() {
+ return rawRequestHeaders;
+ }
+
+ public final RawHeaders getResponseHeaders() {
+ if (rawResponseHeaders == null) {
+ throw new IllegalStateException();
+ }
+ return rawResponseHeaders;
+ }
+
+ public final InputStream getResponseBody() {
+ if (rawResponseHeaders == null) {
+ throw new IllegalStateException();
+ }
+ return responseBodyIn;
+ }
+
+ public final CacheResponse getCacheResponse() {
+ if (rawResponseHeaders == null) {
+ throw new IllegalStateException();
+ }
+ return cacheResponse;
+ }
+
+ public final HttpConnection getConnection() {
+ return connection;
+ }
+
+ /**
+ * 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 {
+ // Are we caching at all?
+ if (!policy.getUseCaches() || responseCache == null) {
+ return;
+ }
+
+ // Should we cache this response for this request?
+ RequestHeaders requestCacheHeaders = new RequestHeaders(uri, rawRequestHeaders);
+ ResponseHeaders responseCacheHeaders = new ResponseHeaders(uri, rawResponseHeaders);
+ if (!responseCacheHeaders.isCacheable(requestCacheHeaders)) {
+ return;
+ }
+
+ // Offer this request to the cache.
+ cacheRequest = responseCache.put(uri, getHttpConnectionToCache());
+ }
+
+ protected HttpURLConnection getHttpConnectionToCache() {
+ return policy;
+ }
+
+ /**
+ * Releases this connection so that it may be either reused or closed.
+ */
+ public final void releaseSocket(boolean reuseSocket) {
+ // we cannot recycle sockets that have incomplete output.
+ if (requestBodyOut != null && !requestBodyOut.closed) {
+ reuseSocket = false;
+ }
+
+ // if the headers specify that the connection shouldn't be reused, don't reuse it
+ if (hasConnectionCloseHeaders()) {
+ reuseSocket = false;
+ }
+
+ /*
+ * Don't return the socket to the connection pool if this is an
+ * intermediate response; we're going to use it again right away.
+ */
+ if (dontReleaseSocketToPool && reuseSocket) {
+ return;
+ }
+
+ if (connection != null) {
+ if (reuseSocket) {
+ HttpConnectionPool.INSTANCE.recycle(connection);
+ } else {
+ connection.closeSocketAndStreams();
+ }
+ connection = null;
+ }
+
+ /*
+ * Ensure that no further I/O attempts from this instance make their way
+ * to the underlying connection (which may get recycled).
+ */
+ socketOut = null;
+ socketIn = null;
+ requestOut = null;
+ }
+
+ /**
+ * Consume the response body so the socket connection can be used for new
+ * message pairs.
+ */
+ public final void discardResponseBody() throws IOException {
+ if (responseBodyIn != null) {
+ if (!(responseBodyIn instanceof UnknownLengthHttpInputStream)) {
+ // skip the response so that the connection may be reused for the retry
+ Streams.skipAll(responseBodyIn);
+ }
+ responseBodyIn.close();
+ }
+ }
+
+ private void initContentStream(InputStream transferStream) throws IOException {
+ if (transparentGzip
+ && "gzip".equalsIgnoreCase(rawResponseHeaders.get("Content-Encoding"))) {
+ /*
+ * If the response was transparently gzipped, remove the gzip header field
+ * so clients don't double decompress. http://b/3009828
+ */
+ rawResponseHeaders.removeAll("Content-Encoding");
+ responseBodyIn = new GZIPInputStream(transferStream);
+ } else {
+ responseBodyIn = transferStream;
+ }
+ }
+
+ private InputStream getTransferStream() throws IOException {
+ if (!hasResponseBody()) {
+ return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
+ }
+
+ if ("chunked".equalsIgnoreCase(rawResponseHeaders.get("Transfer-Encoding"))) {
+ return new ChunkedInputStream(socketIn, cacheRequest, this);
+ }
+
+ String contentLength = rawResponseHeaders.get("Content-Length");
+ if (contentLength != null) {
+ try {
+ int length = Integer.parseInt(contentLength);
+ return new FixedLengthInputStream(socketIn, cacheRequest, this, length);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ /*
+ * 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);
+ }
+
+ /**
+ * Returns the characters up to but not including the next "\r\n", "\n", or
+ * the end of the stream, consuming the end of line delimiter.
+ */
+ static String readLine(InputStream is) throws IOException {
+ StringBuilder result = new StringBuilder(80);
+ while (true) {
+ int c = is.read();
+ if (c == -1 || c == '\n') {
+ break;
+ }
+
+ result.append((char) c);
+ }
+ int length = result.length();
+ if (length > 0 && result.charAt(length - 1) == '\r') {
+ result.setLength(length - 1);
+ }
+ return result.toString();
+ }
+
+ private void readResponseHeaders() throws IOException {
+ RawHeaders headers;
+ do {
+ headers = new RawHeaders();
+ headers.setStatusLine(readLine(socketIn).trim());
+ readHeaders(headers);
+ setResponse(headers, null);
+ } while (headers.getResponseCode() == HTTP_CONTINUE);
+ }
+
+ /**
+ * Returns true if the response must have a (possibly 0-length) body.
+ * See RFC 2616 section 4.3.
+ */
+ public final boolean hasResponseBody() {
+ int responseCode = rawResponseHeaders.getResponseCode();
+ if (method != HEAD
+ && 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.
+ */
+ String contentLength = rawResponseHeaders.get("Content-Length");
+ if (contentLength != null && Integer.parseInt(contentLength) > 0) {
+ return true;
+ }
+ if ("chunked".equalsIgnoreCase(rawResponseHeaders.get("Transfer-Encoding"))) {
+ 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(rawResponseHeaders);
+ }
+
+ private void readHeaders(RawHeaders headers) throws IOException {
+ // parse the result headers until the first blank line
+ String line;
+ while ((line = readLine(socketIn)).length() > 1) {
+ // Header parsing
+ int index = line.indexOf(":");
+ if (index == -1) {
+ headers.add("", line);
+ } else {
+ headers.add(line.substring(0, index), line.substring(index + 1));
+ }
+ }
+
+ 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 {
+ rawRequestHeaders.setStatusLine(getRequestLine());
+
+ int fixedContentLength = policy.getFixedContentLength();
+ if (fixedContentLength != -1) {
+ rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(fixedContentLength));
+ } else if (sendChunked) {
+ rawRequestHeaders.addIfAbsent("Transfer-Encoding", "chunked");
+ } else if (requestBodyOut instanceof RetryableOutputStream) {
+ int size = ((RetryableOutputStream) requestBodyOut).contentLength();
+ rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(size));
+ }
+
+ if (requestBodyOut != null) {
+ rawRequestHeaders.addIfAbsent("Content-Type", "application/x-www-form-urlencoded");
+ }
+
+ return rawRequestHeaders;
+ }
+
+ /**
+ * 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 {
+ rawRequestHeaders.setStatusLine(getRequestLine());
+
+ if (rawRequestHeaders.get("User-Agent") == null) {
+ rawRequestHeaders.add("User-Agent", getDefaultUserAgent());
+ }
+
+ if (rawRequestHeaders.get("Host") == null) {
+ rawRequestHeaders.add("Host", getOriginAddress(policy.getURL()));
+ }
+
+ if (httpMinorVersion > 0) {
+ rawRequestHeaders.addIfAbsent("Connection", "Keep-Alive");
+ }
+
+ if (rawRequestHeaders.get("Accept-Encoding") == null) {
+ transparentGzip = true;
+ rawRequestHeaders.set("Accept-Encoding", "gzip");
+ }
+
+ CookieHandler cookieHandler = CookieHandler.getDefault();
+ if (cookieHandler != null) {
+ Map<String, List<String>> allCookieHeaders
+ = cookieHandler.get(uri, rawRequestHeaders.toMultimap());
+ for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
+ String key = entry.getKey();
+ if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
+ rawRequestHeaders.addAll(key, entry.getValue());
+ }
+ }
+ }
+ }
+
+ 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.isEmpty()) {
+ 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();
+ }
+
+ protected final String getDefaultUserAgent() {
+ String agent = System.getProperty("http.agent");
+ return agent != null ? agent : ("Java" + System.getProperty("java.version"));
+ }
+
+ public final boolean hasConnectionCloseHeaders() {
+ return (rawResponseHeaders != null
+ && "close".equalsIgnoreCase(rawResponseHeaders.get("Connection")))
+ || ("close".equalsIgnoreCase(rawRequestHeaders.get("Connection")));
+ }
+
+ 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();
+ rawResponseHeaders.add(ResponseHeaders.SENT_MILLIS, Long.toString(sentRequestMillis));
+ rawResponseHeaders.add(ResponseHeaders.RECEIVED_MILLIS,
+ Long.toString(System.currentTimeMillis()));
+
+ if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
+ if (responseHeadersToValidate.validate(new ResponseHeaders(uri, rawResponseHeaders))) {
+ // discard the network response
+ discardResponseBody();
+
+ // use the cache response
+ setResponse(responseHeadersToValidate.headers, responseBodyToValidate);
+ responseBodyToValidate = null;
+ return;
+ } else {
+ IoUtils.closeQuietly(responseBodyToValidate);
+ responseBodyToValidate = null;
+ responseHeadersToValidate = null;
+ }
+ }
+
+ 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
index 976d49c..e168f42 100644
--- a/luni/src/main/java/libcore/net/http/HttpHandler.java
+++ b/luni/src/main/java/libcore/net/http/HttpHandler.java
@@ -23,60 +23,20 @@
import java.net.URLConnection;
import java.net.URLStreamHandler;
-/**
- * This is the handler that manages all transactions between the client and a
- * HTTP remote server.
- */
-public class HttpHandler extends URLStreamHandler {
+public final class HttpHandler extends URLStreamHandler {
- /**
- * Returns a connection to the HTTP server specified by this
- * <code>URL</code>.
- *
- * @param u
- * the URL to which the connection is pointing to
- * @return a connection to the resource pointed by this url.
- *
- * @throws IOException
- * if this handler fails to establish a connection
- */
- @Override
- protected URLConnection openConnection(URL u) throws IOException {
+ @Override protected URLConnection openConnection(URL u) throws IOException {
return new HttpURLConnectionImpl(u, getDefaultPort());
}
- /**
- * Returns a connection, which is established via the <code>proxy</code>,
- * to the HTTP server specified by this <code>URL</code>. If the
- * <code>proxy</code> is DIRECT type, the connection is made in normal
- * way.
- *
- * @param url
- * the URL which the connection is pointing to
- * @param proxy
- * the proxy which is used to make the connection
- * @return a connection to the resource pointed by this url.
- *
- * @throws IOException
- * if this handler fails to establish a connection.
- * @throws IllegalArgumentException
- * if any argument is null or the type of proxy is wrong.
- * @throws UnsupportedOperationException
- * if the protocol handler doesn't support this method.
- */
- @Override
- protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+ @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);
}
- /**
- * Return the default port.
- */
- @Override
- protected int getDefaultPort() {
+ @Override protected int getDefaultPort() {
return 80;
}
}
diff --git a/luni/src/main/java/libcore/net/http/HttpHeaders.java b/luni/src/main/java/libcore/net/http/HttpHeaders.java
deleted file mode 100644
index 8eca3b5..0000000
--- a/luni/src/main/java/libcore/net/http/HttpHeaders.java
+++ /dev/null
@@ -1,209 +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.TreeMap;
-
-/**
- * The HTTP status and header lines of a single HTTP message. This class
- * maintains the order of the header lines within the HTTP message.
- */
-public final class HttpHeaders implements Cloneable {
-
- private static final Comparator<String> HEADER_COMPARATOR = new Comparator<String>() {
- @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> alternatingKeysAndValues = new ArrayList<String>(20);
- private String statusLine;
-
- public HttpHeaders() {}
-
- public HttpHeaders(HttpHeaders copyFrom) {
- statusLine = copyFrom.statusLine;
- alternatingKeysAndValues.addAll(copyFrom.alternatingKeysAndValues);
- }
-
- public void setStatusLine(String statusLine) {
- this.statusLine = statusLine;
- }
-
- public String getStatusLine() {
- return statusLine;
- }
-
- /**
- * Add a field with the specified value.
- */
- public void add(String key, String value) {
- if (key == null) {
- throw new IllegalArgumentException("key == null");
- }
- if (value == null) {
- /*
- * Given null values, the RI sends a malformed header 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 '" + key + "' because its value is null");
- return;
- }
- alternatingKeysAndValues.add(key);
- alternatingKeysAndValues.add(value);
- }
-
- public void removeAll(String key) {
- for (int i = 0; i < alternatingKeysAndValues.size(); i += 2) {
- if (key.equalsIgnoreCase(alternatingKeysAndValues.get(i))) {
- alternatingKeysAndValues.remove(i); // key
- alternatingKeysAndValues.remove(i); // value
- }
- }
- }
-
- public void addAll(String key, List<String> headers) {
- for (String header : headers) {
- add(key, header);
- }
- }
-
- public void addIfAbsent(String key, String value) {
- if (get(key) == null) {
- add(key, 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 key, String value) {
- removeAll(key);
- add(key, value);
- }
-
- /**
- * Returns the number of header lines.
- */
- public int length() {
- return alternatingKeysAndValues.size() / 2;
- }
-
- /**
- * Returns the key at {@code position} or null if that is out of range.
- */
- public String getKey(int index) {
- int keyIndex = index * 2;
- if (keyIndex < 0 || keyIndex >= alternatingKeysAndValues.size()) {
- return null;
- }
- return alternatingKeysAndValues.get(keyIndex);
- }
-
- /**
- * 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 >= alternatingKeysAndValues.size()) {
- return null;
- }
- return alternatingKeysAndValues.get(valueIndex);
- }
-
- /**
- * Returns the last value corresponding to the specified key, or null.
- */
- public String get(String key) {
- for (int i = alternatingKeysAndValues.size() - 2; i >= 0; i -= 2) {
- if (key.equalsIgnoreCase(alternatingKeysAndValues.get(i))) {
- return alternatingKeysAndValues.get(i + 1);
- }
- }
- return null;
- }
-
- public String toHeaderString() {
- StringBuilder result = new StringBuilder(256);
- result.append(statusLine).append("\r\n");
- for (int i = 0; i < alternatingKeysAndValues.size(); i += 2) {
- result.append(alternatingKeysAndValues.get(i)).append(": ")
- .append(alternatingKeysAndValues.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>>(HEADER_COMPARATOR);
- for (int i = 0; i < alternatingKeysAndValues.size(); i += 2) {
- String key = alternatingKeysAndValues.get(i);
- String value = alternatingKeysAndValues.get(i + 1);
-
- List<String> allValues = new ArrayList<String>();
- List<String> otherValues = result.get(key);
- if (otherValues != null) {
- allValues.addAll(otherValues);
- }
- allValues.add(value);
- result.put(key, Collections.unmodifiableList(allValues));
- }
- if (statusLine != null) {
- result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine)));
- }
- return Collections.unmodifiableMap(result);
- }
-
- /**
- * Creates a header from the given map of fields to values. If present, the
- * null key's last element will be used to set the status line.
- */
- public static HttpHeaders fromMultimap(Map<String, List<String>> map) {
- HttpHeaders result = new HttpHeaders();
- for (Entry<String, List<String>> entry : map.entrySet()) {
- String key = entry.getKey();
- List<String> values = entry.getValue();
- if (key != null) {
- result.addAll(key, values);
- } else if (!values.isEmpty()) {
- result.setStatusLine(values.get(values.size() - 1));
- }
- }
- return result;
- }
-}
diff --git a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
index 3bf2638..95b37fd 100644
--- a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java
@@ -17,15 +17,11 @@
package libcore.net.http;
-import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.CookieHandler;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
@@ -33,486 +29,150 @@
import java.net.PasswordAuthentication;
import java.net.ProtocolException;
import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.ResponseCache;
import java.net.SocketPermission;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charsets;
import java.security.Permission;
-import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.TimeZone;
-import java.util.zip.GZIPInputStream;
-import libcore.io.Streams;
import org.apache.harmony.luni.util.Base64;
/**
- * This subclass extends <code>HttpURLConnection</code> which in turns extends
- * <code>URLConnection</code> This is the actual class that "does the work",
- * such as connecting, sending request and getting the content from the remote
- * server.
+ * 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
- * headers, 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.
+ * 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.
*/
public class HttpURLConnectionImpl extends HttpURLConnection {
- 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;
-
- /**
- * The subset of HTTP methods that the user may select via {@link #setRequestMethod}.
- */
- public static String PERMITTED_USER_METHODS[] = {
- OPTIONS,
- GET,
- HEAD,
- POST,
- PUT,
- DELETE,
- TRACE
- // Note: we don't allow users to specify "CONNECT"
- };
-
- public static final int DEFAULT_CHUNK_LENGTH = 1024;
-
- /**
- * The maximum number of bytes to buffer when sending a header and a request
- * body. When the header 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;
private final int defaultPort;
- /**
- * 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.
- */
- private int httpVersion = 1; // Assume HTTP/1.1
-
- protected HttpConnection connection;
- private InputStream socketIn;
- private OutputStream socketOut;
-
- /**
- * This stream buffers the request header 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;
-
- protected InputStream responseBodyIn;
-
- private ResponseCache responseCache;
-
- protected CacheResponse cacheResponse;
-
- private CacheRequest cacheRequest;
-
- private boolean hasTriedCache;
-
- private boolean sentRequestHeaders;
-
- /**
- * True if this client added an "Accept-Encoding: gzip" header and is
- * therefore responsible for also decompressing the transfer stream.
- */
- private boolean transparentGzip = false;
-
- boolean sendChunked;
-
- // proxy which is used to make the connection.
private Proxy proxy;
- // the destination URI
- private URI uri;
+ // TODO: should these be set by URLConnection.setDefaultRequestProperty ?
+ private static RawHeaders defaultRequestHeaders = new RawHeaders();
- private static HttpHeaders defaultRequestHeader = new HttpHeaders();
-
- private final HttpHeaders requestHeader;
-
- /** Null until a response is received from the network or the cache */
- private HttpHeaders responseHeader;
+ protected RawHeaders rawRequestHeaders = new RawHeaders(defaultRequestHeaders);
private int redirectionCount;
- /**
- * Intermediate responses are always followed by another request for the
- * same content, possibly from a different URL or with different headers.
- */
- protected boolean intermediateResponse = false;
+ protected IOException httpEngineFailure;
+ protected HttpEngine httpEngine;
- /**
- * Creates an instance of the <code>HttpURLConnection</code>
- *
- * @param url
- * URL The URL this connection is connecting
- * @param port
- * int The default connection port
- */
protected HttpURLConnectionImpl(URL url, int port) {
super(url);
defaultPort = port;
- requestHeader = new HttpHeaders(defaultRequestHeader);
- responseCache = ResponseCache.getDefault();
}
- /**
- * Creates an instance of the <code>HttpURLConnection</code>
- *
- * @param url
- * URL The URL this connection is connecting
- * @param port
- * int The default connection port
- * @param proxy
- * Proxy The proxy which is used to make the connection
- */
protected HttpURLConnectionImpl(URL url, int port, Proxy proxy) {
this(url, port);
this.proxy = proxy;
}
@Override public final void connect() throws IOException {
- if (connected) {
- return;
- }
- makeConnection();
- }
-
- /**
- * Internal method to open a connection to the server. Unlike connect(),
- * this method may be called multiple times for a single response. This may
- * be necessary when following redirects.
- *
- * <p>Request parameters may not be changed after this method has been
- * called.
- */
- protected void makeConnection() throws IOException {
- connected = true;
-
- if (connection != null || responseBodyIn != null) {
- return;
- }
-
+ initHttpEngine();
try {
- uri = url.toURILenient();
- } catch (URISyntaxException e) {
- throw new IOException(e);
+ httpEngine.sendRequest();
+ } catch (IOException e) {
+ httpEngineFailure = e;
+ throw e;
}
-
- if (getFromCache()) {
- return;
- }
-
- // try to determine: to use the proxy or not
- if (proxy != null) {
- // try to make the connection to the proxy
- // specified in constructor.
- // IOException will be thrown in the case of failure
- connection = getHttpConnection(proxy);
- } else {
- // Use system-wide ProxySelect to select proxy list,
- // then try to connect via elements in the proxy list.
- 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
- continue;
- }
- try {
- connection = getHttpConnection(selectedProxy);
- proxy = selectedProxy;
- break; // connected
- } catch (IOException e) {
- // failed to connect, tell it to the selector
- selector.connectFailed(uri, selectedProxy.address(), e);
- }
- }
- }
- if (connection == null) {
- // make direct connection
- connection = getHttpConnection(null);
- }
- }
- connection.setSoTimeout(getReadTimeout());
- setUpTransportIO(connection);
- }
-
- /**
- * Returns connected socket to be used for this HTTP connection.
- */
- private HttpConnection getHttpConnection(Proxy proxy) throws IOException {
- HttpConnection.Address address;
- if (proxy == null || proxy.type() == Proxy.Type.DIRECT) {
- this.proxy = null; // not using proxy
- address = new HttpConnection.Address(uri);
- } else {
- address = new HttpConnection.Address(uri, proxy, requiresTunnel());
- }
- return HttpConnectionPool.INSTANCE.get(address, getConnectTimeout());
- }
-
- /**
- * Sets up the data streams used to send requests and read responses.
- */
- protected void setUpTransportIO(HttpConnection connection) throws IOException {
- socketOut = connection.getOutputStream();
- requestOut = socketOut;
- socketIn = connection.getInputStream();
- }
-
- /**
- * Attempts to load the response headers and body from the response cache.
- * Returns true if the request was satisfied by the cache.
- */
- private boolean getFromCache() throws IOException {
- if (!useCaches || responseCache == null || hasTriedCache) {
- return false;
- }
-
- hasTriedCache = true;
- CacheResponse candidate = responseCache.get(uri, method, requestHeader.toMultimap());
- if (!acceptCacheResponse(candidate)) {
- return false;
- }
- Map<String, List<String>> headersMap = candidate.getHeaders();
- if (headersMap == null) {
- return false;
- }
- InputStream cacheBodyIn = candidate.getBody();
- if (cacheBodyIn == null) {
- return false;
- }
-
- cacheResponse = candidate;
- responseHeader = HttpHeaders.fromMultimap(headersMap);
- parseStatusLine();
- responseBodyIn = cacheBodyIn;
- return true;
- }
-
- /**
- * Returns true if {@code cacheResponse} is sufficient to forgo a network
- * request. HTTPS connections require secure cache responses.
- */
- protected boolean acceptCacheResponse(CacheResponse cacheResponse) {
- return cacheResponse != null;
- }
-
- private void maybeCache() throws IOException {
- // Are we caching at all?
- if (!useCaches || responseCache == null) {
- return;
- }
- // Should we cache this particular response code?
- // TODO: cache response code 300 HTTP_MULT_CHOICE ?
- if (responseCode != HTTP_OK && responseCode != HTTP_NOT_AUTHORITATIVE &&
- responseCode != HTTP_PARTIAL && responseCode != HTTP_MOVED_PERM &&
- responseCode != HTTP_GONE) {
- return;
- }
- // Offer this request to the cache.
- cacheRequest = responseCache.put(uri, getConnectionForCaching());
}
/**
* Close the socket connection to the remote origin server or proxy.
*/
@Override public final void disconnect() {
- releaseSocket(false);
- }
-
- /**
- * Releases this connection so that it may be either reused or closed.
- */
- protected final void releaseSocket(boolean reuseSocket) {
- // we cannot recycle sockets that have incomplete output.
- if (requestBodyOut != null && !requestBodyOut.closed) {
- reuseSocket = false;
- }
-
- // if the headers specify that the connection shouldn't be reused, don't reuse it
- if (hasConnectionCloseHeader()) {
- reuseSocket = false;
- }
-
- /*
- * Don't return the socket to the connection pool if this is an
- * intermediate response; we're going to use it again right away.
- */
- if (intermediateResponse && reuseSocket) {
- return;
- }
-
- if (connection != null) {
- if (reuseSocket) {
- HttpConnectionPool.INSTANCE.recycle(connection);
- } else {
- connection.closeSocketAndStreams();
- }
- connection = null;
- }
-
- /*
- * Ensure that no further I/O attempts from this instance make their way
- * to the underlying connection (which may get recycled).
- */
- socketOut = null;
- socketIn = null;
- requestOut = null;
- }
-
- /**
- * Discard all state initialized from the HTTP response including response
- * code, message, headers and body.
- */
- protected final void discardIntermediateResponse() throws IOException {
- boolean oldIntermediateResponse = intermediateResponse;
- intermediateResponse = true;
- try {
- if (responseBodyIn != null) {
- if (!(responseBodyIn instanceof UnknownLengthHttpInputStream)) {
- // skip the response so that the connection may be reused for the retry
- Streams.skipAll(responseBodyIn);
- }
- responseBodyIn.close();
- responseBodyIn = null;
- }
- sentRequestHeaders = false;
- responseHeader = null;
- responseCode = -1;
- responseMessage = null;
- cacheRequest = null;
- uri = null;
- cacheResponse = null;
- hasTriedCache = false;
- } finally {
- intermediateResponse = oldIntermediateResponse;
+ // TODO: what happens if they call disconnect() before connect?
+ if (httpEngine != null) {
+ httpEngine.releaseSocket(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.
- * <p>
- * If the content type is not what stated above,
- * <code>FileNotFoundException</code> is thrown.
- *
- * @return InputStream the error input stream returned by the server.
*/
- @Override
- public final InputStream getErrorStream() {
- if (connected && method != HEAD && responseCode >= HTTP_BAD_REQUEST) {
- return responseBodyIn;
+ @Override public final InputStream getErrorStream() {
+ try {
+ HttpEngine response = getResponse();
+ if (response.hasResponseBody()
+ && response.getResponseHeaders().getResponseCode() >= HTTP_BAD_REQUEST) {
+ return response.getResponseBody();
+ }
+ return null;
+ } catch (IOException e) {
+ return null;
}
- 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) {
+ @Override public final String getHeaderField(int position) {
try {
- retrieveResponse();
- } catch (IOException ignored) {
+ return getResponse().getResponseHeaders().getValue(position);
+ } catch (IOException e) {
+ return null;
}
- return responseHeader != null ? responseHeader.getValue(position) : null;
}
/**
- * Returns the value of the field corresponding to the <code>key</code>
- * Returns <code>null</code> if there is no such field.
- *
- * If there are multiple fields with that key, the last field value is
- * returned.
- *
- * @return java.lang.String The value of the header field
- * @param key
- * java.lang.String the name of the header field
- *
- * @see #getHeaderField(int)
- * @see #getHeaderFieldKey
+ * 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 key) {
+ @Override public final String getHeaderField(String fieldName) {
try {
- retrieveResponse();
- } catch (IOException ignored) {
- }
- if (responseHeader == null) {
+ RawHeaders responseHeaders = getResponse().getResponseHeaders();
+ return fieldName == null
+ ? responseHeaders.getStatusLine()
+ : responseHeaders.get(fieldName);
+ } catch (IOException e) {
return null;
}
- return key == null ? responseHeader.getStatusLine() : responseHeader.get(key);
}
- @Override
- public final String getHeaderFieldKey(int position) {
+ @Override public final String getHeaderFieldKey(int position) {
try {
- retrieveResponse();
- } catch (IOException ignored) {
+ return getResponse().getResponseHeaders().getFieldName(position);
+ } catch (IOException e) {
+ return null;
}
- return responseHeader != null ? responseHeader.getKey(position) : null;
}
- @Override
- public final Map<String, List<String>> getHeaderFields() {
+ @Override public final Map<String, List<String>> getHeaderFields() {
try {
- retrieveResponse();
- } catch (IOException ignored) {
+ return getResponse().getResponseHeaders().toMultimap();
+ } catch (IOException e) {
+ return null;
}
- return responseHeader != null ? responseHeader.toMultimap() : null;
}
- @Override
- public final Map<String, List<String>> getRequestProperties() {
+ @Override public final Map<String, List<String>> getRequestProperties() {
if (connected) {
throw new IllegalStateException(
"Cannot access request header fields after connection is set");
}
- return requestHeader.toMultimap();
+ return rawRequestHeaders.toMultimap();
}
- @Override
- public final InputStream getInputStream() throws IOException {
+ @Override public final InputStream getInputStream() throws IOException {
if (!doInput) {
throw new ProtocolException("This protocol does not support input");
}
- retrieveResponse();
+ HttpEngine response = getResponse();
/*
* if the requested file does not exist, throw an exception formerly the
@@ -520,495 +180,70 @@
* text/html this has changed to return FileNotFoundException for all
* file types
*/
- if (responseCode >= HTTP_BAD_REQUEST) {
+ if (getResponseCode() >= HTTP_BAD_REQUEST) {
throw new FileNotFoundException(url.toString());
}
- if (responseBodyIn == null) {
- throw new IOException("No response body exists; responseCode=" + responseCode);
- }
-
- return responseBodyIn;
- }
-
- private void initContentStream() throws IOException {
- InputStream transferStream = getTransferStream();
- if (transparentGzip && "gzip".equalsIgnoreCase(responseHeader.get("Content-Encoding"))) {
- /*
- * If the response was transparently gzipped, remove the gzip header
- * so clients don't double decompress. http://b/3009828
- */
- responseHeader.removeAll("Content-Encoding");
- responseBodyIn = new GZIPInputStream(transferStream);
- } else {
- responseBodyIn = transferStream;
- }
- }
-
- private InputStream getTransferStream() throws IOException {
- if (!hasResponseBody()) {
- return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
- }
-
- if ("chunked".equalsIgnoreCase(responseHeader.get("Transfer-Encoding"))) {
- return new ChunkedInputStream(socketIn, cacheRequest, this);
- }
-
- String contentLength = responseHeader.get("Content-Length");
- if (contentLength != null) {
- try {
- int length = Integer.parseInt(contentLength);
- return new FixedLengthInputStream(socketIn, cacheRequest, this, length);
- } catch (NumberFormatException ignored) {
- }
- }
-
- /*
- * 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);
- }
-
- @Override
- public final OutputStream getOutputStream() throws IOException {
- if (!doOutput) {
- throw new ProtocolException("Does not support output");
- }
-
- // you can't write after you read
- if (sentRequestHeaders) {
- // TODO: just return 'requestBodyOut' if that's non-null?
- throw new ProtocolException(
- "OutputStream unavailable because request headers have already been sent!");
- }
-
- if (requestBodyOut != null) {
- return requestBodyOut;
- }
-
- // they are requesting a stream to write to. This implies a POST method
- if (method == GET) {
- method = POST;
- }
-
- // If the request method is neither PUT or POST, then you're not writing
- if (method != PUT && method != POST) {
- throw new ProtocolException(method + " does not support writing");
- }
-
- int contentLength = -1;
- String contentLengthString = requestHeader.get("Content-Length");
- if (contentLengthString != null) {
- contentLength = Integer.parseInt(contentLengthString);
- }
-
- String encoding = requestHeader.get("Transfer-Encoding");
- if (chunkLength > 0 || "chunked".equalsIgnoreCase(encoding)) {
- sendChunked = true;
- contentLength = -1;
- if (chunkLength == -1) {
- chunkLength = DEFAULT_CHUNK_LENGTH;
- }
- }
-
- connect();
-
- if (socketOut == null) {
- // TODO: what should we do if a cached response exists?
- throw new IOException("No socket to write to; was a POST cached?");
- }
-
- if (httpVersion == 0) {
- sendChunked = false;
- }
-
- if (fixedContentLength != -1) {
- writeRequestHeaders(fixedContentLength);
- requestBodyOut = new FixedLengthOutputStream(requestOut, fixedContentLength);
- } else if (sendChunked) {
- writeRequestHeaders(-1);
- requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength);
- } else if (contentLength != -1) {
- writeRequestHeaders(contentLength);
- requestBodyOut = new RetryableOutputStream(contentLength);
- } else {
- requestBodyOut = new RetryableOutputStream();
- }
- return requestBodyOut;
- }
-
- @Override
- public final Permission getPermission() throws IOException {
- String connectToAddress = getConnectToHost() + ":" + getConnectToPort();
- return new SocketPermission(connectToAddress, "connect, resolve");
- }
-
- @Override
- public final String getRequestProperty(String field) {
- if (field == null) {
- return null;
- }
- return requestHeader.get(field);
- }
-
- /**
- * Returns the characters up to but not including the next "\r\n", "\n", or
- * the end of the stream, consuming the end of line delimiter.
- */
- static String readLine(InputStream is) throws IOException {
- StringBuilder result = new StringBuilder(80);
- while (true) {
- int c = is.read();
- if (c == -1 || c == '\n') {
- break;
- }
-
- result.append((char) c);
- }
- int length = result.length();
- if (length > 0 && result.charAt(length - 1) == '\r') {
- result.setLength(length - 1);
- }
- return result.toString();
- }
-
- protected String requestString() {
- if (usingProxy()) {
- return url.toString();
- }
- String file = url.getFile();
- if (file == null || file.length() == 0) {
- file = "/";
- }
- return file;
- }
-
- private void readResponseHeaders() throws IOException {
- do {
- responseHeader = new HttpHeaders();
- responseHeader.setStatusLine(readLine(socketIn).trim());
- readHeaders();
- parseStatusLine();
- } while (responseCode == HTTP_CONTINUE);
- }
-
- /**
- * Returns true if the response must have a (possibly 0-length) body.
- * See RFC 2616 section 4.3.
- */
- private boolean hasResponseBody() {
- if (method != HEAD
- && method != CONNECT
- && (responseCode < HTTP_CONTINUE || responseCode >= 200)
- && responseCode != HTTP_NO_CONTENT
- && responseCode != 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.
- */
- String contentLength = responseHeader.get("Content-Length");
- if (contentLength != null && Integer.parseInt(contentLength) > 0) {
- return true;
- }
- if ("chunked".equalsIgnoreCase(responseHeader.get("Transfer-Encoding"))) {
- return true;
- }
-
- return false;
- }
-
- @Override
- public final int getResponseCode() throws IOException {
- retrieveResponse();
- return responseCode;
- }
-
- private void parseStatusLine() {
- httpVersion = 1;
- responseCode = -1;
- responseMessage = null;
-
- // Status line sample: "HTTP/1.0 200 OK"
- String response = responseHeader.getStatusLine();
- if (response == null || !response.startsWith("HTTP/")) {
- return;
- }
- response = response.trim();
- int mark = response.indexOf(" ") + 1;
- if (mark == 0) {
- return;
- }
- if (response.charAt(mark - 2) != '1') {
- httpVersion = 0;
- }
- int last = mark + 3;
- if (last > response.length()) {
- last = response.length();
- }
- responseCode = Integer.parseInt(response.substring(mark, last));
- if (last + 1 <= response.length()) {
- responseMessage = response.substring(last + 1);
- }
- }
-
- void readHeaders() throws IOException {
- // parse the result headers until the first blank line
- String line;
- while ((line = readLine(socketIn)).length() > 1) {
- // Header parsing
- int index = line.indexOf(":");
- if (index == -1) {
- responseHeader.add("", line.trim());
- } else {
- responseHeader.add(line.substring(0, index), line.substring(index + 1).trim());
- }
- }
-
- CookieHandler cookieHandler = CookieHandler.getDefault();
- if (cookieHandler != null) {
- cookieHandler.put(uri, responseHeader.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 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 {
- byte[] headerBytes = prepareRequestHeaders().toHeaderString().getBytes(Charsets.ISO_8859_1);
-
- if (contentLength != -1
- && headerBytes.length + contentLength <= MAX_REQUEST_BUFFER_LENGTH) {
- requestOut = new BufferedOutputStream(socketOut, headerBytes.length + contentLength);
- }
-
- requestOut.write(headerBytes);
- sentRequestHeaders = true;
- }
-
- /**
- * Populates requestHeader with the HTTP headers to be sent. Header values are
- * derived from the request itself and the cookie manager.
- *
- * <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 HttpHeaders prepareRequestHeaders() throws IOException {
- /*
- * 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.
- */
- if (method == CONNECT) {
- HttpHeaders proxyHeader = new HttpHeaders();
- proxyHeader.setStatusLine(getStatusLine());
-
- // always set Host and User-Agent
- String host = requestHeader.get("Host");
- if (host == null) {
- host = getOriginAddress(url);
- }
- proxyHeader.set("Host", host);
-
- String userAgent = requestHeader.get("User-Agent");
- if (userAgent == null) {
- userAgent = getDefaultUserAgent();
- }
- proxyHeader.set("User-Agent", userAgent);
-
- // copy over the Proxy-Authorization header if it exists
- String proxyAuthorization = requestHeader.get("Proxy-Authorization");
- if (proxyAuthorization != null) {
- proxyHeader.set("Proxy-Authorization", proxyAuthorization);
- }
-
- // Always set the Proxy-Connection to Keep-Alive for the benefit of
- // HTTP/1.0 proxies like Squid.
- proxyHeader.set("Proxy-Connection", "Keep-Alive");
- return proxyHeader;
- }
-
- requestHeader.setStatusLine(getStatusLine());
-
- if (requestHeader.get("User-Agent") == null) {
- requestHeader.add("User-Agent", getDefaultUserAgent());
- }
-
- if (requestHeader.get("Host") == null) {
- requestHeader.add("Host", getOriginAddress(url));
- }
-
- if (httpVersion > 0) {
- requestHeader.addIfAbsent("Connection", "Keep-Alive");
- }
-
- if (fixedContentLength != -1) {
- requestHeader.addIfAbsent("Content-Length", Integer.toString(fixedContentLength));
- } else if (sendChunked) {
- requestHeader.addIfAbsent("Transfer-Encoding", "chunked");
- } else if (requestBodyOut instanceof RetryableOutputStream) {
- int size = ((RetryableOutputStream) requestBodyOut).contentLength();
- requestHeader.addIfAbsent("Content-Length", Integer.toString(size));
- }
-
- if (requestBodyOut != null) {
- requestHeader.addIfAbsent("Content-Type", "application/x-www-form-urlencoded");
- }
-
- if (requestHeader.get("Accept-Encoding") == null) {
- transparentGzip = true;
- requestHeader.set("Accept-Encoding", "gzip");
- }
-
- CookieHandler cookieHandler = CookieHandler.getDefault();
- if (cookieHandler != null) {
- Map<String, List<String>> allCookieHeaders
- = cookieHandler.get(uri, requestHeader.toMultimap());
- for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
- String key = entry.getKey();
- if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
- requestHeader.addAll(key, entry.getValue());
- }
- }
- }
-
- return requestHeader;
- }
-
- private String getStatusLine() {
- String protocol = (httpVersion == 0) ? "HTTP/1.0" : "HTTP/1.1";
- return method + " " + requestString() + " " + protocol;
- }
-
- private String getDefaultUserAgent() {
- String agent = System.getProperty("http.agent");
- return agent != null ? agent : ("Java" + System.getProperty("java.version"));
- }
-
- private boolean hasConnectionCloseHeader() {
- return (responseHeader != null
- && "close".equalsIgnoreCase(responseHeader.get("Connection")))
- || (requestHeader != null
- && "close".equalsIgnoreCase(requestHeader.get("Connection")));
- }
-
- private String getOriginAddress(URL url) {
- int port = url.getPort();
- String result = url.getHost();
- if (port > 0 && port != defaultPort) {
- result = result + ":" + port;
+ InputStream result = response.getResponseBody();
+ if (result == null) {
+ throw new IOException("No response body exists; responseCode=" + getResponseCode());
}
return result;
}
- /**
- * A slightly different implementation from this parent's
- * <code>setIfModifiedSince()</code> Since this HTTP impl supports
- * IfModifiedSince as one of the header field, the request header is updated
- * with the new value.
- *
- *
- * @param newValue
- * the number of millisecond since epoch
- *
- * @throws IllegalStateException
- * if already connected.
- */
- @Override
- public final void setIfModifiedSince(long newValue) {
- super.setIfModifiedSince(newValue);
- // convert from millisecond since epoch to date string
- SimpleDateFormat sdf = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
- sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
- String date = sdf.format(new Date(newValue));
- requestHeader.add("If-Modified-Since", date);
+ @Override public final OutputStream getOutputStream() throws IOException {
+ connect();
+ return httpEngine.getRequestBody();
}
- @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();
- }
- requestHeader.set(field, newValue);
+ @Override public final Permission getPermission() throws IOException {
+ String connectToAddress = getConnectToHost() + ":" + getConnectToPort();
+ return new SocketPermission(connectToAddress, "connect, resolve");
}
- @Override
- public final void addRequestProperty(String field, String value) {
- if (connected) {
- throw new IllegalStateException("Cannot set request property after connection is made");
- }
- if (field == null) {
- throw new NullPointerException();
- }
- requestHeader.add(field, value);
- }
-
- /**
- * Returns the target port of the socket connection; either a port of the
- * origin server or an intermediate proxy.
- */
- private int getConnectToPort() {
- int hostPort = usingProxy()
- ? ((InetSocketAddress) proxy.address()).getPort()
- : url.getPort();
- return hostPort < 0 ? defaultPort : hostPort;
- }
-
- /**
- * Returns the target address of the socket connection; either the address
- * of the origin server or an intermediate proxy.
- */
- private InetAddress getConnectToInetAddress() throws IOException {
- return usingProxy()
- ? ((InetSocketAddress) proxy.address()).getAddress()
- : InetAddress.getByName(url.getHost());
- }
-
- /**
- * Returns the target host name of the socket connection; either the host
- * name of the origin server or an intermediate proxy.
- */
private String getConnectToHost() {
return usingProxy()
? ((InetSocketAddress) proxy.address()).getHostName()
- : url.getHost();
+ : getURL().getHost();
}
- @Override public final boolean usingProxy() {
- return (proxy != null && proxy.type() != Proxy.Type.DIRECT);
+ private int getConnectToPort() {
+ int hostPort = usingProxy()
+ ? ((InetSocketAddress) proxy.address()).getPort()
+ : getURL().getPort();
+ return hostPort < 0 ? getDefaultPort() : hostPort;
}
- protected boolean requiresTunnel() {
- return false;
+ @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 {
+ httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
+ } catch (IOException e) {
+ httpEngineFailure = e;
+ throw e;
+ }
}
/**
- * Returns this connection in a form suitable for use by the response cache.
- * If this returns an HTTPS connection, only secure cache responses will be
- * honored.
+ * Create a new HTTP engine. This hook method is non-final so it can be
+ * overridden by HttpsURLConnectionImpl.
*/
- protected HttpURLConnection getConnectionForCaching() {
- return this;
+ protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
+ HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
+ return new HttpEngine(this, method, requestHeaders, connection, requestBody);
}
/**
@@ -1016,72 +251,56 @@
* many HTTP requests in the process in order to cope with redirects and
* authentication.
*/
- protected final void retrieveResponse() throws IOException {
- if (responseHeader != null) {
- return;
+ private HttpEngine getResponse() throws IOException {
+ initHttpEngine();
+
+ if (httpEngine.hasResponse()) {
+ return httpEngine;
}
- redirectionCount = 0;
- while (true) {
- makeConnection();
+ try {
+ while (true) {
+ httpEngine.sendRequest();
+ httpEngine.readResponse();
- if (cacheResponse == null) {
- getFromNetwork();
+ Retry retry = processResponseHeaders();
+ if (retry == Retry.NONE) {
+ break;
+ }
+
+ /*
+ * The first request was insufficient. Prepare for another...
+ */
+ OutputStream requestBody = httpEngine.getRequestBody();
+ if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
+ throw new HttpRetryException("Cannot retry streamed HTTP body",
+ httpEngine.getResponseHeaders().getResponseCode());
+ }
+
+ if (retry == Retry.SAME_CONNECTION && httpEngine.hasConnectionCloseHeaders()) {
+ retry = Retry.NEW_CONNECTION;
+ }
+
+ HttpConnection connection = null;
+ if (retry == Retry.NEW_CONNECTION) {
+ httpEngine.discardResponseBody();
+ httpEngine.releaseSocket(true);
+ } else {
+ httpEngine.dontReleaseSocketToPool();
+ httpEngine.discardResponseBody();
+ connection = httpEngine.getConnection();
+ }
+
+ httpEngine = newHttpEngine(method, rawRequestHeaders, connection,
+ (RetryableOutputStream) requestBody);
}
-
- Retry retry = processResponseHeaders();
-
- if (retry == Retry.NONE) {
- return;
- }
-
- /*
- * The first request wasn't sufficient. Prepare for another...
- */
-
- if (requestBodyOut != null && !(requestBodyOut instanceof RetryableOutputStream)) {
- throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
- }
-
- if (retry == Retry.SAME_CONNECTION && hasConnectionCloseHeader()) {
- retry = Retry.NEW_CONNECTION;
- }
-
- discardIntermediateResponse();
-
- if (retry == Retry.NEW_CONNECTION) {
- releaseSocket(true);
- }
+ return httpEngine;
+ } catch (IOException e) {
+ httpEngineFailure = e;
+ throw e;
}
}
- private void getFromNetwork() throws IOException {
- if (!sentRequestHeaders) {
- 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();
-
- if (hasResponseBody()) {
- maybeCache(); // reentrant. this calls into user code which may call back into this!
- }
-
- initContentStream();
- }
-
enum Retry {
NONE,
SAME_CONNECTION,
@@ -1094,72 +313,89 @@
* prepare for a follow up request.
*/
private Retry processResponseHeaders() throws IOException {
+ RawHeaders responseHeaders = httpEngine.getResponseHeaders();
+ int responseCode = responseHeaders.getResponseCode();
switch (responseCode) {
- case HTTP_PROXY_AUTH: // proxy authorization failed ?
- if (!usingProxy()) {
- throw new IOException(
- "Received HTTP_PROXY_AUTH (407) code while not using proxy");
- }
- return processAuthHeader("Proxy-Authenticate", "Proxy-Authorization");
+ case HTTP_PROXY_AUTH: // proxy authorization failed ?
+ if (!usingProxy()) {
+ throw new IOException(
+ "Received HTTP_PROXY_AUTH (407) code while not using proxy");
+ }
+ return processAuthHeader("Proxy-Authenticate", "Proxy-Authorization");
- case HTTP_UNAUTHORIZED: // HTTP authorization failed ?
- return processAuthHeader("WWW-Authenticate", "Authorization");
+ case HTTP_UNAUTHORIZED: // HTTP authorization failed ?
+ return processAuthHeader("WWW-Authenticate", "Authorization");
- case HTTP_MULT_CHOICE:
- case HTTP_MOVED_PERM:
- case HTTP_MOVED_TEMP:
- case HTTP_SEE_OTHER:
- case HTTP_USE_PROXY:
- if (!getInstanceFollowRedirects()) {
- return Retry.NONE;
- }
- if (requestBodyOut != null) {
- // TODO: follow redirects for retryable output streams...
- return Retry.NONE;
- }
- if (++redirectionCount > MAX_REDIRECTS) {
- throw new ProtocolException("Too many redirects");
- }
- String location = getHeaderField("Location");
- if (location == null) {
- return Retry.NONE;
- }
- if (responseCode == HTTP_USE_PROXY) {
- int start = 0;
- if (location.startsWith(url.getProtocol() + ':')) {
- start = url.getProtocol().length() + 1;
- }
- if (location.startsWith("//", start)) {
- start += 2;
- }
- setProxy(location.substring(start));
- return Retry.NEW_CONNECTION;
- }
- 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 {
- // TODO: strip cookies?
- requestHeader.removeAll("Host");
- return Retry.NEW_CONNECTION;
- }
-
- default:
+ case HTTP_MULT_CHOICE:
+ case HTTP_MOVED_PERM:
+ case HTTP_MOVED_TEMP:
+ case HTTP_SEE_OTHER:
+ case HTTP_USE_PROXY:
+ if (!getInstanceFollowRedirects()) {
return Retry.NONE;
+ }
+ if (httpEngine.getRequestBody() != null) {
+ // TODO: follow redirects for retryable output streams...
+ return Retry.NONE;
+ }
+ if (++redirectionCount > HttpEngine.MAX_REDIRECTS) {
+ throw new ProtocolException("Too many redirects");
+ }
+ String location = responseHeaders.get("Location");
+ if (location == null) {
+ return Retry.NONE;
+ }
+ if (responseCode == HTTP_USE_PROXY) {
+ int start = 0;
+ if (location.startsWith(url.getProtocol() + ':')) {
+ start = url.getProtocol().length() + 1;
+ }
+ if (location.startsWith("//", start)) {
+ start += 2;
+ }
+ setProxy(location.substring(start));
+ return Retry.NEW_CONNECTION;
+ }
+ 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 {
+ // TODO: strip cookies?
+ rawRequestHeaders.removeAll("Host");
+ return Retry.NEW_CONNECTION;
+ }
+
+ default:
+ return Retry.NONE;
}
}
+ private void setProxy(String proxy) {
+ // TODO: convert IllegalArgumentException etc. to ProtocolException?
+ int colon = proxy.indexOf(':');
+ String host;
+ int port;
+ if (colon != -1) {
+ host = proxy.substring(0, colon);
+ port = Integer.parseInt(proxy.substring(colon + 1));
+ } else {
+ host = proxy;
+ port = getDefaultPort();
+ }
+ this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
+ }
+
/**
* React to a failed authorization response by looking up new credentials.
*/
- private Retry processAuthHeader(String responseHeader, String retryHeader) throws IOException {
+ private Retry processAuthHeader(String fieldName, String value) throws IOException {
// keep asking for username/password until authorized
- String challenge = this.responseHeader.get(responseHeader);
+ String challenge = httpEngine.getResponseHeaders().get(fieldName);
if (challenge == null) {
throw new IOException("Received authentication challenge is null");
}
@@ -1168,7 +404,7 @@
return Retry.NONE; // could not find credentials, end request cycle
}
// add authorization credentials, bypassing the already-connected check
- requestHeader.set(retryHeader, credentials);
+ rawRequestHeaders.set(value, credentials);
return Retry.SAME_CONNECTION;
}
@@ -1202,18 +438,70 @@
return scheme + " " + encoded;
}
- private void setProxy(String proxy) {
- // TODO: convert IllegalArgumentException etc. to ProtocolException?
- int colon = proxy.indexOf(':');
- String host;
- int port;
- if (colon != -1) {
- host = proxy.substring(0, colon);
- port = Integer.parseInt(proxy.substring(colon + 1));
- } else {
- host = proxy;
- port = defaultPort;
+ 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().getResponseMessage();
+ }
+
+ @Override public final int getResponseCode() throws IOException {
+ return getResponse().getResponseHeaders().getResponseCode();
+ }
+
+ @Override public final void setIfModifiedSince(long newValue) {
+ // TODO: set this lazily in prepareRequestHeaders()
+ super.setIfModifiedSince(newValue);
+ rawRequestHeaders.add("If-Modified-Since", HttpDate.format(new Date(newValue)));
+ }
+
+ @Override public final void setRequestProperty(String field, String newValue) {
+ if (connected) {
+ throw new IllegalStateException("Cannot set request property after connection is made");
}
- this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
+ 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
index 142d95f..ed9ba72 100644
--- a/luni/src/main/java/libcore/net/http/HttpsHandler.java
+++ b/luni/src/main/java/libcore/net/http/HttpsHandler.java
@@ -23,26 +23,20 @@
import java.net.URLConnection;
import java.net.URLStreamHandler;
-/**
- * Handler for HttpsURLConnection implementation.
- */
-public class HttpsHandler extends URLStreamHandler {
+public final class HttpsHandler extends URLStreamHandler {
- @Override
- protected URLConnection openConnection(URL url) throws IOException {
+ @Override protected URLConnection openConnection(URL url) throws IOException {
return new HttpsURLConnectionImpl(url, getDefaultPort());
}
- @Override
- protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
+ @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() {
+ @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
index 9f7ae90..46f74f4 100644
--- a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
+++ b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java
@@ -36,383 +36,393 @@
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
-/**
- * HttpsURLConnection implementation.
- */
-public class HttpsURLConnectionImpl extends HttpsURLConnection {
+final class HttpsURLConnectionImpl extends HttpsURLConnection {
- /**
- * HttpsEngine that allows reuse of HttpURLConnectionImpl
- */
- private final HttpsEngine httpsEngine;
-
- /**
- * Local stash of HttpsEngine.connection.sslSocket for answering
- * queries such as getCipherSuite even after
- * httpsEngine.Connection has been recycled. It's presense 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;
+ /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl */
+ private final HttpUrlConnectionDelegate delegate;
protected HttpsURLConnectionImpl(URL url, int port) {
super(url);
- httpsEngine = new HttpsEngine(url, port);
+ delegate = new HttpUrlConnectionDelegate(url, port);
}
protected HttpsURLConnectionImpl(URL url, int port, Proxy proxy) {
super(url);
- httpsEngine = new HttpsEngine(url, port, proxy);
+ delegate = new HttpUrlConnectionDelegate(url, port, proxy);
}
private void checkConnected() {
- if (sslSocket == null) {
+ if (delegate.getSSLSocket() == null) {
throw new IllegalStateException("Connection has not yet been established");
}
}
@Override
public String getCipherSuite() {
- SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+ SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getCipherSuite();
}
checkConnected();
- return sslSocket.getSession().getCipherSuite();
+ return delegate.getSSLSocket().getSession().getCipherSuite();
}
@Override
public Certificate[] getLocalCertificates() {
- SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+ SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
List<Certificate> result = cacheResponse.getLocalCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
checkConnected();
- return sslSocket.getSession().getLocalCertificates();
+ return delegate.getSSLSocket().getSession().getLocalCertificates();
}
@Override
public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
- SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+ SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
List<Certificate> result = cacheResponse.getServerCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
checkConnected();
- return sslSocket.getSession().getPeerCertificates();
+ return delegate.getSSLSocket().getSession().getPeerCertificates();
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+ SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getPeerPrincipal();
}
checkConnected();
- return sslSocket.getSession().getPeerPrincipal();
+ return delegate.getSSLSocket().getSession().getPeerPrincipal();
}
@Override
public Principal getLocalPrincipal() {
- SecureCacheResponse cacheResponse = httpsEngine.getCacheResponse();
+ SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getLocalPrincipal();
}
checkConnected();
- return sslSocket.getSession().getLocalPrincipal();
+ return delegate.getSSLSocket().getSession().getLocalPrincipal();
}
@Override
public void disconnect() {
- httpsEngine.disconnect();
+ delegate.disconnect();
}
@Override
public InputStream getErrorStream() {
- return httpsEngine.getErrorStream();
+ return delegate.getErrorStream();
}
@Override
public String getRequestMethod() {
- return httpsEngine.getRequestMethod();
+ return delegate.getRequestMethod();
}
@Override
public int getResponseCode() throws IOException {
- return httpsEngine.getResponseCode();
+ return delegate.getResponseCode();
}
@Override
public String getResponseMessage() throws IOException {
- return httpsEngine.getResponseMessage();
+ return delegate.getResponseMessage();
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
- httpsEngine.setRequestMethod(method);
+ delegate.setRequestMethod(method);
}
@Override
public boolean usingProxy() {
- return httpsEngine.usingProxy();
+ return delegate.usingProxy();
}
@Override
public boolean getInstanceFollowRedirects() {
- return httpsEngine.getInstanceFollowRedirects();
+ return delegate.getInstanceFollowRedirects();
}
@Override
public void setInstanceFollowRedirects(boolean followRedirects) {
- httpsEngine.setInstanceFollowRedirects(followRedirects);
+ delegate.setInstanceFollowRedirects(followRedirects);
}
@Override
public void connect() throws IOException {
connected = true;
- httpsEngine.connect();
+ delegate.connect();
}
@Override
public boolean getAllowUserInteraction() {
- return httpsEngine.getAllowUserInteraction();
+ return delegate.getAllowUserInteraction();
}
@Override
public Object getContent() throws IOException {
- return httpsEngine.getContent();
+ return delegate.getContent();
}
@SuppressWarnings("unchecked") // Spec does not generify
@Override
public Object getContent(Class[] types) throws IOException {
- return httpsEngine.getContent(types);
+ return delegate.getContent(types);
}
@Override
public String getContentEncoding() {
- return httpsEngine.getContentEncoding();
+ return delegate.getContentEncoding();
}
@Override
public int getContentLength() {
- return httpsEngine.getContentLength();
+ return delegate.getContentLength();
}
@Override
public String getContentType() {
- return httpsEngine.getContentType();
+ return delegate.getContentType();
}
@Override
public long getDate() {
- return httpsEngine.getDate();
+ return delegate.getDate();
}
@Override
public boolean getDefaultUseCaches() {
- return httpsEngine.getDefaultUseCaches();
+ return delegate.getDefaultUseCaches();
}
@Override
public boolean getDoInput() {
- return httpsEngine.getDoInput();
+ return delegate.getDoInput();
}
@Override
public boolean getDoOutput() {
- return httpsEngine.getDoOutput();
+ return delegate.getDoOutput();
}
@Override
public long getExpiration() {
- return httpsEngine.getExpiration();
+ return delegate.getExpiration();
}
@Override
public String getHeaderField(int pos) {
- return httpsEngine.getHeaderField(pos);
+ return delegate.getHeaderField(pos);
}
@Override
public Map<String, List<String>> getHeaderFields() {
- return httpsEngine.getHeaderFields();
+ return delegate.getHeaderFields();
}
@Override
public Map<String, List<String>> getRequestProperties() {
- return httpsEngine.getRequestProperties();
+ return delegate.getRequestProperties();
}
@Override
public void addRequestProperty(String field, String newValue) {
- httpsEngine.addRequestProperty(field, newValue);
+ delegate.addRequestProperty(field, newValue);
}
@Override
public String getHeaderField(String key) {
- return httpsEngine.getHeaderField(key);
+ return delegate.getHeaderField(key);
}
@Override
public long getHeaderFieldDate(String field, long defaultValue) {
- return httpsEngine.getHeaderFieldDate(field, defaultValue);
+ return delegate.getHeaderFieldDate(field, defaultValue);
}
@Override
public int getHeaderFieldInt(String field, int defaultValue) {
- return httpsEngine.getHeaderFieldInt(field, defaultValue);
+ return delegate.getHeaderFieldInt(field, defaultValue);
}
@Override
public String getHeaderFieldKey(int posn) {
- return httpsEngine.getHeaderFieldKey(posn);
+ return delegate.getHeaderFieldKey(posn);
}
@Override
public long getIfModifiedSince() {
- return httpsEngine.getIfModifiedSince();
+ return delegate.getIfModifiedSince();
}
@Override
public InputStream getInputStream() throws IOException {
- return httpsEngine.getInputStream();
+ return delegate.getInputStream();
}
@Override
public long getLastModified() {
- return httpsEngine.getLastModified();
+ return delegate.getLastModified();
}
@Override
public OutputStream getOutputStream() throws IOException {
- return httpsEngine.getOutputStream();
+ return delegate.getOutputStream();
}
@Override
public Permission getPermission() throws IOException {
- return httpsEngine.getPermission();
+ return delegate.getPermission();
}
@Override
public String getRequestProperty(String field) {
- return httpsEngine.getRequestProperty(field);
+ return delegate.getRequestProperty(field);
}
@Override
public URL getURL() {
- return httpsEngine.getURL();
+ return delegate.getURL();
}
@Override
public boolean getUseCaches() {
- return httpsEngine.getUseCaches();
+ return delegate.getUseCaches();
}
@Override
public void setAllowUserInteraction(boolean newValue) {
- httpsEngine.setAllowUserInteraction(newValue);
+ delegate.setAllowUserInteraction(newValue);
}
@Override
public void setDefaultUseCaches(boolean newValue) {
- httpsEngine.setDefaultUseCaches(newValue);
+ delegate.setDefaultUseCaches(newValue);
}
@Override
public void setDoInput(boolean newValue) {
- httpsEngine.setDoInput(newValue);
+ delegate.setDoInput(newValue);
}
@Override
public void setDoOutput(boolean newValue) {
- httpsEngine.setDoOutput(newValue);
+ delegate.setDoOutput(newValue);
}
@Override
public void setIfModifiedSince(long newValue) {
- httpsEngine.setIfModifiedSince(newValue);
+ delegate.setIfModifiedSince(newValue);
}
@Override
public void setRequestProperty(String field, String newValue) {
- httpsEngine.setRequestProperty(field, newValue);
+ delegate.setRequestProperty(field, newValue);
}
@Override
public void setUseCaches(boolean newValue) {
- httpsEngine.setUseCaches(newValue);
+ delegate.setUseCaches(newValue);
}
@Override
public void setConnectTimeout(int timeout) {
- httpsEngine.setConnectTimeout(timeout);
+ delegate.setConnectTimeout(timeout);
}
@Override
public int getConnectTimeout() {
- return httpsEngine.getConnectTimeout();
+ return delegate.getConnectTimeout();
}
@Override
public void setReadTimeout(int timeout) {
- httpsEngine.setReadTimeout(timeout);
+ delegate.setReadTimeout(timeout);
}
@Override
public int getReadTimeout() {
- return httpsEngine.getReadTimeout();
+ return delegate.getReadTimeout();
}
@Override
public String toString() {
- return httpsEngine.toString();
+ return delegate.toString();
}
@Override
public void setFixedLengthStreamingMode(int contentLength) {
- httpsEngine.setFixedLengthStreamingMode(contentLength);
+ delegate.setFixedLengthStreamingMode(contentLength);
}
@Override
public void setChunkedStreamingMode(int chunkLength) {
- httpsEngine.setChunkedStreamingMode(chunkLength);
+ delegate.setChunkedStreamingMode(chunkLength);
}
- private final class HttpsEngine extends HttpURLConnectionImpl {
-
- protected HttpsEngine(URL url, int port) {
+ private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
+ private HttpUrlConnectionDelegate(URL url, int port) {
super(url, port);
}
- protected HttpsEngine(URL url, int port, Proxy proxy) {
+ private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) {
super(url, port, proxy);
}
- @Override public void makeConnection() throws IOException {
- connected = true;
+ @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
+ HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
+ return new HttpsEngine(this, method, requestHeaders, connection, requestBody,
+ HttpsURLConnectionImpl.this);
+ }
- if (connection != null || responseBodyIn != null) {
- return;
- }
+ public SecureCacheResponse getCacheResponse() {
+ return (SecureCacheResponse) httpEngine.getCacheResponse();
+ }
- /*
- * Short-circuit a reentrant call. The first step in doing SSL with
- * an HTTP proxy requires calling retrieveResponse() which calls
- * back into makeConnection(). We can return immediately because the
- * unencrypted connection is already valid.
- */
- if (method == CONNECT) {
- return;
- }
+ public SSLSocket getSSLSocket() {
+ HttpsEngine engine = (HttpsEngine) httpEngine;
+ return engine != null ? engine.sslSocket : null;
+ }
+ }
- boolean connectionReused;
+ 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) {
@@ -427,9 +437,8 @@
}
if (!connectionReused) {
- sslSocket = connection.verifySecureSocketHostname(getHostnameVerifier());
+ sslSocket = connection.verifySecureSocketHostname(enclosing.getHostnameVerifier());
}
- setUpTransportIO(connection);
}
/**
@@ -441,12 +450,12 @@
* an SSL3 only fallback mode without compression.
*/
private boolean makeSslConnection(boolean tlsTolerant) throws IOException {
-
- super.makeConnection();
-
- // if we got a response from the cache, we're done
- if (cacheResponse != null) {
- return true;
+ // 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
@@ -460,63 +469,78 @@
return true;
}
- // make SSL Tunnel
- if (requiresTunnel()) {
- String originalMethod = method;
- method = CONNECT;
- intermediateResponse = true;
- try {
- retrieveResponse();
- discardIntermediateResponse();
- } finally {
- method = originalMethod;
- intermediateResponse = false;
- }
- }
-
- connection.setupSecureSocket(getSSLSocketFactory(), tlsTolerant);
+ connection.setupSecureSocket(enclosing.getSSLSocketFactory(), tlsTolerant);
return false;
}
- public SecureCacheResponse getCacheResponse() {
- return (SecureCacheResponse) cacheResponse;
+ private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection,
+ RawHeaders requestHeaders) throws IOException {
+ HttpEngine connect = new ProxyConnectEngine(policy, requestHeaders, connection);
+ connect.sendRequest();
+ connect.readResponse();
}
- @Override protected void setUpTransportIO(HttpConnection connection) throws IOException {
- if (!requiresTunnel() && sslSocket == null) {
- return; // don't initialize streams that won't be used
- }
- super.setUpTransportIO(connection);
- }
-
- @Override protected boolean requiresTunnel() {
- return usingProxy();
- }
-
- @Override protected HttpURLConnection getConnectionForCaching() {
- return HttpsURLConnectionImpl.this;
- }
-
- @Override protected boolean acceptCacheResponse(CacheResponse cacheResponse) {
+ @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
return cacheResponse instanceof SecureCacheResponse;
}
- @Override protected String requestString() {
- if (!usingProxy()) {
- return super.requestString();
+ @Override protected boolean includeAuthorityInRequestLine() {
+ // Even if there is a proxy, it isn't involved. Always request just the file.
+ return false;
+ }
- } else if (method == CONNECT) {
- // SSL tunnels require host:port for the origin server
- return url.getHost() + ":" + url.getEffectivePort();
+ @Override protected HttpURLConnection getHttpConnectionToCache() {
+ return enclosing;
+ }
+ }
- } else {
- // we has made SSL Tunneling, return /requested.data
- String file = url.getFile();
- if (file == null || file.length() == 0) {
- file = "/";
- }
- return file;
+ private static class ProxyConnectEngine extends HttpEngine {
+ public ProxyConnectEngine(HttpURLConnectionImpl policy, RawHeaders requestHeaders,
+ HttpConnection connection) throws IOException {
+ super(policy, HttpEngine.CONNECT, requestHeaders, null, null);
+ this.connection = connection;
+ }
+
+ /**
+ * 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 {
+ RawHeaders 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.get("Host");
+ if (host == null) {
+ host = getOriginAddress(url);
}
+ result.set("Host", host);
+
+ String userAgent = privateHeaders.get("User-Agent");
+ if (userAgent == null) {
+ userAgent = getDefaultUserAgent();
+ }
+ result.set("User-Agent", userAgent);
+
+ // Copy over the Proxy-Authorization header if it exists.
+ String proxyAuthorization = privateHeaders.get("Proxy-Authorization");
+ 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
new file mode 100644
index 0000000..a6ec3c3
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/RawHeaders.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.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.
+ */
+final class RawHeaders implements Cloneable {
+
+ private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+ @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) {
+ 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 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);
+ }
+ }
+
+ public void addIfAbsent(String fieldName, String value) {
+ if (get(fieldName) == null) {
+ 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;
+ }
+
+ 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
new file mode 100644
index 0000000..ce9354b
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/RequestHeaders.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+/**
+ * Parsed HTTP request headers.
+ */
+final class RequestHeaders {
+ final URI uri;
+ final RawHeaders headers;
+
+ /** Don't use a cache to satisfy this request. */
+ boolean noCache;
+ int maxAgeSeconds = -1;
+ int maxStaleSeconds = -1;
+ 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.
+ */
+ boolean onlyIfCached;
+ String noCacheField;
+
+ /**
+ * 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.
+ */
+ boolean hasConditions;
+
+ /**
+ * 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.
+ */
+ boolean hasAuthorization;
+
+ 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;
+ noCacheField = parameter;
+ } 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)) {
+ hasConditions = true;
+ } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
+ hasConditions = true;
+ } else if ("Authorization".equalsIgnoreCase(fieldName)) {
+ hasAuthorization = true;
+ }
+ }
+ }
+}
diff --git a/luni/src/main/java/libcore/net/http/ResponseHeaders.java b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
new file mode 100644
index 0000000..2ee5a58
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/ResponseHeaders.java
@@ -0,0 +1,323 @@
+/*
+ * 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.URI;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Parsed HTTP response headers.
+ */
+final class ResponseHeaders {
+
+ /** HTTP header name for the local time when the request was sent. */
+ public static final String SENT_MILLIS = "X-Android-Sent-Millis";
+
+ /** HTTP header name for the local time when the response was received. */
+ public static final String RECEIVED_MILLIS = "X-Android-Received-Millis";
+
+ final URI uri;
+ final RawHeaders headers;
+
+ /** The server's time when this response was served, if known. */
+ Date servedDate;
+
+ /** The last modified date of the response, if known. */
+ 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.
+ */
+ Date expires;
+
+ /**
+ * Extension header set by HttpURLConnectionImpl specifying the timestamp
+ * when the HTTP request was first initiated.
+ */
+ long sentRequestMillis;
+
+ /**
+ * Extension header set by HttpURLConnectionImpl specifying the timestamp
+ * when the HTTP response was first received.
+ */
+ 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.
+ */
+ boolean noCache;
+
+ /** If true, this response should not be cached. */
+ boolean noStore;
+
+ /**
+ * The duration past the response's served date that it can be served
+ * without validation.
+ */
+ 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.
+ */
+ 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.
+ */
+ boolean isPublic;
+ String privateField;
+ boolean mustRevalidate;
+ String etag;
+ int ageSeconds = -1;
+
+ 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("private")) {
+ privateField = parameter;
+ } 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 (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
+ sentRequestMillis = Long.parseLong(value);
+ } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
+ receivedResponseMillis = Long.parseLong(value);
+ }
+ }
+ }
+
+ /**
+ * 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 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.noCache || request.hasConditions) {
+ return ResponseSource.NETWORK;
+ }
+
+ long ageMillis = computeAge(nowMillis);
+ long freshMillis = computeFreshnessLifetime();
+
+ if (request.maxAgeSeconds != -1) {
+ freshMillis = Math.min(freshMillis,
+ TimeUnit.SECONDS.toMillis(request.maxAgeSeconds));
+ }
+
+ long minFreshMillis = 0;
+ if (request.minFreshSeconds != -1) {
+ minFreshMillis = TimeUnit.SECONDS.toMillis(request.minFreshSeconds);
+ }
+
+ long maxStaleMillis = 0;
+ if (request.maxStaleSeconds != -1) {
+ maxStaleMillis = TimeUnit.SECONDS.toMillis(request.maxStaleSeconds);
+ }
+
+ if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
+ if (ageMillis + minFreshMillis >= freshMillis) {
+ // TODO: this should be RESPONSE headers
+ request.headers.add("Warning", "110 HttpURLConnection \"Response is stale\"");
+ }
+ if (ageMillis > TimeUnit.HOURS.toMillis(24) && isFreshnessLifetimeHeuristic()) {
+ // TODO: this should be RESPONSE headers
+ request.headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
+ }
+ return ResponseSource.CACHE;
+ }
+
+ if (lastModified != null) {
+ request.headers.add("If-Modified-Since", HttpDate.format(lastModified));
+ request.hasConditions = true;
+ } else if (servedDate != null) {
+ request.headers.add("If-Modified-Since", HttpDate.format(servedDate));
+ request.hasConditions = true;
+ }
+
+ if (etag != null) {
+ request.headers.add("If-None-Match", etag);
+ request.hasConditions = true;
+ }
+
+ 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;
+ }
+}
diff --git a/luni/src/main/java/libcore/net/http/ResponseSource.java b/luni/src/main/java/libcore/net/http/ResponseSource.java
new file mode 100644
index 0000000..731e37f
--- /dev/null
+++ b/luni/src/main/java/libcore/net/http/ResponseSource.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+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/libcore/net/http/UnknownLengthHttpInputStream.java b/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
index b1167c9..a8224a3 100644
--- a/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
+++ b/luni/src/main/java/libcore/net/http/UnknownLengthHttpInputStream.java
@@ -28,8 +28,8 @@
private boolean inputExhausted;
UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest,
- HttpURLConnectionImpl httpURLConnection) throws IOException {
- super(is, httpURLConnection, cacheRequest);
+ HttpEngine httpEngine) throws IOException {
+ super(is, httpEngine, cacheRequest);
}
@Override public int read(byte[] buffer, int offset, int count) throws IOException {
diff --git a/luni/src/main/java/libcore/util/EmptyArray.java b/luni/src/main/java/libcore/util/EmptyArray.java
index d2ee4cf..2040ec1 100644
--- a/luni/src/main/java/libcore/util/EmptyArray.java
+++ b/luni/src/main/java/libcore/util/EmptyArray.java
@@ -28,4 +28,5 @@
public static final Class<?>[] CLASS = new Class[0];
public static final Object[] OBJECT = new Object[0];
public static final String[] STRING = new String[0];
+ public static final Throwable[] THROWABLE = new Throwable[0];
}
diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java b/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
index e5a511e..485d722 100644
--- a/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
+++ b/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java
@@ -41,8 +41,7 @@
public int writeDirect(FileDescriptor fd, int address, int offset, int count) throws IOException;
- public boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
- throws IOException;
+ public boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException;
public boolean isConnected(FileDescriptor fd, int timeout) throws IOException;
public int send(FileDescriptor fd, byte[] data, int offset, int length,
@@ -59,9 +58,6 @@
public void sendUrgentData(FileDescriptor fd, byte value);
- public void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeout)
- throws SocketException;
-
/**
* Select the given file descriptors for read and write operations.
*
@@ -96,17 +92,6 @@
int numReadable, int numWritable, long timeout, int[] flags)
throws SocketException;
- /*
- * Set the nominated socket option in the IP stack.
- *
- * @param fd the socket descriptor @param opt the option selector @param
- * optVal the nominated option value
- *
- * @throws SocketException if the option is invalid or cannot be set
- */
- public void setSocketOption(FileDescriptor fd, int opt, Object optVal)
- throws SocketException;
-
/**
* It is an error to close the same file descriptor from multiple threads
* concurrently.
diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java b/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
index 3fc4ab4..e98b41cd 100644
--- a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
+++ b/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
@@ -43,11 +43,7 @@
public native void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException;
- public native void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeout)
- throws SocketException;
-
- public native boolean connectNonBlocking(FileDescriptor fd, InetAddress inetAddress, int port)
- throws IOException;
+ public native boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException;
public native boolean isConnected(FileDescriptor fd, int timeout) throws IOException;
public native void disconnectDatagram(FileDescriptor fd) throws SocketException;
@@ -91,9 +87,6 @@
public native void sendUrgentData(FileDescriptor fd, byte value);
- public native void setSocketOption(FileDescriptor fd, int opt, Object optVal)
- throws SocketException;
-
public native void close(FileDescriptor fd) throws IOException;
public native int write(FileDescriptor fd, byte[] data, int offset, int count)
diff --git a/luni/src/main/native/ErrorCode.cpp b/luni/src/main/native/ErrorCode.cpp
deleted file mode 100644
index 9947625..0000000
--- a/luni/src/main/native/ErrorCode.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
-*******************************************************************************
-* Copyright (C) 1996-2005, International Business Machines Corporation and *
-* others. All Rights Reserved. *
-*******************************************************************************
-*
-*******************************************************************************
-*/
-
-#include "ErrorCode.h"
-#include "JNIHelp.h"
-
-/**
-* Checks if an error has occurred, throwing a suitable exception if so.
-* @param env JNI environment
-* @param errorCode code to determine if it is an error
-* @return 0 if errorCode is not an error, 1 if errorCode is an error, but the
-* creation of the exception to be thrown fails
- * @exception thrown if errorCode represents an error
-*/
-UBool icu4jni_error(JNIEnv* env, UErrorCode errorCode)
-{
- const char* message = u_errorName(errorCode);
- if (errorCode <= U_ZERO_ERROR || errorCode >= U_ERROR_LIMIT) {
- return 0;
- }
-
- switch (errorCode) {
- case U_ILLEGAL_ARGUMENT_ERROR:
- return jniThrowException(env, "java/lang/IllegalArgumentException", message);
- case U_INDEX_OUTOFBOUNDS_ERROR:
- case U_BUFFER_OVERFLOW_ERROR:
- return jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", message);
- case U_UNSUPPORTED_ERROR:
- return jniThrowException(env, "java/lang/UnsupportedOperationException", message);
- default:
- return jniThrowRuntimeException(env, message);
- }
-}
diff --git a/luni/src/main/native/ErrorCode.h b/luni/src/main/native/ErrorCode.h
deleted file mode 100644
index 1599bc6..0000000
--- a/luni/src/main/native/ErrorCode.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
-*******************************************************************************
-* Copyright (C) 1996-2005, International Business Machines Corporation and *
-* others. All Rights Reserved. *
-*******************************************************************************
-*******************************************************************************
-*/
-
-#ifndef ERRORCODE_H
-#define ERRORCODE_H
-
-#include <jni.h>
-#include "unicode/utypes.h"
-#include "unicode/putil.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
-* Checks if an error has occured.
-* Throws a generic Java RuntimeException if an error has occured.
-* @param env JNI environment variable
-* @param errorcode code to determine if it is an erro
-* @return 0 if errorcode is not an error, 1 if errorcode is an error, but the
-* creation of the exception to be thrown fails
-* @exception thrown if errorcode represents an error
-*/
-UBool icu4jni_error(JNIEnv* env, UErrorCode errorcode);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/luni/src/main/native/JniConstants.cpp b/luni/src/main/native/JniConstants.cpp
index 646bd3f..2681b47 100644
--- a/luni/src/main/native/JniConstants.cpp
+++ b/luni/src/main/native/JniConstants.cpp
@@ -32,15 +32,14 @@
jclass JniConstants::fieldClass;
jclass JniConstants::fieldPositionIteratorClass;
jclass JniConstants::fileDescriptorClass;
+jclass JniConstants::gaiExceptionClass;
jclass JniConstants::inetAddressClass;
jclass JniConstants::inetSocketAddressClass;
jclass JniConstants::inflaterClass;
jclass JniConstants::integerClass;
-jclass JniConstants::interfaceAddressClass;
jclass JniConstants::localeDataClass;
jclass JniConstants::longClass;
jclass JniConstants::methodClass;
-jclass JniConstants::multicastGroupRequestClass;
jclass JniConstants::mutableIntClass;
jclass JniConstants::mutableLongClass;
jclass JniConstants::parsePositionClass;
@@ -50,7 +49,9 @@
jclass JniConstants::socketImplClass;
jclass JniConstants::stringArrayClass;
jclass JniConstants::stringClass;
+jclass JniConstants::structAddrinfoClass;
jclass JniConstants::structFlockClass;
+jclass JniConstants::structGroupReqClass;
jclass JniConstants::structLingerClass;
jclass JniConstants::structStatClass;
jclass JniConstants::structStatFsClass;
@@ -81,15 +82,14 @@
fieldClass = findClass(env, "java/lang/reflect/Field");
fieldPositionIteratorClass = findClass(env, "libcore/icu/NativeDecimalFormat$FieldPositionIterator");
fileDescriptorClass = findClass(env, "java/io/FileDescriptor");
+ gaiExceptionClass = findClass(env, "libcore/io/GaiException");
inetAddressClass = findClass(env, "java/net/InetAddress");
inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress");
inflaterClass = findClass(env, "java/util/zip/Inflater");
integerClass = findClass(env, "java/lang/Integer");
- interfaceAddressClass = findClass(env, "java/net/InterfaceAddress");
localeDataClass = findClass(env, "libcore/icu/LocaleData");
longClass = findClass(env, "java/lang/Long");
methodClass = findClass(env, "java/lang/reflect/Method");
- multicastGroupRequestClass = findClass(env, "java/net/MulticastGroupRequest");
mutableIntClass = findClass(env, "libcore/util/MutableInt");
mutableLongClass = findClass(env, "libcore/util/MutableLong");
parsePositionClass = findClass(env, "java/text/ParsePosition");
@@ -99,7 +99,9 @@
socketImplClass = findClass(env, "java/net/SocketImpl");
stringArrayClass = findClass(env, "[Ljava/lang/String;");
stringClass = findClass(env, "java/lang/String");
+ structAddrinfoClass = findClass(env, "libcore/io/StructAddrinfo");
structFlockClass = findClass(env, "libcore/io/StructFlock");
+ structGroupReqClass = findClass(env, "libcore/io/StructGroupReq");
structLingerClass = findClass(env, "libcore/io/StructLinger");
structStatClass = findClass(env, "libcore/io/StructStat");
structStatFsClass = findClass(env, "libcore/io/StructStatFs");
diff --git a/luni/src/main/native/JniConstants.h b/luni/src/main/native/JniConstants.h
index 2cc41a7..af5056b 100644
--- a/luni/src/main/native/JniConstants.h
+++ b/luni/src/main/native/JniConstants.h
@@ -54,15 +54,14 @@
static jclass fieldClass;
static jclass fieldPositionIteratorClass;
static jclass fileDescriptorClass;
+ static jclass gaiExceptionClass;
static jclass inetAddressClass;
static jclass inetSocketAddressClass;
static jclass inflaterClass;
static jclass integerClass;
- static jclass interfaceAddressClass;
static jclass localeDataClass;
static jclass longClass;
static jclass methodClass;
- static jclass multicastGroupRequestClass;
static jclass mutableIntClass;
static jclass mutableLongClass;
static jclass parsePositionClass;
@@ -72,7 +71,9 @@
static jclass socketImplClass;
static jclass stringArrayClass;
static jclass stringClass;
+ static jclass structAddrinfoClass;
static jclass structFlockClass;
+ static jclass structGroupReqClass;
static jclass structLingerClass;
static jclass structStatClass;
static jclass structStatFsClass;
diff --git a/luni/src/main/native/JniException.cpp b/luni/src/main/native/JniException.cpp
index 427427d..6626448 100644
--- a/luni/src/main/native/JniException.cpp
+++ b/luni/src/main/native/JniException.cpp
@@ -17,31 +17,33 @@
#include "JniException.h"
#include "JNIHelp.h"
+bool maybeThrowIcuException(JNIEnv* env, UErrorCode error) {
+ if (U_SUCCESS(error)) {
+ return false;
+ }
+ const char* message = u_errorName(error);
+ switch (error) {
+ case U_ILLEGAL_ARGUMENT_ERROR:
+ return jniThrowException(env, "java/lang/IllegalArgumentException", message);
+ case U_INDEX_OUTOFBOUNDS_ERROR:
+ case U_BUFFER_OVERFLOW_ERROR:
+ return jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", message);
+ case U_UNSUPPORTED_ERROR:
+ return jniThrowException(env, "java/lang/UnsupportedOperationException", message);
+ default:
+ return jniThrowRuntimeException(env, message);
+ }
+}
+
void jniThrowExceptionWithErrno(JNIEnv* env, const char* exceptionClassName, int error) {
char buf[BUFSIZ];
jniThrowException(env, exceptionClassName, jniStrError(error, buf, sizeof(buf)));
}
-void jniThrowBindException(JNIEnv* env, int error) {
- jniThrowExceptionWithErrno(env, "java/net/BindException", error);
-}
-
-void jniThrowConnectException(JNIEnv* env, int error) {
- jniThrowExceptionWithErrno(env, "java/net/ConnectException", error);
-}
-
void jniThrowOutOfMemoryError(JNIEnv* env, const char* message) {
jniThrowException(env, "java/lang/OutOfMemoryError", message);
}
-void jniThrowSecurityException(JNIEnv* env, int error) {
- jniThrowExceptionWithErrno(env, "java/lang/SecurityException", error);
-}
-
void jniThrowSocketException(JNIEnv* env, int error) {
jniThrowExceptionWithErrno(env, "java/net/SocketException", error);
}
-
-void jniThrowSocketTimeoutException(JNIEnv* env, int error) {
- jniThrowExceptionWithErrno(env, "java/net/SocketTimeoutException", error);
-}
diff --git a/luni/src/main/native/JniException.h b/luni/src/main/native/JniException.h
index c2e2556..b3447d2 100644
--- a/luni/src/main/native/JniException.h
+++ b/luni/src/main/native/JniException.h
@@ -18,14 +18,13 @@
#define JNI_EXCEPTION_H_included
#include "jni.h"
+#include "unicode/utypes.h" // For UErrorCode.
void jniThrowExceptionWithErrno(JNIEnv* env, const char* exceptionClassName, int error);
-void jniThrowBindException(JNIEnv* env, int error);
-void jniThrowConnectException(JNIEnv* env, int error);
void jniThrowOutOfMemoryError(JNIEnv* env, const char* message);
-void jniThrowSecurityException(JNIEnv* env, int error);
void jniThrowSocketException(JNIEnv* env, int error);
-void jniThrowSocketTimeoutException(JNIEnv* env, int error);
+
+bool maybeThrowIcuException(JNIEnv* env, UErrorCode error);
#endif // JNI_EXCEPTION_H_included
diff --git a/luni/src/main/native/NetworkUtilities.cpp b/luni/src/main/native/NetworkUtilities.cpp
index 2e46935..26df02d 100644
--- a/luni/src/main/native/NetworkUtilities.cpp
+++ b/luni/src/main/native/NetworkUtilities.cpp
@@ -24,8 +24,9 @@
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
+#include <sys/socket.h>
-bool byteArrayToSocketAddress(JNIEnv* env, jclass, jbyteArray byteArray, int port, sockaddr_storage* ss) {
+static bool byteArrayToSocketAddress(JNIEnv* env, jbyteArray byteArray, int port, sockaddr_storage* ss) {
if (byteArray == NULL) {
jniThrowNullPointerException(env, NULL);
return false;
@@ -59,7 +60,24 @@
return true;
}
-jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) {
+static jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) {
+ // Convert IPv4-mapped addresses to IPv4 addresses.
+ // The RI states "Java will never return an IPv4-mapped address".
+ sockaddr_storage tmp;
+ memset(&tmp, 0, sizeof(tmp));
+ const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(ss);
+ if (ss->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ // Copy the IPv6 address into the temporary sockaddr_storage.
+ memcpy(&tmp, ss, sizeof(tmp));
+ // Unmap it into an IPv4 address.
+ sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&tmp);
+ sin->sin_family = AF_INET;
+ sin->sin_port = sin6->sin6_port;
+ memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
+ // Fall through into the regular conversion using the unmapped address.
+ ss = &tmp;
+ }
+
const void* rawAddress;
size_t addressLength;
if (ss->ss_family == AF_INET) {
@@ -87,7 +105,7 @@
return byteArray;
}
-jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) {
+static jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) {
if (byteArray == NULL) {
return NULL;
}
@@ -112,7 +130,7 @@
}
static jfieldID fid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B");
jbyteArray addressBytes = reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, fid));
- return byteArrayToSocketAddress(env, NULL, addressBytes, port, ss);
+ return byteArrayToSocketAddress(env, addressBytes, port, ss);
}
bool setBlocking(int fd, bool blocking) {
diff --git a/luni/src/main/native/NetworkUtilities.h b/luni/src/main/native/NetworkUtilities.h
index a3438c8..5e5b19e 100644
--- a/luni/src/main/native/NetworkUtilities.h
+++ b/luni/src/main/native/NetworkUtilities.h
@@ -15,18 +15,8 @@
*/
#include "jni.h"
-
#include <sys/socket.h>
-// Convert from byte[] to InetAddress.
-jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray);
-
-// Convert from byte[] to sockaddr_storage.
-bool byteArrayToSocketAddress(JNIEnv* env, jclass, jbyteArray byteArray, int port, sockaddr_storage* ss);
-
-// Convert from sockaddr_storage to byte[].
-jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss);
-
// Convert from sockaddr_storage to InetAddress.
jobject socketAddressToInetAddress(JNIEnv* env, const sockaddr_storage* ss);
@@ -34,5 +24,7 @@
bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss);
+
// Changes 'fd' to be blocking/non-blocking. Returns false and sets errno on failure.
+// @Deprecated - use IoUtils.setBlocking
bool setBlocking(int fd, bool blocking);
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index 0b7b1ff..e518944 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -31,8 +31,6 @@
extern int register_java_lang_StrictMath(JNIEnv* env);
extern int register_java_lang_System(JNIEnv* env);
extern int register_java_math_NativeBN(JNIEnv* env);
-extern int register_java_net_InetAddress(JNIEnv* env);
-extern int register_java_net_NetworkInterface(JNIEnv* env);
extern int register_java_nio_ByteOrder(JNIEnv* env);
extern int register_java_nio_charset_Charsets(JNIEnv* env);
extern int register_java_text_Bidi(JNIEnv* env);
@@ -62,7 +60,7 @@
extern int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env);
// DalvikVM calls this on startup, so we can statically register all our native methods.
-extern "C" int registerCoreLibrariesJni(JNIEnv* env) {
+int registerCoreLibrariesJni(JNIEnv* env) {
ScopedLocalFrame localFrame(env);
JniConstants::init(env);
@@ -78,8 +76,6 @@
register_java_lang_StrictMath(env) != -1 &&
register_java_lang_System(env) != -1 &&
register_java_math_NativeBN(env) != -1 &&
- register_java_net_InetAddress(env) != -1 &&
- register_java_net_NetworkInterface(env) != -1 &&
register_java_nio_ByteOrder(env) != -1 &&
register_java_nio_charset_Charsets(env) != -1 &&
register_java_text_Bidi(env) != -1 &&
diff --git a/luni/src/main/native/ifaddrs-android.cpp b/luni/src/main/native/ifaddrs-android.cpp
deleted file mode 100644
index 4193bdf..0000000
--- a/luni/src/main/native/ifaddrs-android.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-#ifdef HAVE_ANDROID_OS
-
-#include "ifaddrs-android.h"
-
-#include <arpa/inet.h>
-#include <cstring>
-#include <errno.h>
-#include <net/if.h>
-#include <netinet/in.h>
-#include <new>
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <stdio.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-
-#include "ScopedFd.h"
-#include "UniquePtr.h"
-
-ifaddrs::ifaddrs(ifaddrs* next, sockaddr* ifa_addr, sockaddr* ifa_netmask)
-: ifa_next(next), ifa_name(NULL), ifa_flags(0), ifa_addr(ifa_addr), ifa_netmask(ifa_netmask)
-{
-}
-
-ifaddrs::~ifaddrs() {
- delete ifa_next;
- delete[] ifa_name;
- delete ifa_addr;
- delete ifa_netmask;
-}
-
-static bool sendNetlinkMessage(int fd, const void* data, size_t byteCount) {
- ssize_t sentByteCount = TEMP_FAILURE_RETRY(send(fd, data, byteCount, 0));
- return (sentByteCount == static_cast<ssize_t>(byteCount));
-}
-
-static ssize_t recvNetlinkMessage(int fd, char* buf, size_t byteCount) {
- return TEMP_FAILURE_RETRY(recv(fd, buf, byteCount, 0));
-}
-
-// Returns a pointer to the first byte in the address data (which is
-// stored in network byte order).
-static uint8_t* sockaddrBytes(int family, sockaddr_storage* ss) {
- if (family == AF_INET) {
- sockaddr_in* ss4 = reinterpret_cast<sockaddr_in*>(ss);
- return reinterpret_cast<uint8_t*>(&ss4->sin_addr);
- } else if (family == AF_INET6) {
- sockaddr_in6* ss6 = reinterpret_cast<sockaddr_in6*>(ss);
- return reinterpret_cast<uint8_t*>(&ss6->sin6_addr);
- }
- return NULL;
-}
-
-// Netlink gives us the address family in the header, and the
-// sockaddr_in or sockaddr_in6 bytes as the payload. We need to
-// stitch the two bits together into the sockaddr that's part of
-// our portable interface.
-static sockaddr* toSocketAddress(int family, void* data, size_t byteCount) {
- // Set the address proper...
- sockaddr_storage* ss = new sockaddr_storage;
- memset(ss, 0, sizeof(*ss));
- ss->ss_family = family;
- uint8_t* dst = sockaddrBytes(family, ss);
- memcpy(dst, data, byteCount);
- return reinterpret_cast<sockaddr*>(ss);
-}
-
-// Netlink gives us the prefix length as a bit count. We need to turn
-// that into a BSD-compatible netmask represented by a sockaddr*.
-static sockaddr* toNetmask(int family, size_t prefixLength) {
- // ...and work out the netmask from the prefix length.
- sockaddr_storage* ss = new sockaddr_storage;
- memset(ss, 0, sizeof(*ss));
- ss->ss_family = family;
- uint8_t* dst = sockaddrBytes(family, ss);
- memset(dst, 0xff, prefixLength / 8);
- if ((prefixLength % 8) != 0) {
- dst[prefixLength/8] = (0xff << (8 - (prefixLength % 8)));
- }
- return reinterpret_cast<sockaddr*>(ss);
-}
-
-// Sadly, we can't keep the interface index for portability with BSD.
-// We'll have to keep the name instead, and re-query the index when
-// we need it later.
-static bool setNameAndFlagsByIndex(ifaddrs* ifa, int interfaceIndex) {
- // Get the name.
- char buf[IFNAMSIZ];
- char* name = if_indextoname(interfaceIndex, buf);
- if (name == NULL) {
- return false;
- }
- ifa->ifa_name = new char[strlen(name) + 1];
- strcpy(ifa->ifa_name, name);
-
- // Get the flags.
- ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0));
- if (fd.get() == -1) {
- return false;
- }
- ifreq ifr;
- memset(&ifr, 0, sizeof(ifr));
- strcpy(ifr.ifr_name, name);
- int rc = ioctl(fd.get(), SIOCGIFFLAGS, &ifr);
- if (rc == -1) {
- return false;
- }
- ifa->ifa_flags = ifr.ifr_flags;
- return true;
-}
-
-// Source-compatible with the BSD function.
-int getifaddrs(ifaddrs** result) {
- // Simplify cleanup for callers.
- *result = NULL;
-
- // Create a netlink socket.
- ScopedFd fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE));
- if (fd.get() < 0) {
- return -1;
- }
-
- // Ask for the address information.
- struct {
- nlmsghdr netlinkHeader;
- ifaddrmsg msg;
- } request;
- memset(&request, 0, sizeof(request));
- request.netlinkHeader.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST | NLM_F_MATCH;
- request.netlinkHeader.nlmsg_type = RTM_GETADDR;
- request.netlinkHeader.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(request)));
- request.msg.ifa_family = AF_UNSPEC; // All families.
- request.msg.ifa_index = 0; // All interfaces.
- if (!sendNetlinkMessage(fd.get(), &request, request.netlinkHeader.nlmsg_len)) {
- return -1;
- }
-
- // Read the responses.
- const size_t bufferSize = 65536;
- UniquePtr<char[]> buf(new char[bufferSize]);
- ssize_t bytesRead;
- while ((bytesRead = recvNetlinkMessage(fd.get(), &buf[0], bufferSize)) > 0) {
- nlmsghdr* hdr = reinterpret_cast<nlmsghdr*>(&buf[0]);
- for (; NLMSG_OK(hdr, (size_t)bytesRead); hdr = NLMSG_NEXT(hdr, bytesRead)) {
- switch (hdr->nlmsg_type) {
- case NLMSG_DONE:
- return 0;
- case NLMSG_ERROR:
- return -1;
- case RTM_NEWADDR:
- {
- // A given RTM_NEWADDR payload may contain multiple addresses. The while loop
- // below iterates through them. These locals contain the best address we've
- // seen so far.
- int ifa_index = -1;
- UniquePtr<sockaddr> ifa_addr;
- UniquePtr<sockaddr> ifa_netmask;
-
- ifaddrmsg* address = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(hdr));
- rtattr* rta = IFA_RTA(address);
- size_t ifaPayloadLength = IFA_PAYLOAD(hdr);
- while (RTA_OK(rta, ifaPayloadLength)) {
- // We can't just use IFA_ADDRESS because it's the destination address for
- // a point-to-point interface; we can't just use IFA_LOCAL because we don't
- // always have it. That is: we want IFA_LOCAL if we get it, but IFA_ADDRESS
- // otherwise. We take advantage of the fact that the kernel returns
- // IFA_LOCAL (if available) second.
- if (rta->rta_type == IFA_LOCAL || rta->rta_type == IFA_ADDRESS) {
- int family = address->ifa_family;
- if (family == AF_INET || family == AF_INET6) {
- ifa_index = address->ifa_index;
- ifa_addr.reset(toSocketAddress(family, RTA_DATA(rta), RTA_PAYLOAD(rta)));
- ifa_netmask.reset(toNetmask(family, address->ifa_prefixlen));
- }
- }
- rta = RTA_NEXT(rta, ifaPayloadLength);
- }
-
- // Did we get a usable address? If so, thread it on our list.
- if (ifa_index != -1) {
- *result = new ifaddrs(*result, ifa_addr.release(), ifa_netmask.release());
- if (!setNameAndFlagsByIndex(*result, ifa_index)) {
- return -1;
- }
- }
- }
- break;
- }
- }
- }
- // We only get here if recv fails before we see a NLMSG_DONE.
- return -1;
-}
-
-// Source-compatible with the BSD function.
-void freeifaddrs(ifaddrs* addresses) {
- delete addresses;
-}
-
-#endif
diff --git a/luni/src/main/native/ifaddrs-android.h b/luni/src/main/native/ifaddrs-android.h
deleted file mode 100644
index c89d08a..0000000
--- a/luni/src/main/native/ifaddrs-android.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2009 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.
- */
-
-#ifndef IFADDRS_ANDROID_H_included
-#define IFADDRS_ANDROID_H_included
-
-struct sockaddr;
-
-// Android (bionic) doesn't have getifaddrs(3)/freeifaddrs(3).
-// We fake it here, so java_net_NetworkInterface.cpp can use that API.
-// This code should move into bionic, though.
-
-// Source-compatible subset of the BSD struct.
-struct ifaddrs {
- // Pointer to next struct in list, or NULL at end.
- ifaddrs* ifa_next;
-
- // Interface name.
- char* ifa_name;
-
- // Interface flags.
- unsigned int ifa_flags;
-
- // Interface network address.
- sockaddr* ifa_addr;
-
- // Interface netmask.
- sockaddr* ifa_netmask;
-
- ifaddrs(ifaddrs* next, sockaddr* ifa_addr, sockaddr* ifa_netmask);
-
- ~ifaddrs();
-
-private:
- // Disallow copy and assignment.
- ifaddrs(const ifaddrs&);
- void operator=(const ifaddrs&);
-};
-
-int getifaddrs(ifaddrs** result);
-void freeifaddrs(ifaddrs* addresses);
-
-#endif // IFADDRS_ANDROID_H_included
diff --git a/luni/src/main/native/java_io_File.cpp b/luni/src/main/native/java_io_File.cpp
index 20cbb7e..db88a11 100644
--- a/luni/src/main/native/java_io_File.cpp
+++ b/luni/src/main/native/java_io_File.cpp
@@ -20,7 +20,6 @@
#include "JNIHelp.h"
#include "JniConstants.h"
#include "JniException.h"
-#include "ScopedFd.h"
#include "ScopedPrimitiveArray.h"
#include "ScopedUtfChars.h"
#include "readlink.h"
diff --git a/luni/src/main/native/java_lang_ProcessManager.cpp b/luni/src/main/native/java_lang_ProcessManager.cpp
index 386b5f3..2a1638f 100644
--- a/luni/src/main/native/java_lang_ProcessManager.cpp
+++ b/luni/src/main/native/java_lang_ProcessManager.cpp
@@ -31,16 +31,6 @@
#include "JniConstants.h"
#include "utils/Log.h"
-/** Environment variables. */
-extern char **environ;
-
-static jmethodID onExitMethod = NULL;
-
-#ifdef ANDROID
-// Keeps track of the system properties fd so we don't close it.
-static int androidSystemPropertiesFd = -1;
-#endif
-
/*
* These are constants shared with the higher level code in
* ProcessManager.java.
@@ -50,28 +40,17 @@
#define WAIT_STATUS_STRANGE_ERRNO (-3) // observed an undocumented errno
/**
- * Kills process with the given ID.
- */
-static void ProcessManager_kill(JNIEnv* env, jclass, jint pid) {
- int result = kill((pid_t) pid, SIGKILL);
- if (result == -1) {
- jniThrowIOException(env, errno);
- }
-}
-
-/**
* Loops indefinitely and calls ProcessManager.onExit() when children exit.
*/
-static void ProcessManager_watchChildren(JNIEnv* env, jobject o) {
+static void ProcessManager_watchChildren(JNIEnv* env, jclass processManagerClass, jobject processManager) {
+ static jmethodID onExitMethod = env->GetMethodID(processManagerClass, "onExit", "(II)V");
if (onExitMethod == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "staticInitialize() must run first.");
+ return;
}
- while (1) {
+ while (true) {
+ // Wait for children in our process group.
int status;
-
- /* wait for children in our process group */
pid_t pid = waitpid(0, &status, 0);
if (pid >= 0) {
@@ -116,15 +95,14 @@
* wait() other than the two that are handled
* immediately above.
*/
- LOGE("Error %d calling wait(): %s", errno,
- strerror(errno));
+ LOGE("Error %d calling wait(): %s", errno, strerror(errno));
status = WAIT_STATUS_STRANGE_ERRNO;
break;
}
}
}
- env->CallVoidMethod(o, onExitMethod, pid, status);
+ env->CallVoidMethod(processManager, onExitMethod, pid, status);
if (env->ExceptionOccurred()) {
/*
* The callback threw, so break out of the loop and return,
@@ -136,17 +114,13 @@
}
/** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */
-static void closeNonStandardFds(int skipFd) {
+static void closeNonStandardFds(int skipFd1, int skipFd2) {
// TODO: rather than close all these non-open files, we could look in /proc/self/fd.
rlimit rlimit;
getrlimit(RLIMIT_NOFILE, &rlimit);
const int max_fd = rlimit.rlim_max;
for (int fd = 3; fd < max_fd; ++fd) {
- if (fd != skipFd
-#ifdef ANDROID
- && fd != androidSystemPropertiesFd
-#endif
- ) {
+ if (fd != skipFd1 && fd != skipFd2) {
close(fd);
}
}
@@ -156,8 +130,7 @@
/** Closes all pipes in the given array. */
static void closePipes(int pipes[], int skipFd) {
- int i;
- for (i = 0; i < PIPE_COUNT * 2; i++) {
+ for (int i = 0; i < PIPE_COUNT * 2; i++) {
int fd = pipes[i];
if (fd == -1) {
return;
@@ -173,11 +146,17 @@
const char* workingDirectory, jobject inDescriptor,
jobject outDescriptor, jobject errDescriptor,
jboolean redirectErrorStream) {
- int i, result, error;
+
+ // Keep track of the system properties fd so we don't close it.
+ int androidSystemPropertiesFd = -1;
+ char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
+ if (fdString) {
+ androidSystemPropertiesFd = atoi(fdString);
+ }
// Create 4 pipes: stdin, stdout, stderr, and an exec() status pipe.
int pipes[PIPE_COUNT * 2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
- for (i = 0; i < PIPE_COUNT; i++) {
+ for (int i = 0; i < PIPE_COUNT; i++) {
if (pipe(pipes + i * 2) == -1) {
jniThrowIOException(env, errno);
closePipes(pipes, -1);
@@ -225,8 +204,8 @@
// Make statusOut automatically close if execvp() succeeds.
fcntl(statusOut, F_SETFD, FD_CLOEXEC);
- // Close remaining open fds with the exception of statusOut.
- closeNonStandardFds(statusOut);
+ // Close remaining unwanted open fds.
+ closeNonStandardFds(statusOut, androidSystemPropertiesFd);
// Switch to working directory.
if (workingDirectory != NULL) {
@@ -237,6 +216,7 @@
// Set up environment.
if (environment != NULL) {
+ extern char** environ; // Standard, but not in any header file.
environ = environment;
}
@@ -247,7 +227,7 @@
// If we got here, execvp() failed or the working dir was invalid.
execFailed:
- error = errno;
+ int error = errno;
write(statusOut, &error, sizeof(int));
close(statusOut);
exit(error);
@@ -264,6 +244,7 @@
// Check status pipe for an error code. If execvp() succeeds, the other
// end of the pipe should automatically close, in which case, we'll read
// nothing.
+ int result;
int count = read(statusIn, &result, sizeof(int));
close(statusIn);
if (count > 0) {
@@ -321,8 +302,7 @@
/**
* Converts Java String[] to char** and delegates to executeProcess().
*/
-static pid_t ProcessManager_exec(
- JNIEnv* env, jclass, jobjectArray javaCommands,
+static pid_t ProcessManager_exec(JNIEnv* env, jclass, jobjectArray javaCommands,
jobjectArray javaEnvironment, jstring javaWorkingDirectory,
jobject inDescriptor, jobject outDescriptor, jobject errDescriptor,
jboolean redirectErrorStream) {
@@ -339,8 +319,7 @@
// Convert environment array.
char** environment = convertStrings(env, javaEnvironment);
- pid_t result = executeProcess(
- env, commands, environment, workingDirectory,
+ pid_t result = executeProcess(env, commands, environment, workingDirectory,
inDescriptor, outDescriptor, errDescriptor, redirectErrorStream);
// Temporarily clear exception so we can clean up.
@@ -366,28 +345,8 @@
return result;
}
-/**
- * Looks up Java members.
- */
-static void ProcessManager_staticInitialize(JNIEnv* env,
- jclass clazz) {
-#ifdef ANDROID
- char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
- if (fdString) {
- androidSystemPropertiesFd = atoi(fdString);
- }
-#endif
-
- onExitMethod = env->GetMethodID(clazz, "onExit", "(II)V");
- if (onExitMethod == NULL) {
- return;
- }
-}
-
static JNINativeMethod methods[] = {
- NATIVE_METHOD(ProcessManager, kill, "(I)V"),
- NATIVE_METHOD(ProcessManager, staticInitialize, "()V"),
- NATIVE_METHOD(ProcessManager, watchChildren, "()V"),
+ NATIVE_METHOD(ProcessManager, watchChildren, "(Ljava/lang/ProcessManager;)V"),
NATIVE_METHOD(ProcessManager, exec, "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I"),
};
int register_java_lang_ProcessManager(JNIEnv* env) {
diff --git a/luni/src/main/native/java_net_InetAddress.cpp b/luni/src/main/native/java_net_InetAddress.cpp
deleted file mode 100644
index b85f870..0000000
--- a/luni/src/main/native/java_net_InetAddress.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "InetAddress"
-
-#define LOG_DNS 0
-
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "LocalArray.h"
-#include "NetworkUtilities.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
-#include "jni.h"
-#include "utils/Log.h"
-
-#include <stdio.h>
-#include <string.h>
-#include <netdb.h>
-#include <errno.h>
-
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-
-#if LOG_DNS
-static void logIpString(addrinfo* ai, const char* name)
-{
- char ipString[INET6_ADDRSTRLEN];
- int result = getnameinfo(ai->ai_addr, ai->ai_addrlen, ipString,
- sizeof(ipString), NULL, 0, NI_NUMERICHOST);
- if (result == 0) {
- LOGD("%s: %s (family %d, proto %d)", name, ipString, ai->ai_family,
- ai->ai_protocol);
- } else {
- LOGE("%s: getnameinfo: %s", name, gai_strerror(result));
- }
-}
-#else
-static inline void logIpString(addrinfo*, const char*)
-{
-}
-#endif
-
-static jobjectArray InetAddress_getaddrinfo(JNIEnv* env, jclass, jstring javaName) {
- ScopedUtfChars name(env, javaName);
- if (name.c_str() == NULL) {
- return NULL;
- }
-
- addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC;
- hints.ai_flags = AI_ADDRCONFIG;
- /*
- * If we don't specify a socket type, every address will appear twice, once
- * for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
- * anyway, just pick one.
- */
- hints.ai_socktype = SOCK_STREAM;
-
- addrinfo* addressList = NULL;
- jobjectArray addressArray = NULL;
- int result = getaddrinfo(name.c_str(), NULL, &hints, &addressList);
- if (result == 0 && addressList) {
- // Count results so we know how to size the output array.
- int addressCount = 0;
- for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
- if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
- addressCount++;
- }
- }
-
- // Prepare output array.
- addressArray = env->NewObjectArray(addressCount, JniConstants::byteArrayClass, NULL);
- if (addressArray == NULL) {
- // Appropriate exception will be thrown.
- LOGE("getaddrinfo: could not allocate array of size %i", addressCount);
- freeaddrinfo(addressList);
- return NULL;
- }
-
- // Examine returned addresses one by one, save them in the output array.
- int index = 0;
- for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
- sockaddr* address = ai->ai_addr;
- size_t addressLength = 0;
- void* rawAddress;
-
- switch (ai->ai_family) {
- // Find the raw address length and start pointer.
- case AF_INET6:
- addressLength = 16;
- rawAddress = &reinterpret_cast<sockaddr_in6*>(address)->sin6_addr.s6_addr;
- logIpString(ai, name.c_str());
- break;
- case AF_INET:
- addressLength = 4;
- rawAddress = &reinterpret_cast<sockaddr_in*>(address)->sin_addr.s_addr;
- logIpString(ai, name.c_str());
- break;
- default:
- // Unknown address family. Skip this address.
- LOGE("getaddrinfo: Unknown address family %d", ai->ai_family);
- continue;
- }
-
- // Convert each IP address into a Java byte array.
- ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(addressLength));
- if (byteArray.get() == NULL) {
- // Out of memory error will be thrown on return.
- LOGE("getaddrinfo: Can't allocate %d-byte array", addressLength);
- addressArray = NULL;
- break;
- }
- env->SetByteArrayRegion(byteArray.get(),
- 0, addressLength, reinterpret_cast<jbyte*>(rawAddress));
- env->SetObjectArrayElement(addressArray, index, byteArray.get());
- index++;
- }
- } else if (result == EAI_SYSTEM && errno == EACCES) {
- /* No permission to use network */
- jniThrowException(env, "java/lang/SecurityException",
- "Permission denied (maybe missing INTERNET permission)");
- } else {
- jniThrowExceptionFmt(env, "java/net/UnknownHostException",
- "Unable to resolve host \"%s\": %s", name.c_str(), gai_strerror(result));
- }
-
- if (addressList) {
- freeaddrinfo(addressList);
- }
-
- return addressArray;
-}
-
-/**
- * Looks up the name corresponding to an IP address.
- *
- * @param javaAddress: a byte array containing the raw IP address bytes. Must be
- * 4 or 16 bytes long.
- * @return the hostname.
- * @throws UnknownHostException: the IP address has no associated hostname.
- */
-static jstring InetAddress_getnameinfo(JNIEnv* env, jclass,
- jbyteArray javaAddress)
-{
- if (javaAddress == NULL) {
- jniThrowNullPointerException(env, NULL);
- return NULL;
- }
-
- // Convert the raw address bytes into a socket address structure.
- sockaddr_storage ss;
- memset(&ss, 0, sizeof(ss));
-
- size_t socklen;
- const size_t addressLength = env->GetArrayLength(javaAddress);
- if (addressLength == 4) {
- sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
- sin->sin_family = AF_INET;
- socklen = sizeof(sockaddr_in);
- jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
- env->GetByteArrayRegion(javaAddress, 0, 4, dst);
- } else if (addressLength == 16) {
- sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&ss);
- sin6->sin6_family = AF_INET6;
- socklen = sizeof(sockaddr_in6);
- jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
- env->GetByteArrayRegion(javaAddress, 0, 16, dst);
- } else {
- // The caller already throws an exception in this case. Don't worry
- // about it here.
- return NULL;
- }
-
- // Look up the host name from the IP address.
- char name[NI_MAXHOST];
- int ret = getnameinfo(reinterpret_cast<sockaddr*>(&ss), socklen,
- name, sizeof(name), NULL, 0, NI_NAMEREQD);
- if (ret != 0) {
- jniThrowException(env, "java/net/UnknownHostException", gai_strerror(ret));
- return NULL;
- }
-
- return env->NewStringUTF(name);
-}
-
-static jbyteArray InetAddress_ipStringToByteArray(JNIEnv* env, jobject, jstring javaString) {
- // Convert the String to UTF-8 bytes.
- ScopedUtfChars chars(env, javaString);
- if (chars.c_str() == NULL) {
- return NULL;
- }
- size_t byteCount = chars.size();
- LocalArray<INET6_ADDRSTRLEN> bytes(byteCount + 1);
- char* ipString = &bytes[0];
- strcpy(ipString, chars.c_str());
-
- // Accept IPv6 addresses (only) in square brackets for compatibility.
- if (ipString[0] == '[' && ipString[byteCount - 1] == ']' && strchr(ipString, ':') != NULL) {
- memmove(ipString, ipString + 1, byteCount - 2);
- ipString[byteCount - 2] = '\0';
- }
-
- jbyteArray result = NULL;
- addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_flags = AI_NUMERICHOST;
-
- sockaddr_storage ss;
- memset(&ss, 0, sizeof(ss));
-
- addrinfo* res = NULL;
- int ret = getaddrinfo(ipString, NULL, &hints, &res);
- if (ret == 0 && res) {
- // Convert IPv4-mapped addresses to IPv4 addresses.
- // The RI states "Java will never return an IPv4-mapped address".
- sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(res->ai_addr);
- if (res->ai_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
- sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
- sin->sin_family = AF_INET;
- sin->sin_port = sin6->sin6_port;
- memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
- result = socketAddressToByteArray(env, &ss);
- } else {
- result = socketAddressToByteArray(env, reinterpret_cast<sockaddr_storage*>(res->ai_addr));
- }
- } else {
- // For backwards compatibility, deal with address formats that
- // getaddrinfo does not support. For example, 1.2.3, 1.3, and even 3 are
- // valid IPv4 addresses according to the Java API. If getaddrinfo fails,
- // try to use inet_aton.
- sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
- if (inet_aton(ipString, &sin->sin_addr)) {
- sin->sin_family = AF_INET;
- sin->sin_port = 0;
- result = socketAddressToByteArray(env, &ss);
- }
- }
-
- if (res) {
- freeaddrinfo(res);
- }
-
- if (!result) {
- env->ExceptionClear();
- }
-
- return result;
-}
-
-static jstring InetAddress_byteArrayToIpString(JNIEnv* env, jobject, jbyteArray byteArray) {
- if (byteArray == NULL) {
- jniThrowNullPointerException(env, NULL);
- return NULL;
- }
- sockaddr_storage ss;
- if (!byteArrayToSocketAddress(env, NULL, byteArray, 0, &ss)) {
- return NULL;
- }
- // TODO: getnameinfo seems to want its length parameter to be exactly
- // sizeof(sockaddr_in) for an IPv4 address and sizeof (sockaddr_in6) for an
- // IPv6 address. Fix getnameinfo so it accepts sizeof(sockaddr_storage), and
- // then remove this hack.
- int sa_size;
- if (ss.ss_family == AF_INET) {
- sa_size = sizeof(sockaddr_in);
- } else if (ss.ss_family == AF_INET6) {
- sa_size = sizeof(sockaddr_in6);
- } else {
- // byteArrayToSocketAddress already threw.
- return NULL;
- }
- char ipString[INET6_ADDRSTRLEN];
- int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), sa_size,
- ipString, sizeof(ipString), NULL, 0, NI_NUMERICHOST);
- if (rc != 0) {
- jniThrowException(env, "java/net/UnknownHostException", gai_strerror(rc));
- return NULL;
- }
- return env->NewStringUTF(ipString);
-}
-
-static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(InetAddress, byteArrayToIpString, "([B)Ljava/lang/String;"),
- NATIVE_METHOD(InetAddress, getaddrinfo, "(Ljava/lang/String;)[[B"),
- NATIVE_METHOD(InetAddress, getnameinfo, "([B)Ljava/lang/String;"),
- NATIVE_METHOD(InetAddress, ipStringToByteArray, "(Ljava/lang/String;)[B"),
-};
-int register_java_net_InetAddress(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "java/net/InetAddress", gMethods, NELEM(gMethods));
-}
diff --git a/luni/src/main/native/java_net_NetworkInterface.cpp b/luni/src/main/native/java_net_NetworkInterface.cpp
deleted file mode 100644
index f8f0ba4..0000000
--- a/luni/src/main/native/java_net_NetworkInterface.cpp
+++ /dev/null
@@ -1,230 +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.
- */
-
-#define LOG_TAG "NetworkInterface"
-
-#include "JNIHelp.h"
-#include "JniConstants.h"
-#include "JniException.h"
-#include "jni.h"
-#include "NetworkUtilities.h"
-#include "ScopedFd.h"
-
-#include <errno.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <net/if.h> // Note: Can't appear before <sys/socket.h> on OS X.
-
-#ifdef HAVE_ANDROID_OS
-#include "ifaddrs-android.h"
-#else
-#include <ifaddrs.h>
-#endif
-
-// Ensures we always call freeifaddrs(3) to clean up after getifaddrs(3).
-class ScopedInterfaceAddresses {
-public:
- ScopedInterfaceAddresses() : list(NULL) {
- }
-
- bool init() {
- int rc = getifaddrs(&list);
- return (rc != -1);
- }
-
- ~ScopedInterfaceAddresses() {
- freeifaddrs(list);
- }
-
- ifaddrs* list;
-
-private:
- // Disallow copy and assignment.
- ScopedInterfaceAddresses(const ScopedInterfaceAddresses&);
- void operator=(const ScopedInterfaceAddresses&);
-};
-
-static jobject makeInterfaceAddress(JNIEnv* env, jint interfaceIndex, ifaddrs* ifa) {
- jmethodID constructor = env->GetMethodID(JniConstants::interfaceAddressClass, "<init>",
- "(ILjava/lang/String;Ljava/net/InetAddress;Ljava/net/InetAddress;)V");
- if (constructor == NULL) {
- return NULL;
- }
- jobject javaName = env->NewStringUTF(ifa->ifa_name);
- if (javaName == NULL) {
- return NULL;
- }
- sockaddr_storage* addr = reinterpret_cast<sockaddr_storage*>(ifa->ifa_addr);
- jobject javaAddress = socketAddressToInetAddress(env, addr);
- if (javaAddress == NULL) {
- return NULL;
- }
- sockaddr_storage* mask = reinterpret_cast<sockaddr_storage*>(ifa->ifa_netmask);
- jobject javaMask = socketAddressToInetAddress(env, mask);
- if (javaMask == NULL) {
- return NULL;
- }
- return env->NewObject(JniConstants::interfaceAddressClass, constructor,
- interfaceIndex, javaName, javaAddress, javaMask);
-}
-
-static jobjectArray NetworkInterface_getAllInterfaceAddressesImpl(JNIEnv* env, jclass) {
- // Get the list of interface addresses.
- ScopedInterfaceAddresses addresses;
- if (!addresses.init()) {
- jniThrowSocketException(env, errno);
- return NULL;
- }
-
- // Count how many there are.
- int interfaceAddressCount = 0;
- for (ifaddrs* ifa = addresses.list; ifa != NULL; ifa = ifa->ifa_next) {
- ++interfaceAddressCount;
- }
-
- // Build the InterfaceAddress[]...
- jobjectArray result =
- env->NewObjectArray(interfaceAddressCount, JniConstants::interfaceAddressClass, NULL);
- if (result == NULL) {
- return NULL;
- }
-
- // And fill it in...
- int arrayIndex = 0;
- for (ifaddrs* ifa = addresses.list; ifa != NULL; ifa = ifa->ifa_next) {
- // We're only interested in IP addresses.
- int family = ifa->ifa_addr->sa_family;
- if (family != AF_INET && family != AF_INET6) {
- continue;
- }
- // Until we implement Java 6's NetworkInterface.isUp,
- // we only want interfaces that are up.
- if ((ifa->ifa_flags & IFF_UP) == 0) {
- continue;
- }
- // Find the interface's index, and skip this address if
- // the interface has gone away.
- int interfaceIndex = if_nametoindex(ifa->ifa_name);
- if (interfaceIndex == 0) {
- continue;
- }
- // Make a new InterfaceAddress, and insert it into the array.
- jobject element = makeInterfaceAddress(env, interfaceIndex, ifa);
- if (element == NULL) {
- return NULL;
- }
- env->SetObjectArrayElement(result, arrayIndex, element);
- if (env->ExceptionCheck()) {
- return NULL;
- }
- ++arrayIndex;
- }
- return result;
-}
-
-static bool doIoctl(JNIEnv* env, jstring name, int request, ifreq& ifr) {
- // Copy the name into the ifreq structure, if there's room...
- jsize nameLength = env->GetStringLength(name);
- if (nameLength >= IFNAMSIZ) {
- errno = ENAMETOOLONG;
- jniThrowSocketException(env, errno);
- return false;
- }
- memset(&ifr, 0, sizeof(ifr));
- env->GetStringUTFRegion(name, 0, nameLength, ifr.ifr_name);
-
- // ...and do the ioctl.
- ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0));
- if (fd.get() == -1) {
- jniThrowSocketException(env, errno);
- return false;
- }
- int rc = ioctl(fd.get(), request, &ifr);
- if (rc == -1) {
- jniThrowSocketException(env, errno);
- return false;
- }
- return true;
-}
-
-static jboolean hasFlag(JNIEnv* env, jstring name, int flag) {
- ifreq ifr;
- doIoctl(env, name, SIOCGIFFLAGS, ifr); // May throw.
- return (ifr.ifr_flags & flag) != 0;
-}
-
-static jbyteArray NetworkInterface_getHardwareAddressImpl(JNIEnv* env, jclass, jstring name) {
- ifreq ifr;
- if (!doIoctl(env, name, SIOCGIFHWADDR, ifr)) {
- return NULL;
- }
- jbyte bytes[IFHWADDRLEN];
- bool isEmpty = true;
- for (int i = 0; i < IFHWADDRLEN; ++i) {
- bytes[i] = ifr.ifr_hwaddr.sa_data[i];
- if (bytes[i] != 0) {
- isEmpty = false;
- }
- }
- if (isEmpty) {
- return NULL;
- }
- jbyteArray result = env->NewByteArray(IFHWADDRLEN);
- env->SetByteArrayRegion(result, 0, IFHWADDRLEN, bytes);
- return result;
-}
-
-static jint NetworkInterface_getMTUImpl(JNIEnv* env, jclass, jstring name) {
- ifreq ifr;
- doIoctl(env, name, SIOCGIFMTU, ifr); // May throw.
- return ifr.ifr_mtu;
-}
-
-static jboolean NetworkInterface_isLoopbackImpl(JNIEnv* env, jclass, jstring name) {
- return hasFlag(env, name, IFF_LOOPBACK);
-}
-
-static jboolean NetworkInterface_isPointToPointImpl(JNIEnv* env, jclass, jstring name) {
- return hasFlag(env, name, IFF_POINTOPOINT); // Unix API typo!
-}
-
-static jboolean NetworkInterface_isUpImpl(JNIEnv* env, jclass, jstring name) {
- return hasFlag(env, name, IFF_UP);
-}
-
-static jboolean NetworkInterface_supportsMulticastImpl(JNIEnv* env, jclass, jstring name) {
- return hasFlag(env, name, IFF_MULTICAST);
-}
-
-static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(NetworkInterface, getAllInterfaceAddressesImpl, "()[Ljava/net/InterfaceAddress;"),
- NATIVE_METHOD(NetworkInterface, getHardwareAddressImpl, "(Ljava/lang/String;)[B"),
- NATIVE_METHOD(NetworkInterface, getMTUImpl, "(Ljava/lang/String;)I"),
- NATIVE_METHOD(NetworkInterface, isLoopbackImpl, "(Ljava/lang/String;)Z"),
- NATIVE_METHOD(NetworkInterface, isPointToPointImpl, "(Ljava/lang/String;)Z"),
- NATIVE_METHOD(NetworkInterface, isUpImpl, "(Ljava/lang/String;)Z"),
- NATIVE_METHOD(NetworkInterface, supportsMulticastImpl, "(Ljava/lang/String;)Z"),
-};
-int register_java_net_NetworkInterface(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "java/net/NetworkInterface", gMethods, NELEM(gMethods));
-}
diff --git a/luni/src/main/native/java_text_Bidi.cpp b/luni/src/main/native/java_text_Bidi.cpp
index f44fe3d..27d3ddb 100644
--- a/luni/src/main/native/java_text_Bidi.cpp
+++ b/luni/src/main/native/java_text_Bidi.cpp
@@ -17,9 +17,9 @@
#define LOG_TAG "Bidi"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedPrimitiveArray.h"
#include "UniquePtr.h"
#include "unicode/ubidi.h"
@@ -88,18 +88,18 @@
}
UErrorCode err = U_ZERO_ERROR;
ubidi_setPara(data->uBiDi(), chars.get(), length, paraLevel, data->embeddingLevels(), &err);
- icu4jni_error(env, err);
+ maybeThrowIcuException(env, err);
}
static jlong Bidi_ubidi_setLine(JNIEnv* env, jclass, jlong ptr, jint start, jint limit) {
- UErrorCode err = U_ZERO_ERROR;
- UBiDi* sized = ubidi_openSized(limit - start, 0, &err);
- if (icu4jni_error(env, err) != FALSE) {
+ UErrorCode status = U_ZERO_ERROR;
+ UBiDi* sized = ubidi_openSized(limit - start, 0, &status);
+ if (maybeThrowIcuException(env, status)) {
return 0;
}
UniquePtr<BiDiData> lineData(new BiDiData(sized));
- ubidi_setLine(uBiDi(ptr), start, limit, lineData->uBiDi(), &err);
- icu4jni_error(env, err);
+ ubidi_setLine(uBiDi(ptr), start, limit, lineData->uBiDi(), &status);
+ maybeThrowIcuException(env, status);
return reinterpret_cast<uintptr_t>(lineData.release());
}
@@ -116,9 +116,9 @@
}
static jbyteArray Bidi_ubidi_getLevels(JNIEnv* env, jclass, jlong ptr) {
- UErrorCode err = U_ZERO_ERROR;
- const UBiDiLevel* levels = ubidi_getLevels(uBiDi(ptr), &err);
- if (icu4jni_error(env, err)) {
+ UErrorCode status = U_ZERO_ERROR;
+ const UBiDiLevel* levels = ubidi_getLevels(uBiDi(ptr), &status);
+ if (maybeThrowIcuException(env, status)) {
return NULL;
}
int len = ubidi_getLength(uBiDi(ptr));
@@ -128,9 +128,9 @@
}
static jint Bidi_ubidi_countRuns(JNIEnv* env, jclass, jlong ptr) {
- UErrorCode err = U_ZERO_ERROR;
- int count = ubidi_countRuns(uBiDi(ptr), &err);
- icu4jni_error(env, err);
+ UErrorCode status = U_ZERO_ERROR;
+ int count = ubidi_countRuns(uBiDi(ptr), &status);
+ maybeThrowIcuException(env, status);
return count;
}
@@ -139,9 +139,9 @@
*/
static jobjectArray Bidi_ubidi_getRuns(JNIEnv* env, jclass, jlong ptr) {
UBiDi* ubidi = uBiDi(ptr);
- UErrorCode err = U_ZERO_ERROR;
- int runCount = ubidi_countRuns(ubidi, &err);
- if (icu4jni_error(env, err)) {
+ UErrorCode status = U_ZERO_ERROR;
+ int runCount = ubidi_countRuns(ubidi, &status);
+ if (maybeThrowIcuException(env, status)) {
return NULL;
}
jmethodID bidiRunConstructor = env->GetMethodID(JniConstants::bidiRunClass, "<init>", "(III)V");
diff --git a/luni/src/main/native/java_util_regex_Matcher.cpp b/luni/src/main/native/java_util_regex_Matcher.cpp
index 2af1932..024a6f0 100644
--- a/luni/src/main/native/java_util_regex_Matcher.cpp
+++ b/luni/src/main/native/java_util_regex_Matcher.cpp
@@ -18,10 +18,9 @@
#include <stdlib.h>
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
-#include "ScopedJavaUnicodeString.h"
+#include "JniException.h"
#include "ScopedPrimitiveArray.h"
#include "UniquePtr.h"
#include "jni.h"
@@ -72,7 +71,7 @@
if (mJavaInput) {
mEnv->ReleaseStringChars(mJavaInput, mChars);
}
- icu4jni_error(mEnv, mStatus);
+ maybeThrowIcuException(mEnv, mStatus);
}
RegexMatcher* operator->() {
@@ -174,7 +173,7 @@
RegexPattern* pattern = reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(patternAddr));
UErrorCode status = U_ZERO_ERROR;
RegexMatcher* result = pattern->matcher(status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return static_cast<jint>(reinterpret_cast<uintptr_t>(result));
}
diff --git a/luni/src/main/native/java_util_regex_Pattern.cpp b/luni/src/main/native/java_util_regex_Pattern.cpp
index 5541d33..1e0e1e3 100644
--- a/luni/src/main/native/java_util_regex_Pattern.cpp
+++ b/luni/src/main/native/java_util_regex_Pattern.cpp
@@ -18,7 +18,6 @@
#include <stdlib.h>
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
#include "ScopedJavaUnicodeString.h"
@@ -32,10 +31,41 @@
return reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(addr));
}
+static const char* regexDetailMessage(UErrorCode status) {
+ // These human-readable error messages were culled from "utypes.h", and then slightly tuned
+ // to make more sense in context.
+ // If we don't have a special-case, we'll just return the textual name of
+ // the enum value (such as U_REGEX_RULE_SYNTAX), which is better than nothing.
+ switch (status) {
+ case U_REGEX_INTERNAL_ERROR: return "An internal error was detected";
+ case U_REGEX_RULE_SYNTAX: return "Syntax error in regexp pattern";
+ case U_REGEX_INVALID_STATE: return "Matcher in invalid state for requested operation";
+ case U_REGEX_BAD_ESCAPE_SEQUENCE: return "Unrecognized backslash escape sequence in pattern";
+ case U_REGEX_PROPERTY_SYNTAX: return "Incorrect Unicode property";
+ case U_REGEX_UNIMPLEMENTED: return "Use of unimplemented feature";
+ case U_REGEX_MISMATCHED_PAREN: return "Incorrectly nested parentheses in regexp pattern";
+ case U_REGEX_NUMBER_TOO_BIG: return "Decimal number is too large";
+ case U_REGEX_BAD_INTERVAL: return "Error in {min,max} interval";
+ case U_REGEX_MAX_LT_MIN: return "In {min,max}, max is less than min";
+ case U_REGEX_INVALID_BACK_REF: return "Back-reference to a non-existent capture group";
+ case U_REGEX_INVALID_FLAG: return "Invalid value for match mode flags";
+ case U_REGEX_LOOK_BEHIND_LIMIT: return "Look-behind pattern matches must have a bounded maximum length";
+ case U_REGEX_SET_CONTAINS_STRING: return "Regular expressions cannot have UnicodeSets containing strings";
+ case U_REGEX_OCTAL_TOO_BIG: return "Octal character constants must be <= 0377.";
+ case U_REGEX_MISSING_CLOSE_BRACKET: return "Missing closing bracket in character class";
+ case U_REGEX_INVALID_RANGE: return "In a character range [x-y], x is greater than y";
+ case U_REGEX_STACK_OVERFLOW: return "Regular expression backtrack stack overflow";
+ case U_REGEX_TIME_OUT: return "Maximum allowed match time exceeded";
+ case U_REGEX_STOPPED_BY_CALLER: return "Matching operation aborted by user callback function";
+ default:
+ return u_errorName(status);
+ }
+}
+
static void throwPatternSyntaxException(JNIEnv* env, UErrorCode status, jstring pattern, UParseError error) {
static jmethodID method = env->GetMethodID(JniConstants::patternSyntaxExceptionClass,
"<init>", "(Ljava/lang/String;Ljava/lang/String;I)V");
- jstring message = env->NewStringUTF(u_errorName(status));
+ jstring message = env->NewStringUTF(regexDetailMessage(status));
jclass exceptionClass = JniConstants::patternSyntaxExceptionClass;
jobject exception = env->NewObject(exceptionClass, method, message, pattern, error.offset);
env->Throw(reinterpret_cast<jthrowable>(exception));
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index f04238e..7329b8f 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -16,9 +16,9 @@
#define LOG_TAG "ICU"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedFd.h"
#include "ScopedJavaUnicodeString.h"
#include "ScopedLocalRef.h"
@@ -551,7 +551,7 @@
EnumerationCounter counter(uenum_count(e, &status));
EnumerationGetter getter(e, &status);
jobject result = toStringArray16(env, &counter, &getter);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
uenum_close(e);
return result;
}
diff --git a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
index d430ede..18dca0d 100644
--- a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
+++ b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp
@@ -18,7 +18,6 @@
#include "JNIHelp.h"
#include "JniConstants.h"
-#include "ErrorCode.h"
#include "JniException.h"
#include "ScopedUtfChars.h"
#include "unicode/ubrk.h"
@@ -56,14 +55,14 @@
size_t charCount = env->GetStringLength(mString);
UErrorCode status = U_ZERO_ERROR;
ubrk_setText(mIt, mChars, charCount, &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
}
BreakIteratorPeer* clone(JNIEnv* env) {
UErrorCode status = U_ZERO_ERROR;
jint bufferSize = U_BRK_SAFECLONE_BUFFERSIZE;
UBreakIterator* it = ubrk_safeClone(mIt, NULL, &bufferSize, &status);
- if (icu4jni_error(env, status)) {
+ if (maybeThrowIcuException(env, status)) {
return NULL;
}
BreakIteratorPeer* result = new BreakIteratorPeer(it);
@@ -121,7 +120,7 @@
return 0;
}
UBreakIterator* it = ubrk_open(type, localeChars.c_str(), NULL, 0, &status);
- if (icu4jni_error(env, status)) {
+ if (maybeThrowIcuException(env, status)) {
return NULL;
}
return (new BreakIteratorPeer(it))->toAddress();
diff --git a/luni/src/main/native/libcore_icu_NativeCollation.cpp b/luni/src/main/native/libcore_icu_NativeCollation.cpp
index 60c6488..fa46d59 100644
--- a/luni/src/main/native/libcore_icu_NativeCollation.cpp
+++ b/luni/src/main/native/libcore_icu_NativeCollation.cpp
@@ -9,10 +9,10 @@
#define LOG_TAG "NativeCollation"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
-#include "ScopedJavaUnicodeString.h"
+#include "JniException.h"
+#include "ScopedStringChars.h"
#include "ScopedUtfChars.h"
#include "UniquePtr.h"
#include "ucol_imp.h"
@@ -35,27 +35,33 @@
ucol_closeElements(toCollationElements(address));
}
-static jint NativeCollation_compare(JNIEnv* env, jclass, jint address, jstring lhs0, jstring rhs0) {
- ScopedJavaUnicodeString lhs(env, lhs0);
- ScopedJavaUnicodeString rhs(env, rhs0);
- return ucol_strcoll(toCollator(address),
- lhs.unicodeString().getBuffer(), lhs.unicodeString().length(),
- rhs.unicodeString().getBuffer(), rhs.unicodeString().length());
+static jint NativeCollation_compare(JNIEnv* env, jclass, jint address, jstring javaLhs, jstring javaRhs) {
+ ScopedStringChars lhs(env, javaLhs);
+ if (lhs.get() == NULL) {
+ return NULL;
+ }
+ ScopedStringChars rhs(env, javaRhs);
+ if (rhs.get() == NULL) {
+ return NULL;
+ }
+ return ucol_strcoll(toCollator(address), lhs.get(), lhs.size(), rhs.get(), rhs.size());
}
static jint NativeCollation_getAttribute(JNIEnv* env, jclass, jint address, jint type) {
UErrorCode status = U_ZERO_ERROR;
jint result = ucol_getAttribute(toCollator(address), (UColAttribute) type, &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return result;
}
-static jint NativeCollation_getCollationElementIterator(JNIEnv* env, jclass, jint address, jstring source0) {
- ScopedJavaUnicodeString source(env, source0);
+static jint NativeCollation_getCollationElementIterator(JNIEnv* env, jclass, jint address, jstring javaSource) {
+ ScopedStringChars source(env, javaSource);
+ if (source.get() == NULL) {
+ return -1;
+ }
UErrorCode status = U_ZERO_ERROR;
- UCollationElements* result = ucol_openElements(toCollator(address),
- source.unicodeString().getBuffer(), source.unicodeString().length(), &status);
- icu4jni_error(env, status);
+ UCollationElements* result = ucol_openElements(toCollator(address), source.get(), source.size(), &status);
+ maybeThrowIcuException(env, status);
return static_cast<jint>(reinterpret_cast<uintptr_t>(result));
}
@@ -73,20 +79,21 @@
return env->NewString(rules, length);
}
-static jbyteArray NativeCollation_getSortKey(JNIEnv* env, jclass, jint address, jstring source0) {
- ScopedJavaUnicodeString source(env, source0);
+static jbyteArray NativeCollation_getSortKey(JNIEnv* env, jclass, jint address, jstring javaSource) {
+ ScopedStringChars source(env, javaSource);
+ if (source.get() == NULL) {
+ return NULL;
+ }
const UCollator* collator = toCollator(address);
uint8_t byteArray[UCOL_MAX_BUFFER * 2];
UniquePtr<uint8_t[]> largerByteArray;
uint8_t* usedByteArray = byteArray;
- const UChar* chars = source.unicodeString().getBuffer();
- size_t charCount = source.unicodeString().length();
- size_t byteArraySize = ucol_getSortKey(collator, chars, charCount, usedByteArray, sizeof(byteArray) - 1);
+ size_t byteArraySize = ucol_getSortKey(collator, source.get(), source.size(), usedByteArray, sizeof(byteArray) - 1);
if (byteArraySize > sizeof(byteArray) - 1) {
// didn't fit, try again with a larger buffer.
largerByteArray.reset(new uint8_t[byteArraySize + 1]);
usedByteArray = largerByteArray.get();
- byteArraySize = ucol_getSortKey(collator, chars, charCount, usedByteArray, byteArraySize);
+ byteArraySize = ucol_getSortKey(collator, source.get(), source.size(), usedByteArray, byteArraySize);
}
if (byteArraySize == 0) {
return NULL;
@@ -99,7 +106,7 @@
static jint NativeCollation_next(JNIEnv* env, jclass, jint address) {
UErrorCode status = U_ZERO_ERROR;
jint result = ucol_next(toCollationElements(address), &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return result;
}
@@ -110,23 +117,26 @@
}
UErrorCode status = U_ZERO_ERROR;
UCollator* c = ucol_open(localeChars.c_str(), &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return static_cast<jint>(reinterpret_cast<uintptr_t>(c));
}
-static jint NativeCollation_openCollatorFromRules(JNIEnv* env, jclass, jstring rules0, jint mode, jint strength) {
- ScopedJavaUnicodeString rules(env, rules0);
+static jint NativeCollation_openCollatorFromRules(JNIEnv* env, jclass, jstring javaRules, jint mode, jint strength) {
+ ScopedStringChars rules(env, javaRules);
+ if (rules.get() == NULL) {
+ return -1;
+ }
UErrorCode status = U_ZERO_ERROR;
- UCollator* c = ucol_openRules(rules.unicodeString().getBuffer(), rules.unicodeString().length(),
+ UCollator* c = ucol_openRules(rules.get(), rules.size(),
UColAttributeValue(mode), UCollationStrength(strength), NULL, &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return static_cast<jint>(reinterpret_cast<uintptr_t>(c));
}
static jint NativeCollation_previous(JNIEnv* env, jclass, jint address) {
UErrorCode status = U_ZERO_ERROR;
jint result = ucol_previous(toCollationElements(address), &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return result;
}
@@ -138,28 +148,30 @@
UErrorCode status = U_ZERO_ERROR;
jint bufferSize = U_COL_SAFECLONE_BUFFERSIZE;
UCollator* c = ucol_safeClone(toCollator(address), NULL, &bufferSize, &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return static_cast<jint>(reinterpret_cast<uintptr_t>(c));
}
static void NativeCollation_setAttribute(JNIEnv* env, jclass, jint address, jint type, jint value) {
UErrorCode status = U_ZERO_ERROR;
ucol_setAttribute(toCollator(address), (UColAttribute)type, (UColAttributeValue)value, &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
}
static void NativeCollation_setOffset(JNIEnv* env, jclass, jint address, jint offset) {
UErrorCode status = U_ZERO_ERROR;
ucol_setOffset(toCollationElements(address), offset, &status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
}
-static void NativeCollation_setText(JNIEnv* env, jclass, jint address, jstring source0) {
- ScopedJavaUnicodeString source(env, source0);
+static void NativeCollation_setText(JNIEnv* env, jclass, jint address, jstring javaSource) {
+ ScopedStringChars source(env, javaSource);
+ if (source.get() == NULL) {
+ return;
+ }
UErrorCode status = U_ZERO_ERROR;
- ucol_setText(toCollationElements(address),
- source.unicodeString().getBuffer(), source.unicodeString().length(), &status);
- icu4jni_error(env, status);
+ ucol_setText(toCollationElements(address), source.get(), source.size(), &status);
+ maybeThrowIcuException(env, status);
}
static JNINativeMethod gMethods[] = {
diff --git a/luni/src/main/native/libcore_icu_NativeConverter.cpp b/luni/src/main/native/libcore_icu_NativeConverter.cpp
index 9ef1d04..6a7fbc5 100644
--- a/luni/src/main/native/libcore_icu_NativeConverter.cpp
+++ b/luni/src/main/native/libcore_icu_NativeConverter.cpp
@@ -15,9 +15,9 @@
#define LOG_TAG "NativeConverter"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedLocalRef.h"
#include "ScopedPrimitiveArray.h"
#include "ScopedStringChars.h"
@@ -72,9 +72,9 @@
if (converterNameChars.c_str() == NULL) {
return 0;
}
- UErrorCode errorCode = U_ZERO_ERROR;
- UConverter* cnv = ucnv_open(converterNameChars.c_str(), &errorCode);
- icu4jni_error(env, errorCode);
+ UErrorCode status = U_ZERO_ERROR;
+ UConverter* cnv = ucnv_open(converterNameChars.c_str(), &status);
+ maybeThrowIcuException(env, status);
return reinterpret_cast<uintptr_t>(cnv);
}
diff --git a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
index 51113fa..96ffad8 100644
--- a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
+++ b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp
@@ -16,11 +16,12 @@
#define LOG_TAG "NativeDecimalFormat"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedJavaUnicodeString.h"
#include "ScopedPrimitiveArray.h"
+#include "ScopedStringChars.h"
#include "ScopedUtfChars.h"
#include "UniquePtr.h"
#include "cutils/log.h"
@@ -110,7 +111,7 @@
if (fmt == NULL) {
delete symbols;
}
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return static_cast<jint>(reinterpret_cast<uintptr_t>(fmt));
}
@@ -125,12 +126,14 @@
}
static void NativeDecimalFormat_setSymbol(JNIEnv* env, jclass, jint addr, jint javaSymbol, jstring javaValue) {
- ScopedJavaUnicodeString value(env, javaValue);
- UnicodeString& s(value.unicodeString());
+ ScopedStringChars value(env, javaValue);
+ if (value.get() == NULL) {
+ return;
+ }
UErrorCode status = U_ZERO_ERROR;
UNumberFormatSymbol symbol = static_cast<UNumberFormatSymbol>(javaSymbol);
- unum_setSymbol(toUNumberFormat(addr), symbol, s.getBuffer(), s.length(), &status);
- icu4jni_error(env, status);
+ unum_setSymbol(toUNumberFormat(addr), symbol, value.get(), value.size(), &status);
+ maybeThrowIcuException(env, status);
}
static void NativeDecimalFormat_setAttribute(JNIEnv*, jclass, jint addr, jint javaAttr, jint value) {
@@ -144,12 +147,14 @@
}
static void NativeDecimalFormat_setTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr, jstring javaValue) {
- ScopedJavaUnicodeString value(env, javaValue);
- UnicodeString& s(value.unicodeString());
+ ScopedStringChars value(env, javaValue);
+ if (value.get() == NULL) {
+ return;
+ }
UErrorCode status = U_ZERO_ERROR;
UNumberFormatTextAttribute attr = static_cast<UNumberFormatTextAttribute>(javaAttr);
- unum_setTextAttribute(toUNumberFormat(addr), attr, s.getBuffer(), s.length(), &status);
- icu4jni_error(env, status);
+ unum_setTextAttribute(toUNumberFormat(addr), attr, value.get(), value.size(), &status);
+ maybeThrowIcuException(env, status);
}
static jstring NativeDecimalFormat_getTextAttribute(JNIEnv* env, jclass, jint addr, jint javaAttr) {
@@ -168,7 +173,7 @@
chars.reset(new UChar[charCount]);
charCount = unum_getTextAttribute(fmt, attr, chars.get(), charCount, &status);
}
- return icu4jni_error(env, status) ? NULL : env->NewString(chars.get(), charCount);
+ return maybeThrowIcuException(env, status) ? NULL : env->NewString(chars.get(), charCount);
}
static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized, jstring pattern0) {
@@ -184,7 +189,7 @@
} else {
fmt->applyPattern(pattern.unicodeString(), status);
}
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
}
static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jint addr, jboolean localized) {
diff --git a/luni/src/main/native/libcore_icu_NativeIDN.cpp b/luni/src/main/native/libcore_icu_NativeIDN.cpp
index 118bd7a..3d0cba3 100644
--- a/luni/src/main/native/libcore_icu_NativeIDN.cpp
+++ b/luni/src/main/native/libcore_icu_NativeIDN.cpp
@@ -16,10 +16,9 @@
#define LOG_TAG "NativeIDN"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
-#include "ScopedJavaUnicodeString.h"
+#include "ScopedStringChars.h"
#include "unicode/uidna.h"
static bool isLabelSeparator(const UChar ch) {
@@ -33,15 +32,16 @@
}
}
-static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring s, jint flags, jboolean toAscii) {
- ScopedJavaUnicodeString sus(env, s);
- const UChar* src = sus.unicodeString().getBuffer();
- const size_t srcLength = sus.unicodeString().length();
+static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring javaSrc, jint flags, jboolean toAscii) {
+ ScopedStringChars src(env, javaSrc);
+ if (src.get() == NULL) {
+ return NULL;
+ }
UChar dst[256];
UErrorCode status = U_ZERO_ERROR;
size_t resultLength = toAscii
- ? uidna_IDNToASCII(src, srcLength, &dst[0], sizeof(dst), flags, NULL, &status)
- : uidna_IDNToUnicode(src, srcLength, &dst[0], sizeof(dst), flags, NULL, &status);
+ ? uidna_IDNToASCII(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status)
+ : uidna_IDNToUnicode(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status);
if (U_FAILURE(status)) {
jniThrowException(env, "java/lang/IllegalArgumentException", u_errorName(status));
return NULL;
diff --git a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
index 244c5be..57f31f4 100644
--- a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
+++ b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp
@@ -16,28 +16,28 @@
#define LOG_TAG "NativeNormalizer"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedJavaUnicodeString.h"
#include "unicode/normlzr.h"
static jstring NativeNormalizer_normalizeImpl(JNIEnv* env, jclass, jstring s, jint intMode) {
ScopedJavaUnicodeString src(env, s);
UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
- UErrorCode errorCode = U_ZERO_ERROR;
+ UErrorCode status = U_ZERO_ERROR;
UnicodeString dst;
- Normalizer::normalize(src.unicodeString(), mode, 0, dst, errorCode);
- icu4jni_error(env, errorCode);
+ Normalizer::normalize(src.unicodeString(), mode, 0, dst, status);
+ maybeThrowIcuException(env, status);
return dst.isBogus() ? NULL : env->NewString(dst.getBuffer(), dst.length());
}
static jboolean NativeNormalizer_isNormalizedImpl(JNIEnv* env, jclass, jstring s, jint intMode) {
ScopedJavaUnicodeString src(env, s);
UNormalizationMode mode = static_cast<UNormalizationMode>(intMode);
- UErrorCode errorCode = U_ZERO_ERROR;
- UBool result = Normalizer::isNormalized(src.unicodeString(), mode, errorCode);
- icu4jni_error(env, errorCode);
+ UErrorCode status = U_ZERO_ERROR;
+ UBool result = Normalizer::isNormalized(src.unicodeString(), mode, status);
+ maybeThrowIcuException(env, status);
return result;
}
diff --git a/luni/src/main/native/libcore_icu_NativePluralRules.cpp b/luni/src/main/native/libcore_icu_NativePluralRules.cpp
index 5a74be9..3a443d7 100644
--- a/luni/src/main/native/libcore_icu_NativePluralRules.cpp
+++ b/luni/src/main/native/libcore_icu_NativePluralRules.cpp
@@ -16,9 +16,9 @@
#define LOG_TAG "NativePluralRules"
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedUtfChars.h"
#include "unicode/plurrule.h"
@@ -34,7 +34,7 @@
Locale locale = Locale::createFromName(ScopedUtfChars(env, localeName).c_str());
UErrorCode status = U_ZERO_ERROR;
PluralRules* result = PluralRules::forLocale(locale, status);
- icu4jni_error(env, status);
+ maybeThrowIcuException(env, status);
return reinterpret_cast<uintptr_t>(result);
}
diff --git a/luni/src/main/native/libcore_icu_TimeZones.cpp b/luni/src/main/native/libcore_icu_TimeZones.cpp
index e8d54cc..9da108d 100644
--- a/luni/src/main/native/libcore_icu_TimeZones.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZones.cpp
@@ -19,9 +19,9 @@
#include <map>
#include <vector>
-#include "ErrorCode.h"
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "JniException.h"
#include "ScopedJavaUnicodeString.h"
#include "ScopedLocalRef.h"
#include "ScopedUtfChars.h"
@@ -43,16 +43,14 @@
}
UErrorCode status = U_ZERO_ERROR;
int32_t idCount = ids->count(status);
- if (U_FAILURE(status)) {
- icu4jni_error(env, status);
+ if (maybeThrowIcuException(env, status)) {
return NULL;
}
jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringClass, NULL);
for (int32_t i = 0; i < idCount; ++i) {
const UnicodeString* id = ids->snext(status);
- if (U_FAILURE(status)) {
- icu4jni_error(env, status);
+ if (maybeThrowIcuException(env, status)) {
return NULL;
}
ScopedLocalRef<jstring> idString(env, env->NewString(id->getBuffer(), id->length()));
diff --git a/luni/src/main/native/libcore_io_Memory.cpp b/luni/src/main/native/libcore_io_Memory.cpp
index 13ac4a0..e86f133 100644
--- a/luni/src/main/native/libcore_io_Memory.cpp
+++ b/luni/src/main/native/libcore_io_Memory.cpp
@@ -18,6 +18,7 @@
#include "JNIHelp.h"
#include "JniConstants.h"
+#include "ScopedBytes.h"
#include "ScopedPrimitiveArray.h"
#include "UniquePtr.h"
@@ -76,8 +77,16 @@
}
}
-static void Memory_memmove(JNIEnv*, jclass, jint dstAddress, jint srcAddress, jlong length) {
- memmove(cast<void*>(dstAddress), cast<const void*>(srcAddress), length);
+static void Memory_memmove(JNIEnv* env, jclass, jobject dstObject, jint dstOffset, jobject srcObject, jint srcOffset, jlong length) {
+ ScopedBytesRW dstBytes(env, dstObject);
+ if (dstBytes.get() == NULL) {
+ return;
+ }
+ ScopedBytesRO srcBytes(env, srcObject);
+ if (srcBytes.get() == NULL) {
+ return;
+ }
+ memmove(dstBytes.get() + dstOffset, srcBytes.get() + srcOffset, length);
}
static jbyte Memory_peekByte(JNIEnv*, jclass, jint srcAddress) {
@@ -316,7 +325,7 @@
}
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(Memory, memmove, "(IIJ)V"),
+ NATIVE_METHOD(Memory, memmove, "(Ljava/lang/Object;ILjava/lang/Object;IJ)V"),
NATIVE_METHOD(Memory, peekByte, "(I)B"),
NATIVE_METHOD(Memory, peekByteArray, "(I[BII)V"),
NATIVE_METHOD(Memory, peekCharArray, "(I[CIIZ)V"),
diff --git a/luni/src/main/native/libcore_io_OsConstants.cpp b/luni/src/main/native/libcore_io_OsConstants.cpp
index 6165256..dd8ebf2 100644
--- a/luni/src/main/native/libcore_io_OsConstants.cpp
+++ b/luni/src/main/native/libcore_io_OsConstants.cpp
@@ -22,8 +22,11 @@
#include <errno.h>
#include <fcntl.h>
+#include <net/if.h>
+#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
+#include <signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
@@ -42,12 +45,30 @@
initConstant(env, c, "AF_INET6", AF_INET6);
initConstant(env, c, "AF_UNIX", AF_UNIX);
initConstant(env, c, "AF_UNSPEC", AF_UNSPEC);
+ initConstant(env, c, "AI_ADDRCONFIG", AI_ADDRCONFIG);
+ initConstant(env, c, "AI_ALL", AI_ALL);
+ initConstant(env, c, "AI_CANONNAME", AI_CANONNAME);
+ initConstant(env, c, "AI_NUMERICHOST", AI_NUMERICHOST);
+ initConstant(env, c, "AI_NUMERICSERV", AI_NUMERICSERV);
+ initConstant(env, c, "AI_PASSIVE", AI_PASSIVE);
+ initConstant(env, c, "AI_V4MAPPED", AI_V4MAPPED);
initConstant(env, c, "E2BIG", E2BIG);
initConstant(env, c, "EACCES", EACCES);
initConstant(env, c, "EADDRINUSE", EADDRINUSE);
initConstant(env, c, "EADDRNOTAVAIL", EADDRNOTAVAIL);
initConstant(env, c, "EAFNOSUPPORT", EAFNOSUPPORT);
initConstant(env, c, "EAGAIN", EAGAIN);
+ initConstant(env, c, "EAI_AGAIN", EAI_AGAIN);
+ initConstant(env, c, "EAI_BADFLAGS", EAI_BADFLAGS);
+ initConstant(env, c, "EAI_FAIL", EAI_FAIL);
+ initConstant(env, c, "EAI_FAMILY", EAI_FAMILY);
+ initConstant(env, c, "EAI_MEMORY", EAI_MEMORY);
+ initConstant(env, c, "EAI_NODATA", EAI_NODATA);
+ initConstant(env, c, "EAI_NONAME", EAI_NONAME);
+ initConstant(env, c, "EAI_OVERFLOW", EAI_OVERFLOW);
+ initConstant(env, c, "EAI_SERVICE", EAI_SERVICE);
+ initConstant(env, c, "EAI_SOCKTYPE", EAI_SOCKTYPE);
+ initConstant(env, c, "EAI_SYSTEM", EAI_SYSTEM);
initConstant(env, c, "EALREADY", EALREADY);
initConstant(env, c, "EBADF", EBADF);
initConstant(env, c, "EBADMSG", EBADMSG);
@@ -142,6 +163,22 @@
initConstant(env, c, "F_SETOWN", F_SETOWN);
initConstant(env, c, "F_UNLCK", F_UNLCK);
initConstant(env, c, "F_WRLCK", F_WRLCK);
+ initConstant(env, c, "IFF_ALLMULTI", IFF_ALLMULTI);
+ initConstant(env, c, "IFF_AUTOMEDIA", IFF_AUTOMEDIA);
+ initConstant(env, c, "IFF_BROADCAST", IFF_BROADCAST);
+ initConstant(env, c, "IFF_DEBUG", IFF_DEBUG);
+ initConstant(env, c, "IFF_DYNAMIC", IFF_DYNAMIC);
+ initConstant(env, c, "IFF_LOOPBACK", IFF_LOOPBACK);
+ initConstant(env, c, "IFF_MASTER", IFF_MASTER);
+ initConstant(env, c, "IFF_MULTICAST", IFF_MULTICAST);
+ initConstant(env, c, "IFF_NOARP", IFF_NOARP);
+ initConstant(env, c, "IFF_NOTRAILERS", IFF_NOTRAILERS);
+ initConstant(env, c, "IFF_POINTOPOINT", IFF_POINTOPOINT);
+ initConstant(env, c, "IFF_PORTSEL", IFF_PORTSEL);
+ initConstant(env, c, "IFF_PROMISC", IFF_PROMISC);
+ initConstant(env, c, "IFF_RUNNING", IFF_RUNNING);
+ initConstant(env, c, "IFF_SLAVE", IFF_SLAVE);
+ initConstant(env, c, "IFF_UP", IFF_UP);
initConstant(env, c, "IPPROTO_ICMP", IPPROTO_ICMP);
initConstant(env, c, "IPPROTO_IP", IPPROTO_IP);
initConstant(env, c, "IPPROTO_IPV6", IPPROTO_IPV6);
@@ -183,6 +220,11 @@
initConstant(env, c, "MS_ASYNC", MS_ASYNC);
initConstant(env, c, "MS_INVALIDATE", MS_INVALIDATE);
initConstant(env, c, "MS_SYNC", MS_SYNC);
+ initConstant(env, c, "NI_DGRAM", NI_DGRAM);
+ initConstant(env, c, "NI_NAMEREQD", NI_NAMEREQD);
+ initConstant(env, c, "NI_NOFQDN", NI_NOFQDN);
+ initConstant(env, c, "NI_NUMERICHOST", NI_NUMERICHOST);
+ initConstant(env, c, "NI_NUMERICSERV", NI_NUMERICSERV);
initConstant(env, c, "O_ACCMODE", O_ACCMODE);
initConstant(env, c, "O_APPEND", O_APPEND);
initConstant(env, c, "O_CREAT", O_CREAT);
@@ -205,11 +247,49 @@
initConstant(env, c, "SHUT_RD", SHUT_RD);
initConstant(env, c, "SHUT_RDWR", SHUT_RDWR);
initConstant(env, c, "SHUT_WR", SHUT_WR);
+ initConstant(env, c, "SIGABRT", SIGABRT);
+ initConstant(env, c, "SIGALRM", SIGALRM);
+ initConstant(env, c, "SIGBUS", SIGBUS);
+ initConstant(env, c, "SIGCHLD", SIGCHLD);
+ initConstant(env, c, "SIGCONT", SIGCONT);
+ initConstant(env, c, "SIGFPE", SIGFPE);
+ initConstant(env, c, "SIGHUP", SIGHUP);
+ initConstant(env, c, "SIGILL", SIGILL);
+ initConstant(env, c, "SIGINT", SIGINT);
+ initConstant(env, c, "SIGIO", SIGIO);
+ initConstant(env, c, "SIGKILL", SIGKILL);
+ initConstant(env, c, "SIGPIPE", SIGPIPE);
+ initConstant(env, c, "SIGPROF", SIGPROF);
+ initConstant(env, c, "SIGPWR", SIGPWR);
+ initConstant(env, c, "SIGQUIT", SIGQUIT);
+ initConstant(env, c, "SIGRTMAX", SIGRTMAX);
+ initConstant(env, c, "SIGRTMIN", SIGRTMIN);
+ initConstant(env, c, "SIGSEGV", SIGSEGV);
+ initConstant(env, c, "SIGSTKFLT", SIGSTKFLT);
+ initConstant(env, c, "SIGSTOP", SIGSTOP);
+ initConstant(env, c, "SIGSYS", SIGSYS);
+ initConstant(env, c, "SIGTERM", SIGTERM);
+ initConstant(env, c, "SIGTRAP", SIGTRAP);
+ initConstant(env, c, "SIGTSTP", SIGTSTP);
+ initConstant(env, c, "SIGTTIN", SIGTTIN);
+ initConstant(env, c, "SIGTTOU", SIGTTOU);
+ initConstant(env, c, "SIGURG", SIGURG);
+ initConstant(env, c, "SIGUSR1", SIGUSR1);
+ initConstant(env, c, "SIGUSR2", SIGUSR2);
+ initConstant(env, c, "SIGVTALRM", SIGVTALRM);
+ initConstant(env, c, "SIGWINCH", SIGWINCH);
+ initConstant(env, c, "SIGXCPU", SIGXCPU);
+ initConstant(env, c, "SIGXFSZ", SIGXFSZ);
+ initConstant(env, c, "SIOCGIFADDR", SIOCGIFADDR);
+ initConstant(env, c, "SIOCGIFBRDADDR", SIOCGIFBRDADDR);
+ initConstant(env, c, "SIOCGIFDSTADDR", SIOCGIFDSTADDR);
+ initConstant(env, c, "SIOCGIFNETMASK", SIOCGIFNETMASK);
initConstant(env, c, "SOCK_DGRAM", SOCK_DGRAM);
initConstant(env, c, "SOCK_RAW", SOCK_RAW);
initConstant(env, c, "SOCK_SEQPACKET", SOCK_SEQPACKET);
initConstant(env, c, "SOCK_STREAM", SOCK_STREAM);
initConstant(env, c, "SOL_SOCKET", SOL_SOCKET);
+ initConstant(env, c, "SO_BINDTODEVICE", SO_BINDTODEVICE);
initConstant(env, c, "SO_BROADCAST", SO_BROADCAST);
initConstant(env, c, "SO_DEBUG", SO_DEBUG);
initConstant(env, c, "SO_DONTROUTE", SO_DONTROUTE);
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index 6c86011..aa69eac 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -20,19 +20,27 @@
#include "JniConstants.h"
#include "JniException.h"
#include "NetworkUtilities.h"
+#include "ScopedBytes.h"
#include "ScopedPrimitiveArray.h"
#include "ScopedUtfChars.h"
#include "StaticAssert.h"
+#include "UniquePtr.h"
#include "toStringArray.h"
+#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
+#include <net/if.h>
+#include <netdb.h>
#include <netinet/in.h>
+#include <netinet/in.h>
+#include <signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -41,17 +49,24 @@
#include <sys/vfs.h> // Bionic doesn't have <sys/statvfs.h>
#include <unistd.h>
-static void throwErrnoException(JNIEnv* env, const char* name) {
- int errnum = errno;
+struct addrinfo_deleter {
+ void operator()(addrinfo* p) const {
+ if (p != NULL) { // bionic's freeaddrinfo(3) crashes when passed NULL.
+ freeaddrinfo(p);
+ }
+ }
+};
+static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2,
+ const char* functionName, int error) {
jthrowable cause = NULL;
if (env->ExceptionCheck()) {
cause = env->ExceptionOccurred();
env->ExceptionClear();
}
- ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name));
- if (javaName.get() == NULL) {
+ ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+ if (detailMessage.get() == NULL) {
// Not really much we can do here. We're probably dead in the water,
// but let's try to stumble on...
env->ExceptionClear();
@@ -59,18 +74,36 @@
jobject exception;
if (cause != NULL) {
- static jmethodID ctor = env->GetMethodID(JniConstants::errnoExceptionClass, "<init>",
- "(Ljava/lang/String;ILjava/lang/Throwable;)V");
- exception = env->NewObject(JniConstants::errnoExceptionClass, ctor,
- javaName.get(), errnum, cause);
+ exception = env->NewObject(exceptionClass, ctor3, detailMessage.get(), error, cause);
} else {
- static jmethodID ctor = env->GetMethodID(JniConstants::errnoExceptionClass, "<init>",
- "(Ljava/lang/String;I)V");
- exception = env->NewObject(JniConstants::errnoExceptionClass, ctor, javaName.get(), errnum);
+ exception = env->NewObject(exceptionClass, ctor2, detailMessage.get(), error);
}
env->Throw(reinterpret_cast<jthrowable>(exception));
}
+static void throwErrnoException(JNIEnv* env, const char* functionName) {
+ int error = errno;
+ static jmethodID ctor3 = env->GetMethodID(JniConstants::errnoExceptionClass,
+ "<init>", "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+ static jmethodID ctor2 = env->GetMethodID(JniConstants::errnoExceptionClass,
+ "<init>", "(Ljava/lang/String;I)V");
+ throwException(env, JniConstants::errnoExceptionClass, ctor3, ctor2, functionName, error);
+}
+
+static void throwGaiException(JNIEnv* env, const char* functionName, int error) {
+ if (error == EAI_SYSTEM) {
+ // EAI_SYSTEM means "look at errno instead", so we want our GaiException to have the
+ // relevant ErrnoException as its cause.
+ throwErrnoException(env, functionName);
+ // Deliberately fall through to throw another exception...
+ }
+ static jmethodID ctor3 = env->GetMethodID(JniConstants::gaiExceptionClass,
+ "<init>", "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+ static jmethodID ctor2 = env->GetMethodID(JniConstants::gaiExceptionClass,
+ "<init>", "(Ljava/lang/String;I)V");
+ throwException(env, JniConstants::gaiExceptionClass, ctor3, ctor2, functionName, error);
+}
+
template <typename rc_t>
static rc_t throwIfMinusOne(JNIEnv* env, const char* name, rc_t rc) {
if (rc == rc_t(-1)) {
@@ -79,7 +112,7 @@
return rc;
}
-template <typename T>
+template <typename ScopedT>
class IoVec {
public:
IoVec(JNIEnv* env, size_t bufferCount) : mEnv(env), mBufferCount(bufferCount) {
@@ -101,16 +134,8 @@
// TODO: Linux actually has a 1024 buffer limit. glibc works around this, and we should too.
for (size_t i = 0; i < mBufferCount; ++i) {
jobject buffer = mEnv->GetObjectArrayElement(javaBuffers, i);
- jbyte* ptr;
- if (mEnv->IsInstanceOf(buffer, JniConstants::byteArrayClass)) {
- // We need to pin the array for the duration.
- jbyteArray byteArray = reinterpret_cast<jbyteArray>(buffer);
- mScopedByteArrays.push_back(new T(mEnv, byteArray));
- ptr = const_cast<jbyte*>(mScopedByteArrays.back()->get());
- } else {
- // A direct ByteBuffer is easier.
- ptr = reinterpret_cast<jbyte*>(mEnv->GetDirectBufferAddress(buffer));
- }
+ mScopedBuffers.push_back(new ScopedT(mEnv, buffer));
+ jbyte* ptr = const_cast<jbyte*>(mScopedBuffers.back()->get());
if (ptr == NULL) {
return false;
}
@@ -123,8 +148,8 @@
}
~IoVec() {
- for (size_t i = 0; i < mScopedByteArrays.size(); ++i) {
- delete mScopedByteArrays[i];
+ for (size_t i = 0; i < mScopedBuffers.size(); ++i) {
+ delete mScopedBuffers[i];
}
mEnv->PopLocalFrame(NULL);
}
@@ -141,7 +166,7 @@
JNIEnv* mEnv;
size_t mBufferCount;
std::vector<iovec> mIoVec;
- std::vector<T*> mScopedByteArrays;
+ std::vector<ScopedT*> mScopedBuffers;
};
static jobject makeInetSocketAddress(JNIEnv* env, const sockaddr_storage* ss, int port) {
@@ -222,6 +247,17 @@
sysname, nodename, release, version, machine);
};
+static bool fillIfreq(JNIEnv* env, jstring javaInterfaceName, struct ifreq& req) {
+ ScopedUtfChars interfaceName(env, javaInterfaceName);
+ if (interfaceName.c_str() == NULL) {
+ return false;
+ }
+ memset(&req, 0, sizeof(req));
+ strncpy(req.ifr_name, interfaceName.c_str(), sizeof(req.ifr_name));
+ req.ifr_name[sizeof(req.ifr_name) - 1] = '\0';
+ return true;
+}
+
static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
ScopedUtfChars path(env, javaPath);
if (path.c_str() == NULL) {
@@ -348,6 +384,76 @@
throwIfMinusOne(env, "ftruncate", TEMP_FAILURE_RETRY(ftruncate64(fd, length)));
}
+static jstring Posix_gai_strerror(JNIEnv* env, jobject, jint error) {
+ return env->NewStringUTF(gai_strerror(error));
+}
+
+static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jobject javaHints) {
+ ScopedUtfChars node(env, javaNode);
+ if (node.c_str() == NULL) {
+ return NULL;
+ }
+
+ static jfieldID flagsFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_flags", "I");
+ static jfieldID familyFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_family", "I");
+ static jfieldID socktypeFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_socktype", "I");
+ static jfieldID protocolFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_protocol", "I");
+
+ addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = env->GetIntField(javaHints, flagsFid);
+ hints.ai_family = env->GetIntField(javaHints, familyFid);
+ hints.ai_socktype = env->GetIntField(javaHints, socktypeFid);
+ hints.ai_protocol = env->GetIntField(javaHints, protocolFid);
+
+ addrinfo* addressList = NULL;
+ int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);
+ UniquePtr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);
+ if (rc != 0) {
+ throwGaiException(env, "getaddrinfo", rc);
+ return NULL;
+ }
+
+ // Count results so we know how to size the output array.
+ int addressCount = 0;
+ for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
+ if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
+ ++addressCount;
+ } else {
+ LOGE("getaddrinfo unexpected ai_family %i", ai->ai_family);
+ }
+ }
+ if (addressCount == 0) {
+ return NULL;
+ }
+
+ // Prepare output array.
+ jobjectArray result = env->NewObjectArray(addressCount, JniConstants::inetAddressClass, NULL);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ // Examine returned addresses one by one, save them in the output array.
+ int index = 0;
+ for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) {
+ if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
+ // Unknown address family. Skip this address.
+ LOGE("getaddrinfo unexpected ai_family %i", ai->ai_family);
+ continue;
+ }
+
+ // Convert each IP address into a Java byte array.
+ sockaddr_storage* address = reinterpret_cast<sockaddr_storage*>(ai->ai_addr);
+ ScopedLocalRef<jobject> inetAddress(env, socketAddressToInetAddress(env, address));
+ if (inetAddress.get() == NULL) {
+ return NULL;
+ }
+ env->SetObjectArrayElement(result, index, inetAddress.get());
+ ++index;
+ }
+ return result;
+}
+
static jstring Posix_getenv(JNIEnv* env, jobject, jstring javaName) {
ScopedUtfChars name(env, javaName);
if (name.c_str() == NULL) {
@@ -356,6 +462,25 @@
return env->NewStringUTF(getenv(name.c_str()));
}
+static jstring Posix_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint flags) {
+ sockaddr_storage ss;
+ if (!inetAddressToSocketAddress(env, javaAddress, 0, &ss)) {
+ return NULL;
+ }
+ // TODO: bionic's getnameinfo(3) seems to want its length parameter to be exactly
+ // sizeof(sockaddr_in) for an IPv4 address and sizeof (sockaddr_in6) for an
+ // IPv6 address. Fix getnameinfo so it accepts sizeof(sockaddr_storage), and
+ // then remove this hack.
+ socklen_t size = (ss.ss_family == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
+ char buf[NI_MAXHOST]; // NI_MAXHOST is longer than INET6_ADDRSTRLEN.
+ int rc = getnameinfo(reinterpret_cast<sockaddr*>(&ss), size, buf, sizeof(buf), NULL, 0, flags);
+ if (rc != 0) {
+ throwGaiException(env, "getnameinfo", rc);
+ return NULL;
+ }
+ return env->NewStringUTF(buf);
+}
+
static jobject Posix_getsockname(JNIEnv* env, jobject, jobject javaFd) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
sockaddr_storage ss;
@@ -427,6 +552,42 @@
return makeStructTimeval(env, tv);
}
+static jstring Posix_if_indextoname(JNIEnv* env, jobject, jint index) {
+ char buf[IF_NAMESIZE];
+ char* name = if_indextoname(index, buf);
+ // if_indextoname(3) returns NULL on failure, which will come out of NewStringUTF unscathed.
+ // There's no useful information in errno, so we don't bother throwing. Callers can null-check.
+ return env->NewStringUTF(name);
+}
+
+static jobject Posix_inet_aton(JNIEnv* env, jobject, jstring javaName) {
+ ScopedUtfChars name(env, javaName);
+ if (name.c_str() == NULL) {
+ return NULL;
+ }
+ sockaddr_storage ss;
+ memset(&ss, 0, sizeof(ss));
+ sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
+ if (inet_aton(name.c_str(), &sin->sin_addr) == 0) {
+ return NULL;
+ }
+ sin->sin_family = AF_INET; // inet_aton only supports IPv4.
+ return socketAddressToInetAddress(env, &ss);
+}
+
+static jobject Posix_ioctlInetAddress(JNIEnv* env, jobject, jobject javaFd, jint cmd, jstring javaInterfaceName) {
+ struct ifreq req;
+ if (!fillIfreq(env, javaInterfaceName, req)) {
+ return NULL;
+ }
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ int rc = throwIfMinusOne(env, "ioctl", TEMP_FAILURE_RETRY(ioctl(fd, cmd, &req)));
+ if (rc == -1) {
+ return NULL;
+ }
+ return socketAddressToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr));
+}
+
static jint Posix_ioctlInt(JNIEnv* env, jobject, jobject javaFd, jint cmd, jobject javaArg) {
// This is complicated because ioctls may return their result by updating their argument
// or via their return value, so we need to support both.
@@ -445,6 +606,10 @@
return TEMP_FAILURE_RETRY(isatty(fd)) == 0;
}
+static void Posix_kill(JNIEnv* env, jobject, jint pid, jint sig) {
+ throwIfMinusOne(env, "kill", TEMP_FAILURE_RETRY(kill(pid, sig)));
+}
+
static void Posix_listen(JNIEnv* env, jobject, jobject javaFd, jint backlog) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
throwIfMinusOne(env, "listen", TEMP_FAILURE_RETRY(listen(fd, backlog)));
@@ -536,23 +701,17 @@
return result;
}
-static jint Posix_read(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- ScopedByteArrayRW bytes(env, javaBytes);
+static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount) {
+ ScopedBytesRW bytes(env, javaBytes);
if (bytes.get() == NULL) {
return -1;
}
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
return throwIfMinusOne(env, "read", TEMP_FAILURE_RETRY(read(fd, bytes.get() + byteOffset, byteCount)));
}
-static jint Posix_readDirectBuffer(JNIEnv* env, jobject, jobject javaFd, jobject byteBuffer, jint position, jint remaining) {
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- jbyte* ptr = reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(byteBuffer));
- return throwIfMinusOne(env, "read", TEMP_FAILURE_RETRY(read(fd, ptr + position, remaining)));
-}
-
static jint Posix_readv(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffers, jintArray offsets, jintArray byteCounts) {
- IoVec<ScopedByteArrayRW> ioVec(env, env->GetArrayLength(buffers));
+ IoVec<ScopedBytesRW> ioVec(env, env->GetArrayLength(buffers));
if (!ioVec.init(buffers, offsets, byteCounts)) {
return -1;
}
@@ -598,11 +757,84 @@
return result;
}
+static void Posix_setsockoptByte(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ u_char byte = value;
+ throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &byte, sizeof(byte))));
+}
+
+static void Posix_setsockoptIfreq(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jstring javaInterfaceName) {
+ struct ifreq req;
+ if (!fillIfreq(env, javaInterfaceName, req)) {
+ return;
+ }
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &req, sizeof(req))));
+}
+
static void Posix_setsockoptInt(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
}
+static void Posix_setsockoptIpMreqn(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) {
+ ip_mreqn req;
+ memset(&req, 0, sizeof(req));
+ req.imr_ifindex = value;
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &req, sizeof(req))));
+}
+
+static void Posix_setsockoptGroupReq(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaGroupReq) {
+ struct group_req value;
+
+ static jfieldID grInterfaceFid = env->GetFieldID(JniConstants::structGroupReqClass, "gr_interface", "I");
+ value.gr_interface = env->GetIntField(javaGroupReq, grInterfaceFid);
+ // Get the IPv4 or IPv6 multicast address to join or leave.
+ static jfieldID grGroupFid = env->GetFieldID(JniConstants::structGroupReqClass, "gr_group", "Ljava/net/InetAddress;");
+ jobject javaGroup = env->GetObjectField(javaGroupReq, grGroupFid);
+ if (!inetAddressToSocketAddress(env, javaGroup, 0, &value.gr_group)) {
+ return;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ int rc = TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value)));
+ if (rc == -1 && errno == EINVAL) {
+ // Maybe we're a 32-bit binary talking to a 64-bit kernel?
+ // glibc doesn't automatically handle this.
+ struct GCC_HIDDEN group_req64 {
+ uint32_t gr_interface;
+ uint32_t my_padding;
+ sockaddr_storage gr_group;
+ };
+ group_req64 value64;
+ value64.gr_interface = value.gr_interface;
+ memcpy(&value64.gr_group, &value.gr_group, sizeof(value.gr_group));
+ rc = TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value64, sizeof(value64)));
+ }
+ throwIfMinusOne(env, "setsockopt", rc);
+}
+
+static void Posix_setsockoptLinger(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaLinger) {
+ static jfieldID lOnoffFid = env->GetFieldID(JniConstants::structLingerClass, "l_onoff", "I");
+ static jfieldID lLingerFid = env->GetFieldID(JniConstants::structLingerClass, "l_linger", "I");
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ struct linger value;
+ value.l_onoff = env->GetIntField(javaLinger, lOnoffFid);
+ value.l_linger = env->GetIntField(javaLinger, lLingerFid);
+ throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
+}
+
+static void Posix_setsockoptTimeval(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jobject javaTimeval) {
+ static jfieldID tvSecFid = env->GetFieldID(JniConstants::structTimevalClass, "tv_sec", "J");
+ static jfieldID tvUsecFid = env->GetFieldID(JniConstants::structTimevalClass, "tv_usec", "J");
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ struct timeval value;
+ value.tv_sec = env->GetIntField(javaTimeval, tvSecFid);
+ value.tv_usec = env->GetIntField(javaTimeval, tvUsecFid);
+ throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value))));
+}
+
static void Posix_shutdown(JNIEnv* env, jobject, jobject javaFd, jint how) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
throwIfMinusOne(env, "shutdown", TEMP_FAILURE_RETRY(shutdown(fd, how)));
@@ -667,23 +899,17 @@
return makeStructUtsname(env, buf);
}
-static jint Posix_write(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- ScopedByteArrayRO bytes(env, javaBytes);
+static jint Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) {
+ ScopedBytesRO bytes(env, javaBytes);
if (bytes.get() == NULL) {
return -1;
}
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, bytes.get() + byteOffset, byteCount)));
}
-static jint Posix_writeDirectBuffer(JNIEnv* env, jobject, jobject javaFd, jobject byteBuffer, jint position, jint remaining) {
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- jbyte* ptr = reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(byteBuffer));
- return throwIfMinusOne(env, "write", TEMP_FAILURE_RETRY(write(fd, ptr + position, remaining)));
-}
-
static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffers, jintArray offsets, jintArray byteCounts) {
- IoVec<ScopedByteArrayRO> ioVec(env, env->GetArrayLength(buffers));
+ IoVec<ScopedBytesRO> ioVec(env, env->GetArrayLength(buffers));
if (!ioVec.init(buffers, offsets, byteCounts)) {
return -1;
}
@@ -704,15 +930,22 @@
NATIVE_METHOD(Posix, fstatfs, "(Ljava/io/FileDescriptor;)Llibcore/io/StructStatFs;"),
NATIVE_METHOD(Posix, fsync, "(Ljava/io/FileDescriptor;)V"),
NATIVE_METHOD(Posix, ftruncate, "(Ljava/io/FileDescriptor;J)V"),
+ NATIVE_METHOD(Posix, gai_strerror, "(I)Ljava/lang/String;"),
+ NATIVE_METHOD(Posix, getaddrinfo, "(Ljava/lang/String;Llibcore/io/StructAddrinfo;)[Ljava/net/InetAddress;"),
NATIVE_METHOD(Posix, getenv, "(Ljava/lang/String;)Ljava/lang/String;"),
+ NATIVE_METHOD(Posix, getnameinfo, "(Ljava/net/InetAddress;I)Ljava/lang/String;"),
NATIVE_METHOD(Posix, getsockname, "(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;"),
NATIVE_METHOD(Posix, getsockoptByte, "(Ljava/io/FileDescriptor;II)I"),
NATIVE_METHOD(Posix, getsockoptInAddr, "(Ljava/io/FileDescriptor;II)Ljava/net/InetAddress;"),
NATIVE_METHOD(Posix, getsockoptInt, "(Ljava/io/FileDescriptor;II)I"),
NATIVE_METHOD(Posix, getsockoptLinger, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructLinger;"),
NATIVE_METHOD(Posix, getsockoptTimeval, "(Ljava/io/FileDescriptor;II)Llibcore/io/StructTimeval;"),
+ NATIVE_METHOD(Posix, if_indextoname, "(I)Ljava/lang/String;"),
+ NATIVE_METHOD(Posix, inet_aton, "(Ljava/lang/String;)Ljava/net/InetAddress;"),
+ NATIVE_METHOD(Posix, ioctlInetAddress, "(Ljava/io/FileDescriptor;ILjava/lang/String;)Ljava/net/InetAddress;"),
NATIVE_METHOD(Posix, ioctlInt, "(Ljava/io/FileDescriptor;ILlibcore/util/MutableInt;)I"),
NATIVE_METHOD(Posix, isatty, "(Ljava/io/FileDescriptor;)Z"),
+ NATIVE_METHOD(Posix, kill, "(II)V"),
NATIVE_METHOD(Posix, listen, "(Ljava/io/FileDescriptor;I)V"),
NATIVE_METHOD(Posix, lseek, "(Ljava/io/FileDescriptor;JI)J"),
NATIVE_METHOD(Posix, lstat, "(Ljava/lang/String;)Llibcore/io/StructStat;"),
@@ -725,13 +958,18 @@
NATIVE_METHOD(Posix, munmap, "(JJ)V"),
NATIVE_METHOD(Posix, open, "(Ljava/lang/String;II)Ljava/io/FileDescriptor;"),
NATIVE_METHOD(Posix, pipe, "()[Ljava/io/FileDescriptor;"),
- NATIVE_METHOD(Posix, read, "(Ljava/io/FileDescriptor;[BII)I"),
- NATIVE_METHOD(Posix, readDirectBuffer, "(Ljava/io/FileDescriptor;Ljava/nio/ByteBuffer;II)I"),
+ NATIVE_METHOD(Posix, readBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;II)I"),
NATIVE_METHOD(Posix, readv, "(Ljava/io/FileDescriptor;[Ljava/lang/Object;[I[I)I"),
NATIVE_METHOD(Posix, remove, "(Ljava/lang/String;)V"),
NATIVE_METHOD(Posix, rename, "(Ljava/lang/String;Ljava/lang/String;)V"),
NATIVE_METHOD(Posix, sendfile, "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Llibcore/util/MutableLong;J)J"),
+ NATIVE_METHOD(Posix, setsockoptByte, "(Ljava/io/FileDescriptor;III)V"),
+ NATIVE_METHOD(Posix, setsockoptIfreq, "(Ljava/io/FileDescriptor;IILjava/lang/String;)V"),
NATIVE_METHOD(Posix, setsockoptInt, "(Ljava/io/FileDescriptor;III)V"),
+ NATIVE_METHOD(Posix, setsockoptIpMreqn, "(Ljava/io/FileDescriptor;III)V"),
+ NATIVE_METHOD(Posix, setsockoptGroupReq, "(Ljava/io/FileDescriptor;IILlibcore/io/StructGroupReq;)V"),
+ NATIVE_METHOD(Posix, setsockoptLinger, "(Ljava/io/FileDescriptor;IILlibcore/io/StructLinger;)V"),
+ NATIVE_METHOD(Posix, setsockoptTimeval, "(Ljava/io/FileDescriptor;IILlibcore/io/StructTimeval;)V"),
NATIVE_METHOD(Posix, shutdown, "(Ljava/io/FileDescriptor;I)V"),
NATIVE_METHOD(Posix, socket, "(III)Ljava/io/FileDescriptor;"),
NATIVE_METHOD(Posix, stat, "(Ljava/lang/String;)Llibcore/io/StructStat;"),
@@ -740,8 +978,7 @@
NATIVE_METHOD(Posix, symlink, "(Ljava/lang/String;Ljava/lang/String;)V"),
NATIVE_METHOD(Posix, sysconf, "(I)J"),
NATIVE_METHOD(Posix, uname, "()Llibcore/io/StructUtsname;"),
- NATIVE_METHOD(Posix, write, "(Ljava/io/FileDescriptor;[BII)I"),
- NATIVE_METHOD(Posix, writeDirectBuffer, "(Ljava/io/FileDescriptor;Ljava/nio/ByteBuffer;II)I"),
+ NATIVE_METHOD(Posix, writeBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;II)I"),
NATIVE_METHOD(Posix, writev, "(Ljava/io/FileDescriptor;[Ljava/lang/Object;[I[I)I"),
};
int register_libcore_io_Posix(JNIEnv* env) {
diff --git a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
index b68f033..d7208b1 100644
--- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
+++ b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
@@ -40,29 +40,15 @@
#include <sys/un.h>
#include <unistd.h>
-#define JAVASOCKOPT_IP_MULTICAST_IF 16
-#define JAVASOCKOPT_IP_MULTICAST_IF2 31
-#define JAVASOCKOPT_IP_MULTICAST_LOOP 18
-#define JAVASOCKOPT_IP_TOS 3
-#define JAVASOCKOPT_MCAST_JOIN_GROUP 19
-#define JAVASOCKOPT_MCAST_LEAVE_GROUP 20
-#define JAVASOCKOPT_MULTICAST_TTL 17
-#define JAVASOCKOPT_SO_BROADCAST 32
-#define JAVASOCKOPT_SO_KEEPALIVE 8
-#define JAVASOCKOPT_SO_LINGER 128
-#define JAVASOCKOPT_SO_OOBINLINE 4099
-#define JAVASOCKOPT_SO_RCVBUF 4098
-#define JAVASOCKOPT_SO_TIMEOUT 4102
-#define JAVASOCKOPT_SO_REUSEADDR 4
-#define JAVASOCKOPT_SO_SNDBUF 4097
-#define JAVASOCKOPT_SO_BINDTODEVICE 8192
-#define JAVASOCKOPT_TCP_NODELAY 1
-
/* constants for OSNetworkSystem_selectImpl */
#define SOCKET_OP_NONE 0
#define SOCKET_OP_READ 1
#define SOCKET_OP_WRITE 2
+static void jniThrowSocketTimeoutException(JNIEnv* env, int error) {
+ jniThrowExceptionWithErrno(env, "java/net/SocketTimeoutException", error);
+}
+
/**
* Returns the port number in a sockaddr_storage structure.
*
@@ -81,80 +67,33 @@
}
}
-/**
- * Obtain the socket address family from an existing socket.
- *
- * @param socket the file descriptor of the socket to examine
- * @return an integer, the address family of the socket
- */
-static int getSocketAddressFamily(int socket) {
- sockaddr_storage ss;
- socklen_t namelen = sizeof(ss);
- int ret = getsockname(socket, reinterpret_cast<sockaddr*>(&ss), &namelen);
- if (ret != 0) {
- return AF_UNSPEC;
- } else {
- return ss.ss_family;
- }
-}
-
-// Handles translating between IPv4 and IPv6 addresses so -- where possible --
-// we can use either class of address with either an IPv4 or IPv6 socket.
+// Creates mapped addresses.
+// TODO: can this move to inetAddressToSocketAddress?
class CompatibleSocketAddress {
public:
- // Constructs an address corresponding to 'ss' that's compatible with 'fd'.
- CompatibleSocketAddress(int fd, const sockaddr_storage& ss, bool mapUnspecified) {
- const int desiredFamily = getSocketAddressFamily(fd);
- if (ss.ss_family == AF_INET6) {
- if (desiredFamily == AF_INET6) {
- // Nothing to do.
- mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
- } else {
- sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&mTmp);
- const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(&ss);
- memset(sin, 0, sizeof(*sin));
- sin->sin_family = AF_INET;
- sin->sin_port = sin6->sin6_port;
- if (IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr)) {
- // We have an IPv6-mapped IPv4 address, but need plain old IPv4.
- // Unmap the mapped address in ss into an IPv6 address in mTmp.
- memcpy(&sin->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
- mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
- } else if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
- // Translate the IPv6 loopback address to the IPv4 one.
- sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
- } else {
- // We can't help you. We return what you gave us, and assume you'll
- // get a sensible error when you use the address.
- mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
- }
+ CompatibleSocketAddress(const sockaddr_storage& ss, bool mapUnspecified) {
+ mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
+ if (ss.ss_family == AF_INET) {
+ // Map the IPv4 address in ss into an IPv6 address in mTmp.
+ const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
+ sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&mTmp);
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = sin->sin_port;
+ // TODO: mapUnspecified was introduced because kernels < 2.6.31 don't allow
+ // you to bind to ::ffff:0.0.0.0. When we move to something >= 2.6.31, we
+ // should make the code behave as if mapUnspecified were always true, and
+ // remove the parameter.
+ // TODO: this code still appears to be necessary on 2.6.32, so there's something
+ // wrong in the above comment.
+ if (sin->sin_addr.s_addr != 0 || mapUnspecified) {
+ memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
}
- } else /* ss.ss_family == AF_INET */ {
- if (desiredFamily == AF_INET) {
- // Nothing to do.
- mCompatibleAddress = reinterpret_cast<const sockaddr*>(&ss);
- } else {
- // We have IPv4 and need IPv6.
- // Map the IPv4 address in ss into an IPv6 address in mTmp.
- const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
- sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(&mTmp);
- memset(sin6, 0, sizeof(*sin6));
- sin6->sin6_family = AF_INET6;
- sin6->sin6_port = sin->sin_port;
- // TODO: mapUnspecified was introduced because kernels < 2.6.31 don't allow
- // you to bind to ::ffff:0.0.0.0. When we move to something >= 2.6.31, we
- // should make the code behave as if mapUnspecified were always true, and
- // remove the parameter.
- if (sin->sin_addr.s_addr != 0 || mapUnspecified) {
- memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2);
- }
- memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
- mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
- }
+ memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4);
+ mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp);
}
}
- // Returns a pointer to an address compatible with the socket.
+ // Returns a pointer to an IPv6 address.
const sockaddr* get() const {
return mCompatibleAddress;
}
@@ -171,174 +110,16 @@
return tv;
}
-/**
- * Query OS for timestamp.
- * Retrieve the current value of system clock and convert to milliseconds.
- *
- * @param[in] portLibrary The port library.
- *
- * @return 0 on failure, time value in milliseconds on success.
- * @deprecated Use @ref time_hires_clock and @ref time_hires_delta
- *
- * technically, this should return uint64_t since both timeval.tv_sec and
- * timeval.tv_usec are long
- */
-static int time_msec_clock() {
- timeval tv;
- struct timezone tzp;
- gettimeofday(&tv, &tzp);
- return tv.tv_sec * 1000 + tv.tv_usec / 1000;
-}
-
-/**
- * Establish a connection to a peer with a timeout. The member functions are called
- * repeatedly in order to carry out the connect and to allow other tasks to
- * proceed on certain platforms. The caller must first call ConnectHelper::start.
- * if the result is -EINPROGRESS it will then
- * call ConnectHelper::isConnected until either another error or 0 is returned to
- * indicate the connect is complete. Each time the function should sleep for no
- * more than 'timeout' milliseconds. If the connect succeeds or an error occurs,
- * the caller must always end the process by calling ConnectHelper::done.
- *
- * Member functions return 0 if no errors occur, otherwise -errno. TODO: use +errno.
- */
-class ConnectHelper {
-public:
- ConnectHelper(JNIEnv* env) : mEnv(env) {
- }
-
- int start(NetFd& fd, jobject inetAddr, jint port) {
- sockaddr_storage ss;
- if (!inetAddressToSocketAddress(mEnv, inetAddr, port, &ss)) {
- return -EINVAL; // Bogus, but clearly a failure, and we've already thrown.
- }
-
- // Set the socket to non-blocking and initiate a connection attempt...
- const CompatibleSocketAddress compatibleAddress(fd.get(), ss, true);
- if (!setBlocking(fd.get(), false) ||
- connect(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage)) == -1) {
- if (fd.isClosed()) {
- return -EINVAL; // Bogus, but clearly a failure, and we've already thrown.
- }
- if (errno != EINPROGRESS) {
- didFail(fd.get(), -errno);
- }
- return -errno;
- }
- // We connected straight away!
- didConnect(fd.get());
- return 0;
- }
-
- // Returns 0 if we're connected; -EINPROGRESS if we're still hopeful, -errno if we've failed.
- // 'timeout' the timeout in milliseconds. If timeout is negative, perform a blocking operation.
- int isConnected(int fd, int timeout) {
- timeval passedTimeout(toTimeval(timeout));
-
- // Initialize the fd sets for the select.
- fd_set readSet;
- fd_set writeSet;
- FD_ZERO(&readSet);
- FD_ZERO(&writeSet);
- FD_SET(fd, &readSet);
- FD_SET(fd, &writeSet);
-
- int nfds = fd + 1;
- timeval* tp = timeout >= 0 ? &passedTimeout : NULL;
- int rc = select(nfds, &readSet, &writeSet, NULL, tp);
- if (rc == -1) {
- if (errno == EINTR) {
- // We can't trivially retry a select with TEMP_FAILURE_RETRY, so punt and ask the
- // caller to try again.
- return -EINPROGRESS;
- }
- return -errno;
- }
-
- // If the fd is just in the write set, we're connected.
- if (FD_ISSET(fd, &writeSet) && !FD_ISSET(fd, &readSet)) {
- return 0;
- }
-
- // If the fd is in both the read and write set, there was an error.
- if (FD_ISSET(fd, &readSet) || FD_ISSET(fd, &writeSet)) {
- // Get the pending error.
- int error = 0;
- socklen_t errorLen = sizeof(error);
- if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorLen) == -1) {
- return -errno; // Couldn't get the real error, so report why not.
- }
- return -error;
- }
-
- // Timeout expired.
- return -EINPROGRESS;
- }
-
- void didConnect(int fd) {
- if (fd != -1) {
- setBlocking(fd, true);
- }
- }
-
- void didFail(int fd, int result) {
- if (fd != -1) {
- setBlocking(fd, true);
- }
-
- if (result == -ECONNRESET || result == -ECONNREFUSED || result == -EADDRNOTAVAIL ||
- result == -EADDRINUSE || result == -ENETUNREACH) {
- jniThrowConnectException(mEnv, -result);
- } else if (result == -EACCES) {
- jniThrowSecurityException(mEnv, -result);
- } else if (result == -ETIMEDOUT) {
- jniThrowSocketTimeoutException(mEnv, -result);
- } else {
- jniThrowSocketException(mEnv, -result);
- }
- }
-
-private:
- JNIEnv* mEnv;
-};
-
-static void mcastJoinLeaveGroup(JNIEnv* env, int fd, jobject javaGroupRequest, bool join) {
- group_req groupRequest;
-
- // Get the IPv4 or IPv6 multicast address to join or leave.
- static jfieldID grGroupFid = env->GetFieldID(JniConstants::multicastGroupRequestClass,
- "gr_group", "Ljava/net/InetAddress;");
- jobject group = env->GetObjectField(javaGroupRequest, grGroupFid);
- if (!inetAddressToSocketAddress(env, group, 0, &groupRequest.gr_group)) {
- return;
- }
-
- // Get the interface index to use (or 0 for "whatever").
- static jfieldID grInterfaceFid =
- env->GetFieldID(JniConstants::multicastGroupRequestClass, "gr_interface", "I");
- groupRequest.gr_interface = env->GetIntField(javaGroupRequest, grInterfaceFid);
-
- // Decide exactly what we're trying to do...
- int level = groupRequest.gr_group.ss_family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
- int option = join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
-
- int rc = setsockopt(fd, level, option, &groupRequest, sizeof(groupRequest));
- if (rc == -1 && errno == EINVAL) {
- // Maybe we're a 32-bit binary talking to a 64-bit kernel?
- // glibc doesn't automatically handle this.
- struct GCC_HIDDEN group_req64 {
- uint32_t gr_interface;
- uint32_t my_padding;
- sockaddr_storage gr_group;
- };
- group_req64 groupRequest64;
- groupRequest64.gr_interface = groupRequest.gr_interface;
- memcpy(&groupRequest64.gr_group, &groupRequest.gr_group, sizeof(groupRequest.gr_group));
- rc = setsockopt(fd, level, option, &groupRequest64, sizeof(groupRequest64));
- }
- if (rc == -1) {
- jniThrowSocketException(env, errno);
- return;
+static void throwConnectException(JNIEnv* env, int error) {
+ if (error == ECONNRESET || error == ECONNREFUSED || error == EADDRNOTAVAIL ||
+ error == EADDRINUSE || error == ENETUNREACH) {
+ jniThrowExceptionWithErrno(env, "java/net/ConnectException", error);
+ } else if (error == EACCES) {
+ jniThrowExceptionWithErrno(env, "java/lang/SecurityException", error);
+ } else if (error == ETIMEDOUT) {
+ jniThrowSocketTimeoutException(env, error);
+ } else {
+ jniThrowSocketException(env, error);
}
}
@@ -389,89 +170,91 @@
return result;
}
-static jboolean OSNetworkSystem_connectNonBlocking(JNIEnv* env, jobject, jobject fileDescriptor, jobject inetAddr, jint port) {
+static jboolean OSNetworkSystem_connect(JNIEnv* env, jobject, jobject fileDescriptor, jobject inetAddr, jint port) {
NetFd fd(env, fileDescriptor);
if (fd.isClosed()) {
return JNI_FALSE;
}
- ConnectHelper context(env);
- return context.start(fd, inetAddr, port) == 0;
+ sockaddr_storage ss;
+ if (!inetAddressToSocketAddress(env, inetAddr, port, &ss)) {
+ return JNI_FALSE;
+ }
+
+ // Initiate a connection attempt...
+ const CompatibleSocketAddress compatibleAddress(ss, true);
+ int rc = connect(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage));
+ int connectErrno = errno;
+
+ // Did we get interrupted?
+ if (fd.isClosed()) {
+ return JNI_FALSE;
+ }
+
+ // Did we fail to connect?
+ if (rc == -1) {
+ if (connectErrno != EINPROGRESS) {
+ throwConnectException(env, connectErrno); // Permanent failure, so throw.
+ }
+ return JNI_FALSE;
+ }
+
+ // We connected straight away!
+ return JNI_TRUE;
}
static jboolean OSNetworkSystem_isConnected(JNIEnv* env, jobject, jobject fileDescriptor, jint timeout) {
- NetFd fd(env, fileDescriptor);
- if (fd.isClosed()) {
+ NetFd netFd(env, fileDescriptor);
+ if (netFd.isClosed()) {
return JNI_FALSE;
}
- ConnectHelper context(env);
- int result = context.isConnected(fd.get(), timeout);
- if (result == 0) {
- context.didConnect(fd.get());
- return JNI_TRUE;
- } else if (result == -EINPROGRESS) {
- // Not yet connected, but not yet denied either... Try again later.
- return JNI_FALSE;
- } else {
- context.didFail(fd.get(), result);
- return JNI_FALSE;
- }
-}
-
-// TODO: move this into Java, using connectNonBlocking and isConnected!
-static void OSNetworkSystem_connect(JNIEnv* env, jobject, jobject fileDescriptor,
- jobject inetAddr, jint port, jint timeout) {
-
- /* if a timeout was specified calculate the finish time value */
- bool hasTimeout = timeout > 0;
- int finishTime = 0;
- if (hasTimeout) {
- finishTime = time_msec_clock() + (int) timeout;
- }
-
- NetFd fd(env, fileDescriptor);
- if (fd.isClosed()) {
- return;
- }
-
- ConnectHelper context(env);
- int result = context.start(fd, inetAddr, port);
- int remainingTimeout = timeout;
- while (result == -EINPROGRESS) {
- /*
- * ok now try and connect. Depending on the platform this may sleep
- * for up to passedTimeout milliseconds
- */
- result = context.isConnected(fd.get(), remainingTimeout);
- if (fd.isClosed()) {
- return;
- }
- if (result == 0) {
- context.didConnect(fd.get());
- return;
- } else if (result != -EINPROGRESS) {
- context.didFail(fd.get(), result);
- return;
- }
-
- /* check if the timeout has expired */
- if (hasTimeout) {
- remainingTimeout = finishTime - time_msec_clock();
- if (remainingTimeout <= 0) {
- context.didFail(fd.get(), -ETIMEDOUT);
- return;
- }
+ // Initialize the fd sets and call select.
+ int fd = netFd.get();
+ int nfds = fd + 1;
+ fd_set readSet;
+ fd_set writeSet;
+ FD_ZERO(&readSet);
+ FD_ZERO(&writeSet);
+ FD_SET(fd, &readSet);
+ FD_SET(fd, &writeSet);
+ timeval passedTimeout(toTimeval(timeout));
+ int rc = select(nfds, &readSet, &writeSet, NULL, &passedTimeout);
+ if (rc == -1) {
+ if (errno == EINTR) {
+ // We can't trivially retry a select with TEMP_FAILURE_RETRY, so punt and ask the
+ // caller to try again.
} else {
- remainingTimeout = 100;
+ throwConnectException(env, errno);
}
+ return JNI_FALSE;
}
+
+ // If the fd is just in the write set, we're connected.
+ if (FD_ISSET(fd, &writeSet) && !FD_ISSET(fd, &readSet)) {
+ return JNI_TRUE;
+ }
+
+ // If the fd is in both the read and write set, there was an error.
+ if (FD_ISSET(fd, &readSet) || FD_ISSET(fd, &writeSet)) {
+ // Get the pending error.
+ int error = 0;
+ socklen_t errorLen = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorLen) == -1) {
+ error = errno; // Couldn't get the real error, so report why getsockopt failed.
+ }
+ throwConnectException(env, error);
+ return JNI_FALSE;
+ }
+
+ // Timeout expired.
+ return JNI_FALSE;
}
static void OSNetworkSystem_bind(JNIEnv* env, jobject, jobject fileDescriptor,
jobject inetAddress, jint port) {
- sockaddr_storage socketAddress;
- if (!inetAddressToSocketAddress(env, inetAddress, port, &socketAddress)) {
+ sockaddr_storage ss;
+ if (!inetAddressToSocketAddress(env, inetAddress, port, &ss)) {
return;
}
@@ -480,10 +263,10 @@
return;
}
- const CompatibleSocketAddress compatibleAddress(fd.get(), socketAddress, false);
+ const CompatibleSocketAddress compatibleAddress(ss, false);
int rc = TEMP_FAILURE_RETRY(bind(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage)));
if (rc == -1) {
- jniThrowBindException(env, errno);
+ jniThrowExceptionWithErrno(env, "java/net/BindException", errno);
}
}
@@ -682,15 +465,11 @@
if (packet != NULL) {
env->SetIntField(packet, lengthFid, bytesReceived);
if (!connected) {
- jbyteArray addr = socketAddressToByteArray(env, &ss);
- if (addr == NULL) {
- return 0;
- }
- int port = getSocketAddressPort(&ss);
- jobject sender = byteArrayToInetAddress(env, addr);
+ jobject sender = socketAddressToInetAddress(env, &ss);
if (sender == NULL) {
return 0;
}
+ int port = getSocketAddressPort(&ss);
env->SetObjectField(packet, addressFid, sender);
env->SetIntField(packet, portFid, port);
}
@@ -866,170 +645,6 @@
translateFdSet(env, writeFDArray, countWriteC, writeFds, flagArray.get(), countReadC, SOCKET_OP_WRITE);
}
-template <typename T>
-static void setSocketOption(JNIEnv* env, const NetFd& fd, int level, int option, T* value) {
- int rc = setsockopt(fd.get(), level, option, value, sizeof(*value));
- if (rc == -1) {
- LOGE("setSocketOption(fd=%i, level=%i, option=%i) failed: %s (errno=%i)",
- fd.get(), level, option, strerror(errno), errno);
- jniThrowSocketException(env, errno);
- }
-}
-
-static void OSNetworkSystem_setSocketOption(JNIEnv* env, jobject, jobject fileDescriptor, jint option, jobject optVal) {
- NetFd fd(env, fileDescriptor);
- if (fd.isClosed()) {
- return;
- }
-
- int intVal;
- bool wasBoolean = false;
- if (env->IsInstanceOf(optVal, JniConstants::integerClass)) {
- intVal = intValue(env, optVal);
- } else if (env->IsInstanceOf(optVal, JniConstants::booleanClass)) {
- intVal = (int) booleanValue(env, optVal);
- wasBoolean = true;
- } else if (env->IsInstanceOf(optVal, JniConstants::inetAddressClass)) {
- // We use optVal directly as an InetAddress for IP_MULTICAST_IF.
- } else if (env->IsInstanceOf(optVal, JniConstants::multicastGroupRequestClass)) {
- // We use optVal directly as a MulticastGroupRequest for MCAST_JOIN_GROUP/MCAST_LEAVE_GROUP.
- } else {
- jniThrowSocketException(env, EINVAL);
- return;
- }
-
- int family = getSocketAddressFamily(fd.get());
- if (family != AF_INET && family != AF_INET6) {
- jniThrowSocketException(env, EAFNOSUPPORT);
- return;
- }
-
- // Since we expect to have a AF_INET6 socket even if we're communicating via IPv4, we always
- // set the IPPROTO_IP options. As long as we fall back to creating IPv4 sockets if creating
- // an IPv6 socket fails, we need to make setting the IPPROTO_IPV6 options conditional.
- switch (option) {
- case JAVASOCKOPT_IP_TOS:
- setSocketOption(env, fd, IPPROTO_IP, IP_TOS, &intVal);
- if (family == AF_INET6) {
- setSocketOption(env, fd, IPPROTO_IPV6, IPV6_TCLASS, &intVal);
- }
- return;
- case JAVASOCKOPT_SO_BROADCAST:
- setSocketOption(env, fd, SOL_SOCKET, SO_BROADCAST, &intVal);
- return;
- case JAVASOCKOPT_SO_KEEPALIVE:
- setSocketOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &intVal);
- return;
- case JAVASOCKOPT_SO_LINGER:
- {
- linger l;
- l.l_onoff = !wasBoolean;
- l.l_linger = intVal <= 65535 ? intVal : 65535;
- setSocketOption(env, fd, SOL_SOCKET, SO_LINGER, &l);
- return;
- }
- case JAVASOCKOPT_SO_OOBINLINE:
- setSocketOption(env, fd, SOL_SOCKET, SO_OOBINLINE, &intVal);
- return;
- case JAVASOCKOPT_SO_RCVBUF:
- setSocketOption(env, fd, SOL_SOCKET, SO_RCVBUF, &intVal);
- return;
- case JAVASOCKOPT_SO_REUSEADDR:
- setSocketOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &intVal);
- return;
- case JAVASOCKOPT_SO_SNDBUF:
- setSocketOption(env, fd, SOL_SOCKET, SO_SNDBUF, &intVal);
- return;
- case JAVASOCKOPT_SO_BINDTODEVICE: {
- // intVal contains the interface index
- char ifname[IF_NAMESIZE];
-
- if (if_indextoname(intVal, ifname) == NULL) {
- jniThrowSocketException(env, ENODEV);
- } else {
- ifreq ifr;
-
- memset(&ifr, 0, sizeof(ifr));
- strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
- ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
- setSocketOption(env, fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr);
- }
- return;
- }
- case JAVASOCKOPT_SO_TIMEOUT:
- {
- timeval timeout(toTimeval(intVal));
- setSocketOption(env, fd, SOL_SOCKET, SO_RCVTIMEO, &timeout);
- return;
- }
- case JAVASOCKOPT_TCP_NODELAY:
- setSocketOption(env, fd, IPPROTO_TCP, TCP_NODELAY, &intVal);
- return;
- case JAVASOCKOPT_MCAST_JOIN_GROUP:
- mcastJoinLeaveGroup(env, fd.get(), optVal, true);
- return;
- case JAVASOCKOPT_MCAST_LEAVE_GROUP:
- mcastJoinLeaveGroup(env, fd.get(), optVal, false);
- return;
- case JAVASOCKOPT_IP_MULTICAST_IF:
- {
- sockaddr_storage sockVal;
- if (!env->IsInstanceOf(optVal, JniConstants::inetAddressClass) ||
- !inetAddressToSocketAddress(env, optVal, 0, &sockVal)) {
- return;
- }
- // This call is IPv4 only. The socket may be IPv6, but the address
- // that identifies the interface to join must be an IPv4 address.
- if (sockVal.ss_family != AF_INET) {
- jniThrowSocketException(env, EAFNOSUPPORT);
- return;
- }
- ip_mreqn mcast_req;
- memset(&mcast_req, 0, sizeof(mcast_req));
- mcast_req.imr_address = reinterpret_cast<sockaddr_in*>(&sockVal)->sin_addr;
- setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &mcast_req);
- return;
- }
- case JAVASOCKOPT_IP_MULTICAST_IF2:
- // TODO: is this right? should we unconditionally set the IPPROTO_IP state in case
- // we have an IPv6 socket communicating via IPv4?
- if (family == AF_INET) {
- // IP_MULTICAST_IF expects a pointer to an ip_mreqn struct.
- ip_mreqn multicastRequest;
- memset(&multicastRequest, 0, sizeof(multicastRequest));
- multicastRequest.imr_ifindex = intVal;
- setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &multicastRequest);
- } else {
- // IPV6_MULTICAST_IF expects a pointer to an integer.
- setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &intVal);
- }
- return;
- case JAVASOCKOPT_MULTICAST_TTL:
- {
- // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
- // IPv4 multicast TTL uses a byte.
- u_char ttl = intVal;
- setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl);
- if (family == AF_INET6) {
- setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &intVal);
- }
- return;
- }
- case JAVASOCKOPT_IP_MULTICAST_LOOP:
- {
- // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
- u_char loopback = intVal;
- setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loopback);
- if (family == AF_INET6) {
- setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &intVal);
- }
- return;
- }
- default:
- jniThrowSocketException(env, ENOPROTOOPT);
- }
-}
-
static void OSNetworkSystem_close(JNIEnv* env, jobject, jobject fileDescriptor) {
NetFd fd(env, fileDescriptor);
if (fd.isClosed()) {
@@ -1048,8 +663,7 @@
NATIVE_METHOD(OSNetworkSystem, accept, "(Ljava/io/FileDescriptor;Ljava/net/SocketImpl;Ljava/io/FileDescriptor;)V"),
NATIVE_METHOD(OSNetworkSystem, bind, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"),
NATIVE_METHOD(OSNetworkSystem, close, "(Ljava/io/FileDescriptor;)V"),
- NATIVE_METHOD(OSNetworkSystem, connectNonBlocking, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)Z"),
- NATIVE_METHOD(OSNetworkSystem, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;II)V"),
+ NATIVE_METHOD(OSNetworkSystem, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)Z"),
NATIVE_METHOD(OSNetworkSystem, disconnectDatagram, "(Ljava/io/FileDescriptor;)V"),
NATIVE_METHOD(OSNetworkSystem, isConnected, "(Ljava/io/FileDescriptor;I)Z"),
NATIVE_METHOD(OSNetworkSystem, read, "(Ljava/io/FileDescriptor;[BII)I"),
@@ -1060,7 +674,6 @@
NATIVE_METHOD(OSNetworkSystem, send, "(Ljava/io/FileDescriptor;[BIIILjava/net/InetAddress;)I"),
NATIVE_METHOD(OSNetworkSystem, sendDirect, "(Ljava/io/FileDescriptor;IIIILjava/net/InetAddress;)I"),
NATIVE_METHOD(OSNetworkSystem, sendUrgentData, "(Ljava/io/FileDescriptor;B)V"),
- NATIVE_METHOD(OSNetworkSystem, setSocketOption, "(Ljava/io/FileDescriptor;ILjava/lang/Object;)V"),
NATIVE_METHOD(OSNetworkSystem, write, "(Ljava/io/FileDescriptor;[BII)I"),
NATIVE_METHOD(OSNetworkSystem, writeDirect, "(Ljava/io/FileDescriptor;III)I"),
};
diff --git a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
index 4ef4378..1756a44 100644
--- a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
+++ b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
@@ -20,9 +20,9 @@
#include "JniConstants.h"
#include "JniException.h"
#include "LocalArray.h"
-#include "ScopedJavaUnicodeString.h"
#include "ScopedLocalRef.h"
#include "ScopedPrimitiveArray.h"
+#include "ScopedStringChars.h"
#include "ScopedUtfChars.h"
#include "UniquePtr.h"
#include "jni.h"
@@ -1040,11 +1040,13 @@
append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE);
}
-static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer,
- jstring javaXml, jboolean isFinal) {
- ScopedJavaUnicodeString xml(env, javaXml);
- const char* bytes = reinterpret_cast<const char*>(xml.unicodeString().getBuffer());
- size_t byteCount = 2 * xml.unicodeString().length();
+static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer, jstring javaXml, jboolean isFinal) {
+ ScopedStringChars xml(env, javaXml);
+ if (xml.get() == NULL) {
+ return;
+ }
+ const char* bytes = reinterpret_cast<const char*>(xml.get());
+ size_t byteCount = 2 * xml.size();
append(env, object, pointer, bytes, 0, byteCount, isFinal);
}
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index 1ab475c..c7620b3 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -5,13 +5,11 @@
LOCAL_SRC_FILES := \
AsynchronousSocketCloseMonitor.cpp \
- ErrorCode.cpp \
JniConstants.cpp \
JniException.cpp \
NetworkUtilities.cpp \
Register.cpp \
cbigint.cpp \
- ifaddrs-android.cpp \
java_io_Console.cpp \
java_io_File.cpp \
java_io_ObjectStreamClass.cpp \
@@ -22,8 +20,6 @@
java_lang_StrictMath.cpp \
java_lang_System.cpp \
java_math_NativeBN.cpp \
- java_net_InetAddress.cpp \
- java_net_NetworkInterface.cpp \
java_nio_ByteOrder.cpp \
java_nio_charset_Charsets.cpp \
java_text_Bidi.cpp \
diff --git a/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
new file mode 100644
index 0000000..6835c7b
--- /dev/null
+++ b/luni/src/test/java/libcore/java/io/InterruptedStreamTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.java.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PipedReader;
+import java.io.PipedWriter;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.Pipe;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+import junit.framework.TestCase;
+
+/**
+ * Test that interrupting a thread blocked on I/O causes that thread to throw
+ * an InterruptedIOException.
+ */
+public final class InterruptedStreamTest extends TestCase {
+
+ private static final int BUFFER_SIZE = 1024 * 1024;
+
+ private Socket[] sockets;
+
+ @Override protected void tearDown() throws Exception {
+ if (sockets != null) {
+ sockets[0].close();
+ sockets[1].close();
+ }
+ super.tearDown();
+ }
+
+ public void testInterruptPipedInputStream() throws Exception {
+ PipedOutputStream out = new PipedOutputStream();
+ PipedInputStream in = new PipedInputStream(out);
+ testInterruptInputStream(in);
+ }
+
+ public void testInterruptPipedOutputStream() throws Exception {
+ PipedOutputStream out = new PipedOutputStream();
+ new PipedInputStream(out);
+ testInterruptOutputStream(out);
+ }
+
+ public void testInterruptPipedReader() throws Exception {
+ PipedWriter writer = new PipedWriter();
+ PipedReader reader = new PipedReader(writer);
+ testInterruptReader(reader);
+ }
+
+ public void testInterruptPipedWriter() throws Exception {
+ final PipedWriter writer = new PipedWriter();
+ new PipedReader(writer);
+ testInterruptWriter(writer);
+ }
+
+ public void testInterruptReadablePipeChannel() throws Exception {
+ testInterruptReadableChannel(Pipe.open().source());
+ }
+
+ public void testInterruptWritablePipeChannel() throws Exception {
+ testInterruptWritableChannel(Pipe.open().sink());
+ }
+
+ /**
+ * Neither the RI nor Dalvik support interrupting sockets. We'd like to add
+ * support for that. http://b/4181738
+ */
+ public void testInterruptSocketInputStream() throws Exception {
+ sockets = newSocketPair();
+ testInterruptInputStream(sockets[0].getInputStream());
+ }
+
+ public void testInterruptSocketOutputStream() throws Exception {
+ sockets = newSocketPair();
+ testInterruptOutputStream(sockets[0].getOutputStream());
+ }
+
+ public void testInterruptReadableSocketChannel() throws Exception {
+ sockets = newSocketChannelPair();
+ testInterruptReadableChannel(sockets[0].getChannel());
+ }
+
+ public void testInterruptWritableSocketChannel() throws Exception {
+ sockets = newSocketChannelPair();
+ testInterruptReadableChannel(sockets[0].getChannel());
+ }
+
+ /**
+ * Returns a pair of connected sockets backed by IO sockets.
+ */
+ private Socket[] newSocketPair() throws IOException {
+ ServerSocket serverSocket = new ServerSocket(0);
+ serverSocket.setReuseAddress(true);
+ Socket client = new Socket();
+ client.connect(serverSocket.getLocalSocketAddress());
+ Socket server = serverSocket.accept();
+ return new Socket[] { client, server };
+ }
+
+ /**
+ * Returns a pair of connected sockets backed by NIO socket channels.
+ */
+ private Socket[] newSocketChannelPair() throws IOException {
+ ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
+ serverSocketChannel.socket().bind(new InetSocketAddress(0));
+ SocketChannel clientSocketChannel = SocketChannel.open();
+ clientSocketChannel.connect(serverSocketChannel.socket().getLocalSocketAddress());
+ SocketChannel server = serverSocketChannel.accept();
+ serverSocketChannel.close();
+ return new Socket[] { clientSocketChannel.socket(), server.socket() };
+ }
+
+ private void testInterruptInputStream(final InputStream in) throws Exception {
+ interruptMeLater();
+ try {
+ in.read();
+ fail();
+ } catch (InterruptedIOException expected) {
+ }
+ }
+
+ private void testInterruptReader(final PipedReader reader) throws Exception {
+ interruptMeLater();
+ try {
+ reader.read();
+ fail();
+ } catch (InterruptedIOException expected) {
+ }
+ }
+
+ private void testInterruptReadableChannel(final ReadableByteChannel channel) throws Exception {
+ interruptMeLater();
+ try {
+ channel.read(ByteBuffer.allocate(BUFFER_SIZE));
+ fail();
+ } catch (ClosedByInterruptException expected) {
+ }
+ }
+
+ private void testInterruptOutputStream(final OutputStream out) throws Exception {
+ interruptMeLater();
+ try {
+ // this will block when the receiving buffer fills up
+ while (true) {
+ out.write(new byte[BUFFER_SIZE]);
+ }
+ } catch (InterruptedIOException expected) {
+ }
+ }
+
+ private void testInterruptWriter(final PipedWriter writer) throws Exception {
+ interruptMeLater();
+ try {
+ // this will block when the receiving buffer fills up
+ while (true) {
+ writer.write(new char[BUFFER_SIZE]);
+ }
+ } catch (InterruptedIOException expected) {
+ }
+ }
+
+ private void testInterruptWritableChannel(final WritableByteChannel channel) throws Exception {
+ interruptMeLater();
+ try {
+ // this will block when the receiving buffer fills up
+ while (true) {
+ channel.write(ByteBuffer.allocate(BUFFER_SIZE));
+ }
+ } catch (ClosedByInterruptException expected) {
+ }
+ }
+
+ private void interruptMeLater() throws Exception {
+ final Thread toInterrupt = Thread.currentThread();
+ new Thread(new Runnable () {
+ @Override public void run() {
+ toInterrupt.interrupt();
+ }
+ }).start();
+ }
+}
diff --git a/luni/src/test/java/libcore/java/lang/PackageTest.java b/luni/src/test/java/libcore/java/lang/PackageTest.java
new file mode 100644
index 0000000..dc862f3
--- /dev/null
+++ b/luni/src/test/java/libcore/java/lang/PackageTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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.java.lang;
+
+public class PackageTest extends junit.framework.TestCase {
+ public void test_getAnnotations() throws Exception {
+ // Package annotations aren't supported, but pre-ICS we crashed.
+ assertEquals(0, getClass().getPackage().getAnnotations().length);
+ assertEquals(0, getClass().getPackage().getDeclaredAnnotations().length);
+ }
+}
diff --git a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java
index 405de6e..d03ae65 100644
--- a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java
+++ b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java
@@ -33,7 +33,7 @@
return new File(deviceSh).exists() ? deviceSh : desktopSh;
}
- public void testRedirectErrorStream(boolean doRedirect,
+ private static void assertRedirectErrorStream(boolean doRedirect,
String expectedOut, String expectedErr) throws Exception {
ProcessBuilder pb = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2");
pb.redirectErrorStream(doRedirect);
@@ -41,11 +41,11 @@
}
public void test_redirectErrorStream_true() throws Exception {
- testRedirectErrorStream(true, "out\nerr\n", "");
+ assertRedirectErrorStream(true, "out\nerr\n", "");
}
public void test_redirectErrorStream_false() throws Exception {
- testRedirectErrorStream(false, "out\n", "err\n");
+ assertRedirectErrorStream(false, "out\n", "err\n");
}
public void testEnvironment() throws Exception {
diff --git a/luni/src/test/java/libcore/java/lang/SystemTest.java b/luni/src/test/java/libcore/java/lang/SystemTest.java
index a7b6225..34ca6c7 100644
--- a/luni/src/test/java/libcore/java/lang/SystemTest.java
+++ b/luni/src/test/java/libcore/java/lang/SystemTest.java
@@ -72,8 +72,7 @@
System.arraycopy(new char[5], 0, "Hello", 0, 3);
fail();
} catch (ArrayStoreException e) {
- assertEquals("source and destination must be arrays, but were "
- + "[C and Ljava/lang/String;", e.getMessage());
+ assertEquals("destination of type java.lang.String is not an array", e.getMessage());
}
}
@@ -82,8 +81,7 @@
System.arraycopy("Hello", 0, new char[5], 0, 3);
fail();
} catch (ArrayStoreException e) {
- assertEquals("source and destination must be arrays, but were "
- + "Ljava/lang/String; and [C", e.getMessage());
+ assertEquals("source of type java.lang.String is not an array", e.getMessage());
}
}
@@ -92,8 +90,7 @@
System.arraycopy(new char[5], 0, new Object[5], 0, 3);
fail();
} catch (ArrayStoreException e) {
- assertEquals("source and destination arrays are incompatible: "
- + "[C and [Ljava/lang/Object;", e.getMessage());
+ assertEquals("char[] and java.lang.Object[] are incompatible array types", e.getMessage());
}
}
@@ -103,9 +100,7 @@
new Integer[] { 1, 2, 3, null, null }, 0, 3);
fail();
} catch (ArrayStoreException e) {
- assertEquals("source[2] of type Ljava/lang/String; cannot be "
- + "stored in destination array of type [Ljava/lang/Integer;",
- e.getMessage());
+ assertEquals("source[2] of type java.lang.String cannot be stored in destination array of type java.lang.Integer[]", e.getMessage());
}
}
}
diff --git a/luni/src/test/java/libcore/java/lang/ThrowableTest.java b/luni/src/test/java/libcore/java/lang/ThrowableTest.java
index 1814932..700f616 100644
--- a/luni/src/test/java/libcore/java/lang/ThrowableTest.java
+++ b/luni/src/test/java/libcore/java/lang/ThrowableTest.java
@@ -18,7 +18,9 @@
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Arrays;
import junit.framework.TestCase;
+import libcore.java.util.SerializableTester;
public class ThrowableTest extends TestCase {
private static class NoStackTraceException extends Exception {
@@ -35,4 +37,286 @@
ex.printStackTrace(new PrintWriter(new StringWriter()));
}
}
+
+ private static class SuppressionsThrowable extends Throwable {
+ public SuppressionsThrowable(String detailMessage, Throwable throwable,
+ boolean enableSuppression) {
+ super(detailMessage, throwable, enableSuppression);
+ }
+ }
+
+ public void testAddSuppressed() {
+ Throwable throwable = new Throwable();
+ assertSuppressed(throwable);
+ Throwable suppressedA = new Throwable();
+ throwable.addSuppressed(suppressedA);
+ assertSuppressed(throwable, suppressedA);
+ Throwable suppressedB = new Throwable();
+ throwable.addSuppressed(suppressedB);
+ assertSuppressed(throwable, suppressedA, suppressedB);
+ }
+
+ public void testAddDuplicateSuppressed() {
+ Throwable throwable = new Throwable();
+ Throwable suppressedA = new Throwable();
+ throwable.addSuppressed(suppressedA);
+ throwable.addSuppressed(suppressedA);
+ throwable.addSuppressed(suppressedA);
+ assertSuppressed(throwable, suppressedA, suppressedA, suppressedA);
+ }
+
+ public void testGetSuppressedReturnsCopy() {
+ Throwable throwable = new Throwable();
+ Throwable suppressedA = new Throwable();
+ Throwable suppressedB = new Throwable();
+ throwable.addSuppressed(suppressedA);
+ throwable.addSuppressed(suppressedB);
+ Throwable[] mutable = throwable.getSuppressed();
+ mutable[0] = null;
+ mutable[1] = null;
+ assertSuppressed(throwable, suppressedA, suppressedB);
+ }
+
+ public void testAddSuppressedWithSuppressionDisabled() {
+ Throwable throwable = new SuppressionsThrowable("foo", null, false);
+ assertSuppressed(throwable);
+ throwable.addSuppressed(new Throwable());
+ assertSuppressed(throwable);
+ throwable.addSuppressed(new Throwable());
+ assertSuppressed(throwable);
+ }
+
+ public void testAddSuppressedSelf() {
+ Throwable throwable = new Throwable();
+ try {
+ throwable.addSuppressed(throwable);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testAddSuppressedNull() {
+ Throwable throwable = new Throwable();
+ try {
+ throwable.addSuppressed(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testPrintStackTraceWithCause() {
+ Throwable throwable = newThrowable("Throwable", "A", "B");
+ throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
+
+ assertEquals("java.lang.Throwable: Throwable\n"
+ + "\tat ClassB.doB(ClassB.java:1)\n"
+ + "\tat ClassA.doA(ClassA.java:0)\n"
+ + "Caused by: java.lang.Throwable: Cause\n"
+ + "\tat ClassD.doD(ClassD.java:3)\n"
+ + "\tat ClassC.doC(ClassC.java:2)\n"
+ + "\t... 2 more\n", printStackTraceToString(throwable));
+ }
+
+ public void testPrintStackTraceWithCauseAndSuppressed() {
+ Throwable throwable = newThrowable("Throwable", "A", "B");
+ throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
+ throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "E", "F"));
+ throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "G", "H"));
+
+ assertEquals("java.lang.Throwable: Throwable\n"
+ + "\tat ClassB.doB(ClassB.java:1)\n"
+ + "\tat ClassA.doA(ClassA.java:0)\n"
+ + "\tSuppressed: java.lang.Throwable: Suppressed\n"
+ + "\t\tat ClassF.doF(ClassF.java:3)\n"
+ + "\t\tat ClassE.doE(ClassE.java:2)\n"
+ + "\t\t... 2 more\n"
+ + "\tSuppressed: java.lang.Throwable: Suppressed\n"
+ + "\t\tat ClassH.doH(ClassH.java:3)\n"
+ + "\t\tat ClassG.doG(ClassG.java:2)\n"
+ + "\t\t... 2 more\n"
+ + "Caused by: java.lang.Throwable: Cause\n"
+ + "\tat ClassD.doD(ClassD.java:3)\n"
+ + "\tat ClassC.doC(ClassC.java:2)\n"
+ + "\t... 2 more\n", printStackTraceToString(throwable));
+ }
+
+ public void testPrintStackTraceWithEverything() {
+ Throwable throwable = newThrowable("Throwable", "A", "B");
+ Throwable cause = newThrowable("Cause", "A", "B", "C", "D");
+ Throwable suppressed = newThrowable("Suppressed", "A", "B", "E", "F");
+
+ throwable.addSuppressed(suppressed);
+ suppressed.addSuppressed(newThrowable("Suppressed/Suppressed", "A", "B", "E", "G"));
+ suppressed.initCause(newThrowable("Suppressed/Cause", "A", "B", "E", "H"));
+
+ throwable.initCause(cause);
+ cause.addSuppressed(newThrowable("Cause/Suppressed", "A", "B", "C", "I"));
+ cause.initCause(newThrowable("Cause/Cause", "A", "B", "C", "J"));
+
+ assertEquals("java.lang.Throwable: Throwable\n"
+ + "\tat ClassB.doB(ClassB.java:1)\n"
+ + "\tat ClassA.doA(ClassA.java:0)\n"
+ + "\tSuppressed: java.lang.Throwable: Suppressed\n"
+ + "\t\tat ClassF.doF(ClassF.java:3)\n"
+ + "\t\tat ClassE.doE(ClassE.java:2)\n"
+ + "\t\t... 2 more\n"
+ + "\t\tSuppressed: java.lang.Throwable: Suppressed/Suppressed\n"
+ + "\t\t\tat ClassG.doG(ClassG.java:3)\n"
+ + "\t\t\t... 3 more\n"
+ + "\tCaused by: java.lang.Throwable: Suppressed/Cause\n"
+ + "\t\tat ClassH.doH(ClassH.java:3)\n"
+ + "\t\t... 3 more\n"
+ + "Caused by: java.lang.Throwable: Cause\n"
+ + "\tat ClassD.doD(ClassD.java:3)\n"
+ + "\tat ClassC.doC(ClassC.java:2)\n"
+ + "\t... 2 more\n"
+ + "\tSuppressed: java.lang.Throwable: Cause/Suppressed\n"
+ + "\t\tat ClassI.doI(ClassI.java:3)\n"
+ + "\t\t... 3 more\n"
+ + "Caused by: java.lang.Throwable: Cause/Cause\n"
+ + "\tat ClassJ.doJ(ClassJ.java:3)\n"
+ + "\t... 3 more\n", printStackTraceToString(throwable));
+ }
+
+ public void testSetStackTraceWithNullElement() {
+ Throwable throwable = new Throwable();
+ try {
+ throwable.setStackTrace(new StackTraceElement[]{ null });
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testCauseSerialization() {
+ String s = "aced0005737200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300034c0"
+ + "00563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4"
+ + "d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636"
+ + "574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b78707371007e0"
+ + "00071007e000574000543617573657572001e5b4c6a6176612e6c616e672e537461636b547261636"
+ + "5456c656d656e743b02462a3c3cfd22390200007870000000047372001b6a6176612e6c616e672e5"
+ + "37461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d62657"
+ + "24c000e6465636c6172696e67436c61737371007e00024c000866696c654e616d6571007e00024c0"
+ + "00a6d6574686f644e616d6571007e0002787000000003740006436c6173734474000b436c6173734"
+ + "42e6a617661740003646f447371007e000900000002740006436c6173734374000b436c617373432"
+ + "e6a617661740003646f437371007e000900000001740006436c6173734274000b436c617373422e6"
+ + "a617661740003646f427371007e000900000000740006436c6173734174000b436c617373412e6a6"
+ + "17661740003646f41787400095468726f7761626c657571007e0007000000027371007e000900000"
+ + "001740006436c6173734274000b436c617373422e6a617661740003646f427371007e00090000000"
+ + "0740006436c6173734174000b436c617373412e6a617661740003646f4178";
+ Throwable throwable = newThrowable("Throwable", "A", "B");
+ throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
+ assertSerialized(throwable, s);
+ }
+
+ public void testSuppressedSerialization() {
+ String s = "aced0005737200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c0"
+ + "00563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4"
+ + "d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636"
+ + "574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b4c00147375707"
+ + "0726573736564457863657074696f6e737400104c6a6176612f7574696c2f4c6973743b787071007"
+ + "e000574000a53657269616c697a65647572001e5b4c6a6176612e6c616e672e537461636b5472616"
+ + "365456c656d656e743b02462a3c3cfd22390200007870000000027372001b6a6176612e6c616e672"
+ + "e537461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d626"
+ + "5724c000e6465636c6172696e67436c61737371007e00024c000866696c654e616d6571007e00024"
+ + "c000a6d6574686f644e616d6571007e0002787000000001740006436c6173734274000b436c61737"
+ + "3422e6a617661740003646f427371007e000900000000740006436c6173734174000b436c6173734"
+ + "12e6a617661740003646f41737200136a6176612e7574696c2e41727261794c6973747881d21d99c"
+ + "7619d03000149000473697a657870000000017704000000017371007e000071007e001474000a537"
+ + "570707265737365647571007e0007000000047371007e000900000003740006436c6173734474000"
+ + "b436c617373442e6a617661740003646f447371007e000900000002740006436c6173734374000b4"
+ + "36c617373432e6a617661740003646f437371007e000900000001740006436c6173734274000b436"
+ + "c617373422e6a617661740003646f427371007e000900000000740006436c6173734174000b436c6"
+ + "17373412e6a617661740003646f41737200266a6176612e7574696c2e436f6c6c656374696f6e732"
+ + "4556e6d6f6469666961626c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00047"
+ + "872002c6a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6"
+ + "c6c656374696f6e19420080cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6"
+ + "c656374696f6e3b78707371007e0012000000007704000000007871007e002b787878";
+ Throwable throwable = newThrowable("Serialized", "A", "B");
+ throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "C", "D"));
+ assertSerialized(throwable, s);
+ }
+
+ public void testDisableSuppressionSerialization() {
+ String s = "aced0005737200356c6962636f72652e6a6176612e6c616e672e5468726f7761626c65546573742"
+ + "45375707072657373696f6e735468726f7761626c6502cff43b5390d137020000787200136a61766"
+ + "12e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6"
+ + "176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6"
+ + "176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c6"
+ + "16e672f537461636b5472616365456c656d656e743b4c00147375707072657373656445786365707"
+ + "4696f6e737400104c6a6176612f7574696c2f4c6973743b787070740003666f6f7572001e5b4c6a6"
+ + "176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd223902000078700"
+ + "00000007078";
+ Throwable throwable = new SuppressionsThrowable("foo", null, false);
+ throwable.setStackTrace(new StackTraceElement[0]);
+ new SerializableTester<Throwable>(throwable, s) {
+ @Override protected boolean equals(Throwable a, Throwable b) {
+ return printStackTraceToString(a).equals(printStackTraceToString(b));
+ }
+ @Override protected void verify(Throwable deserialized) {
+ // the suppressed exception is silently discarded
+ deserialized.addSuppressed(newThrowable("Suppressed"));
+ assertSuppressed(deserialized);
+ }
+ }.test();
+ }
+
+ public void testEnableSuppressionSerialization() {
+ String s = "aced0005737200356c6962636f72652e6a6176612e6c616e672e5468726f7761626c65546573742"
+ + "45375707072657373696f6e735468726f7761626c6502cff43b5390d137020000787200136a61766"
+ + "12e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6"
+ + "176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6"
+ + "176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c6"
+ + "16e672f537461636b5472616365456c656d656e743b4c00147375707072657373656445786365707"
+ + "4696f6e737400104c6a6176612f7574696c2f4c6973743b787070740003666f6f7572001e5b4c6a6"
+ + "176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd223902000078700"
+ + "0000000737200266a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626"
+ + "c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00057872002c6a6176612e75746"
+ + "96c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6c6c656374696f6e1942008"
+ + "0cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6c656374696f6e3b7870737"
+ + "200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657"
+ + "870000000007704000000007871007e000f78";
+ Throwable throwable = new SuppressionsThrowable("foo", null, true);
+ throwable.setStackTrace(new StackTraceElement[0]);
+ new SerializableTester<Throwable>(throwable, s) {
+ @Override protected boolean equals(Throwable a, Throwable b) {
+ return printStackTraceToString(a).equals(printStackTraceToString(b));
+ }
+ @Override protected void verify(Throwable deserialized) {
+ // the suppressed exception is permitted
+ Throwable suppressed = newThrowable("Suppressed");
+ deserialized.addSuppressed(suppressed);
+ assertSuppressed(deserialized, suppressed);
+ }
+ }.test();
+ }
+
+ private void assertSerialized(final Throwable throwable, String golden) {
+ new SerializableTester<Throwable>(throwable, golden) {
+ @Override protected boolean equals(Throwable a, Throwable b) {
+ return printStackTraceToString(a).equals(printStackTraceToString(b));
+ }
+ }.test();
+ }
+
+ private Throwable newThrowable(String message, String... stackTraceElements) {
+ StackTraceElement[] array = new StackTraceElement[stackTraceElements.length];
+ for (int i = 0; i < stackTraceElements.length; i++) {
+ String s = stackTraceElements[i];
+ array[stackTraceElements.length - 1 - i]
+ = new StackTraceElement("Class" + s, "do" + s, "Class" + s + ".java", i);
+ }
+ Throwable result = new Throwable(message);
+ result.setStackTrace(array);
+ return result;
+ }
+
+ private String printStackTraceToString(Throwable throwable) {
+ StringWriter writer = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ private void assertSuppressed(Throwable throwable, Throwable... expectedSuppressed) {
+ assertEquals(Arrays.asList(throwable.getSuppressed()), Arrays.asList(expectedSuppressed));
+ }
}
diff --git a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
index 0c268ff..7b87ae0 100644
--- a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
+++ b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java
@@ -45,13 +45,13 @@
m.invoke(new Integer(5)); // Wrong type for 'this'.
fail();
} catch (IllegalArgumentException iae) {
- assertEquals("expected receiver of type java.lang.String, not java.lang.Integer", iae.getMessage());
+ assertEquals("expected receiver of type java.lang.String, but got java.lang.Integer", iae.getMessage());
}
try {
m.invoke(null); // Null for 'this'.
fail();
} catch (NullPointerException npe) {
- assertEquals("expected receiver of type java.lang.String, not null", npe.getMessage());
+ assertEquals("expected receiver of type java.lang.String, but got null", npe.getMessage());
}
}
diff --git a/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java b/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java
new file mode 100644
index 0000000..ca8c790
--- /dev/null
+++ b/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java
@@ -0,0 +1,1287 @@
+/*
+ * 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.java.net;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+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.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+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 libcore.net.http.HttpResponseCache;
+import tests.http.MockResponse;
+import tests.http.MockWebServer;
+import tests.http.RecordedRequest;
+import static tests.http.SocketPolicy.DISCONNECT_AT_END;
+
+public final class HttpResponseCacheTest extends TestCase {
+ private MockWebServer server = new MockWebServer();
+ private HttpResponseCache cache = new HttpResponseCache();
+
+ @Override protected void setUp() throws Exception {
+ super.setUp();
+ ResponseCache.setDefault(cache);
+ }
+
+ @Override protected void tearDown() throws Exception {
+ ResponseCache.setDefault(null);
+ server.shutdown();
+ 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(true, 206);
+ 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();
+ server.enqueue(new MockResponse()
+ .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
+ .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
+ .setResponseCode(responseCode)
+ .setBody("ABCDE")
+ .addHeader("WWW-Authenticate: challenge"));
+ server.play();
+
+ URL url = server.getUrl("/");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(responseCode, conn.getResponseCode());
+
+ // exhaust the content stream
+ readAscii(conn);
+
+ Set<URI> expectedCachedUris = shouldPut
+ ? Collections.singleton(url.toURI())
+ : Collections.<URI>emptySet();
+ assertEquals(Integer.toString(responseCode),
+ expectedCachedUris, cache.getContents().keySet());
+ 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.getSuccessCount());
+ assertEquals(0, cache.getAbortCount());
+
+ 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());
+ assertEquals(1, cache.getMissCount());
+ assertEquals(1, cache.getHitCount());
+ assertEquals(1, cache.getSuccessCount());
+ assertEquals(0, cache.getAbortCount());
+ }
+
+ 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(1, cache.getMissCount());
+ 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 IOException {
+ 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(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
+ assertEquals(2, cache.getHitCount());
+ }
+
+ 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(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
+ 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) {
+ }
+
+ assertEquals(1, cache.getAbortCount());
+ assertEquals(0, cache.getSuccessCount());
+ URLConnection connection = server.getUrl("/").openConnection();
+ assertEquals("Request #2", readAscii(connection));
+ assertEquals(1, cache.getAbortCount());
+ assertEquals(1, cache.getSuccessCount());
+ }
+
+ 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.getAbortCount());
+ assertEquals(0, cache.getSuccessCount());
+ connection = server.getUrl("/").openConnection();
+ assertEquals("Request #2", readAscii(connection));
+ assertEquals(1, cache.getAbortCount());
+ assertEquals(1, cache.getSuccessCount());
+ }
+
+ 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 testRequestOnlyIfCachedWithNoResponseCached() throws IOException {
+ // (no responses enqueued)
+ server.play();
+
+ HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
+ connection.addRequestProperty("Cache-Control", "only-if-cached");
+ assertBadGateway(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");
+ assertBadGateway(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");
+ assertBadGateway(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 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 testCacheControlMustRevalidate() throws Exception {
+ fail("Cache-Control: must-revalidate"); // TODO
+ }
+
+ public void testVaryResponsesAreNotSupported() 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("B", readAscii(connection2));
+ }
+
+ 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()));
+ }
+
+ /**
+ * @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) {
+ Date date = new Date(System.currentTimeMillis() + timeUnit.toMillis(delta));
+ 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()));
+ }
+
+ /**
+ * @return the request with the conditional get headers.
+ */
+ private RecordedRequest assertConditionallyCached(MockResponse response) throws Exception {
+ // scenario 1: condition succeeds
+ server.enqueue(response.setBody("A"));
+ server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+
+ // scenario 2: condition fails
+ server.enqueue(response.setBody("B"));
+ server.enqueue(new MockResponse().setBody("C"));
+
+ server.play();
+
+ URL valid = server.getUrl("/valid");
+ assertEquals("A", readAscii(valid.openConnection()));
+ assertEquals("A", readAscii(valid.openConnection()));
+
+ URL invalid = server.getUrl("/invalid");
+ assertEquals("B", readAscii(invalid.openConnection()));
+ assertEquals("C", readAscii(invalid.openConnection()));
+
+ 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 assertBadGateway(HttpURLConnection connection) throws IOException {
+ try {
+ connection.getInputStream();
+ fail();
+ } catch (FileNotFoundException expected) {
+ }
+ assertEquals(HttpURLConnection.HTTP_BAD_GATEWAY, 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 static class InsecureResponseCache extends ResponseCache {
+ private final HttpResponseCache delegate = new HttpResponseCache();
+
+ @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
+ return delegate.put(uri, connection);
+ }
+
+ @Override public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ final CacheResponse response = delegate.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/java/net/InetAddressTest.java b/luni/src/test/java/libcore/java/net/InetAddressTest.java
index b23dbf8..59200c5 100644
--- a/luni/src/test/java/libcore/java/net/InetAddressTest.java
+++ b/luni/src/test/java/libcore/java/net/InetAddressTest.java
@@ -28,6 +28,16 @@
assertEquals("/1.2.0.3", InetAddress.parseNumericAddress("1.2.3").toString());
assertEquals("/1.0.0.2", InetAddress.parseNumericAddress("1.2").toString());
assertEquals("/0.0.0.1", InetAddress.parseNumericAddress("1").toString());
+ assertEquals("/0.0.4.210", InetAddress.parseNumericAddress("1234").toString());
+ // Optional square brackets around IPv6 addresses, including mapped IPv4.
+ assertEquals("/2001:4860:800d::68", InetAddress.parseNumericAddress("[2001:4860:800d::68]").toString());
+ assertEquals("/127.0.0.1", InetAddress.parseNumericAddress("[::ffff:127.0.0.1]").toString());
+ try {
+ // Actual IPv4 addresses may not be surrounded by square brackets.
+ assertEquals("/127.0.0.1", InetAddress.parseNumericAddress("[127.0.0.1]").toString());
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
try {
// Almost numeric but invalid...
InetAddress.parseNumericAddress("1.");
@@ -48,4 +58,9 @@
public void test_getLoopbackAddress() throws Exception {
assertTrue(InetAddress.getLoopbackAddress().isLoopbackAddress());
}
+
+ public void test_0() throws Exception {
+ // The RI special-cases "0" for legacy IPv4 applications.
+ assertTrue(InetAddress.getByName("0").isAnyLocalAddress());
+ }
}
diff --git a/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java b/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java
index fe89adb..bdde645 100644
--- a/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java
+++ b/luni/src/test/java/libcore/java/net/NetworkInterfaceTest.java
@@ -17,6 +17,7 @@
package libcore.java.net;
import java.net.*;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -34,4 +35,65 @@
assertEquals(expected, actual);
}
+
+ public void testLoopback() throws Exception {
+ // We know lo shouldn't have a hardware address or an IPv4 broadcast address.
+ NetworkInterface lo = NetworkInterface.getByName("lo");
+ assertNull(lo.getHardwareAddress());
+ for (InterfaceAddress ia : lo.getInterfaceAddresses()) {
+ assertNull(ia.getBroadcast());
+ }
+
+ // But eth0, if it exists, should...
+ NetworkInterface eth0 = NetworkInterface.getByName("eth0");
+ if (eth0 != null) {
+ assertEquals(6, eth0.getHardwareAddress().length);
+ for (InterfaceAddress ia : eth0.getInterfaceAddresses()) {
+ if (ia.getAddress() instanceof Inet4Address) {
+ assertNotNull(ia.getBroadcast());
+ }
+ }
+ }
+ }
+
+ public void testDumpAll() throws Exception {
+ Set<String> allNames = new HashSet<String>();
+ Set<Integer> allIndexes = new HashSet<Integer>();
+ for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+ System.err.println(nif);
+ System.err.println(nif.getInterfaceAddresses());
+ String flags = nif.isUp() ? "UP" : "DOWN";
+ if (nif.isLoopback()) {
+ flags += " LOOPBACK";
+ }
+ if (nif.isPointToPoint()) {
+ flags += " PTP";
+ }
+ if (nif.isVirtual()) {
+ flags += " VIRTUAL";
+ }
+ if (nif.supportsMulticast()) {
+ flags += " MULTICAST";
+ }
+ flags += " MTU=" + nif.getMTU();
+ byte[] mac = nif.getHardwareAddress();
+ if (mac != null) {
+ flags += " HWADDR=";
+ for (int i = 0; i < mac.length; ++i) {
+ if (i > 0) {
+ flags += ":";
+ }
+ flags += String.format("%02x", mac[i]);
+ }
+ }
+ System.err.println(flags);
+ System.err.println("-");
+
+ assertFalse(allNames.contains(nif.getName()));
+ allNames.add(nif.getName());
+
+ assertFalse(allIndexes.contains(nif.getIndex()));
+ allIndexes.add(nif.getIndex());
+ }
+ }
}
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 5a67d8d..90161ea 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -16,11 +16,9 @@
package libcore.java.net;
-import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.CacheRequest;
@@ -32,14 +30,10 @@
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.ResponseCache;
-import java.net.SecureCacheResponse;
import java.net.SocketTimeoutException;
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.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
@@ -50,7 +44,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
@@ -66,7 +59,6 @@
import javax.net.ssl.X509TrustManager;
import libcore.java.security.TestKeyStore;
import libcore.javax.net.ssl.TestSSLContext;
-import tests.http.DefaultResponseCache;
import tests.http.MockResponse;
import tests.http.MockWebServer;
import tests.http.RecordedRequest;
@@ -329,148 +321,6 @@
}
}
- /**
- * 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 test_responseCaching() 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(true, 206);
- assertCached(false, 207);
- // (See test_responseCaching_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);
- }
- }
-
- public void test_responseCaching_300() throws Exception {
- // TODO: fix this for android
- assertCached(false, 300);
- }
-
- /**
- * 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(false, 410);
- }
-
- private void assertCached(boolean shouldPut, int responseCode) throws Exception {
- server = new MockWebServer();
- server.enqueue(new MockResponse()
- .setResponseCode(responseCode)
- .setBody("ABCDE")
- .addHeader("WWW-Authenticate: challenge"));
- server.play();
-
- DefaultResponseCache responseCache = new DefaultResponseCache();
- ResponseCache.setDefault(responseCache);
- URL url = server.getUrl("/");
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- assertEquals(responseCode, conn.getResponseCode());
-
- // exhaust the content stream
- try {
- // TODO: remove special case once testUnauthorizedResponseHandling() is fixed
- if (responseCode != 401) {
- readAscii(conn.getInputStream(), Integer.MAX_VALUE);
- }
- } catch (IOException expected) {
- }
-
- Set<URI> expectedCachedUris = shouldPut
- ? Collections.singleton(url.toURI())
- : Collections.<URI>emptySet();
- assertEquals(Integer.toString(responseCode),
- expectedCachedUris, responseCache.getContents().keySet());
- 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;
- 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("/");
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- assertEquals(body, readAscii(connection.getInputStream(), Integer.MAX_VALUE));
- assertEquals(1, cacheCount.get());
- }
-
public void testGetResponseCodeNoResponseBody() throws Exception {
server.enqueue(new MockResponse()
.addHeader("abc: def"));
@@ -763,186 +613,6 @@
}
}
- 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()
- .setStatus("HTTP/1.1 200 Fantastic");
- transferKind.setBody(response, "I love puppies but hate spiders", 1);
- server.enqueue(response);
- server.play();
-
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
- // 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(in, "I love ".length()));
- reliableSkip(in, "puppies but hate ".length());
- assertEquals("spiders", readAscii(in, "spiders".length()));
- assertEquals(-1, in.read());
- in.close();
- assertEquals(1, cache.getSuccessCount());
- assertEquals(0, cache.getAbortCount());
-
- urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
- in = urlConnection.getInputStream();
- assertEquals("I love puppies but hate spiders",
- readAscii(in, "I love puppies but hate spiders".length()));
- assertEquals(200, urlConnection.getResponseCode());
- assertEquals("Fantastic", urlConnection.getResponseMessage());
-
- assertEquals(-1, in.read());
- assertEquals(1, cache.getMissCount());
- assertEquals(1, cache.getHitCount());
- assertEquals(1, cache.getSuccessCount());
- assertEquals(0, cache.getAbortCount());
- }
-
- public void testSecureResponseCaching() throws IOException {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse().setBody("ABC"));
- server.play();
-
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
- // 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.getInputStream(), Integer.MAX_VALUE));
-
- assertEquals(1, cache.getMissCount());
- 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 insecureResponseCache = new InsecureResponseCache();
- ResponseCache.setDefault(insecureResponseCache);
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
- connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached!
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("DEF", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
- }
-
- public void testResponseCachingAndRedirects() throws IOException {
- server.enqueue(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
- .addHeader("Location: /foo"));
- server.enqueue(new MockResponse().setBody("ABC"));
- server.enqueue(new MockResponse().setBody("DEF"));
- server.play();
-
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
- HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection();
- assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
- connection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached!
- assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
- assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
- assertEquals(2, cache.getHitCount());
- }
-
- public void testSecureResponseCachingAndRedirects() throws IOException {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse()
- .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
- .addHeader("Location: /foo"));
- server.enqueue(new MockResponse().setBody("ABC"));
- server.enqueue(new MockResponse().setBody("DEF"));
- server.play();
-
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
- connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached!
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
-
- assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2
- 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.getInputStream(), Integer.MAX_VALUE);
- assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A"));
- }
-
- private void reliableSkip(InputStream in, int length) throws IOException {
- while (length > 0) {
- length -= in.skip(length);
- }
- }
-
/**
* Reads {@code count} characters from the stream. If the stream is
* exhausted before {@code count} characters can be read, the remaining
@@ -961,100 +631,6 @@
return result.toString();
}
- 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();
-
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
- 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) {
- }
-
- assertEquals(1, cache.getAbortCount());
- assertEquals(0, cache.getSuccessCount());
- assertContent("Request #2", server.getUrl("/").openConnection());
- assertEquals(1, cache.getAbortCount());
- assertEquals(1, cache.getSuccessCount());
- }
-
- 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();
-
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
- InputStream in = server.getUrl("/").openConnection().getInputStream();
- assertEquals("ABCDE", readAscii(in, 5));
- in.close();
- try {
- in.read();
- fail("Expected an IOException because the stream is closed.");
- } catch (IOException expected) {
- }
-
- assertEquals(1, cache.getAbortCount());
- assertEquals(0, cache.getSuccessCount());
- assertContent("Request #2", server.getUrl("/").openConnection());
- assertEquals(1, cache.getAbortCount());
- assertEquals(1, cache.getSuccessCount());
- }
-
- /**
- * 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;
- }
-
public void testMarkAndResetWithContentLengthHeader() throws IOException {
testMarkAndReset(TransferKind.FIXED_LENGTH);
}
@@ -1071,11 +647,9 @@
MockResponse response = new MockResponse();
transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
server.enqueue(response);
+ server.enqueue(response);
server.play();
- DefaultResponseCache cache = new DefaultResponseCache();
- ResponseCache.setDefault(cache);
-
InputStream in = server.getUrl("/").openConnection().getInputStream();
assertFalse("This implementation claims to support mark().", in.markSupported());
in.mark(5);
@@ -1086,10 +660,7 @@
} catch (IOException expected) {
}
assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
-
assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection());
- assertEquals(1, cache.getSuccessCount());
- assertEquals(1, cache.getHitCount());
}
/**
@@ -1923,7 +1494,7 @@
}
/**
- * Encodes the response body using GZIP and adds the corresponding header.
+ * Returns a gzipped copy of {@code bytes}.
*/
public byte[] gzip(byte[] bytes) throws IOException {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
@@ -1933,10 +1504,6 @@
return bytesOut.toByteArray();
}
- private <T> List<T> toListOrNull(T[] arrayOrNull) {
- return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null;
- }
-
/**
* Reads at most {@code limit} characters from {@code in} and asserts that
* content equals {@code expected}.
@@ -2081,28 +1648,4 @@
return true;
}
}
-
- private static class InsecureResponseCache extends ResponseCache {
- private final DefaultResponseCache delegate = new DefaultResponseCache();
-
- @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
- return delegate.put(uri, connection);
- }
-
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) throws IOException {
- final CacheResponse response = delegate.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/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java
index 5787a13..6f3f948 100644
--- a/luni/src/test/java/libcore/java/nio/BufferTest.java
+++ b/luni/src/test/java/libcore/java/nio/BufferTest.java
@@ -556,4 +556,86 @@
assertEquals(0, directBuffer.arrayOffset());
assertEquals(1, directSlice.arrayOffset());
}
+
+ // http://code.google.com/p/android/issues/detail?id=16184
+ public void testPutByteBuffer() throws Exception {
+ ByteBuffer dst = ByteBuffer.allocate(10).asReadOnlyBuffer();
+
+ // Can't put into a read-only buffer.
+ try {
+ dst.put(ByteBuffer.allocate(5));
+ fail();
+ } catch (ReadOnlyBufferException expected) {
+ }
+
+ // Can't put a buffer into itself.
+ dst = ByteBuffer.allocate(10);
+ try {
+ dst.put(dst);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Can't put the null ByteBuffer.
+ try {
+ dst.put((ByteBuffer) null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+
+ // Can't put a larger source into a smaller destination.
+ try {
+ dst.put(ByteBuffer.allocate(dst.capacity() + 1));
+ fail();
+ } catch (BufferOverflowException expected) {
+ }
+
+ assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocate(8), false);
+ assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocateDirect(8), false);
+ assertPutByteBuffer(ByteBuffer.allocate(10), allocateMapped(8), false);
+ assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocate(8), true);
+ assertPutByteBuffer(ByteBuffer.allocate(10), ByteBuffer.allocateDirect(8), true);
+ assertPutByteBuffer(ByteBuffer.allocate(10), allocateMapped(8), true);
+
+ assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocate(8), false);
+ assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocateDirect(8), false);
+ assertPutByteBuffer(ByteBuffer.allocateDirect(10), allocateMapped(8), false);
+ assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocate(8), true);
+ assertPutByteBuffer(ByteBuffer.allocateDirect(10), ByteBuffer.allocateDirect(8), true);
+ assertPutByteBuffer(ByteBuffer.allocateDirect(10), allocateMapped(8), true);
+ }
+
+ private void assertPutByteBuffer(ByteBuffer dst, ByteBuffer src, boolean readOnly) {
+ // Start 'dst' off as the index-identity pattern.
+ for (int i = 0; i < dst.capacity(); ++i) {
+ dst.put(i, (byte) i);
+ }
+ // Deliberately offset the position we'll write to by 1.
+ dst.position(1);
+
+ // Make the source more interesting.
+ for (int i = 0; i < src.capacity(); ++i) {
+ src.put(i, (byte) (16 + i));
+ }
+ if (readOnly) {
+ src = src.asReadOnlyBuffer();
+ }
+
+ ByteBuffer dst2 = dst.put(src);
+ assertSame(dst, dst2);
+ assertEquals(0, src.remaining());
+ assertEquals(src.position(), src.capacity());
+ assertEquals(dst.position(), src.capacity() + 1);
+ for (int i = 0; i < src.capacity(); ++i) {
+ assertEquals(src.get(i), dst.get(i + 1));
+ }
+
+ // No room for another.
+ src.position(0);
+ try {
+ dst.put(src);
+ fail();
+ } catch (BufferOverflowException expected) {
+ }
+ }
}
diff --git a/luni/src/test/java/libcore/java/security/KeyStoreTest.java b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
index bd062dc..f255392 100644
--- a/luni/src/test/java/libcore/java/security/KeyStoreTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyStoreTest.java
@@ -154,6 +154,11 @@
return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC"));
}
+ private static boolean isLoadStoreParameterSupported(KeyStore ks) {
+ // BouncyCastle's PKCS12 allows a JDKPKCS12StoreParameter
+ return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC"));
+ }
+
private static boolean isSetKeyByteArrayUnimplemented(KeyStore ks) {
// All of BouncyCastle's
// KeyStore.setKeyEntry(String,byte[],char[]) implementations
@@ -1556,6 +1561,10 @@
keyStore.store(null);
fail();
} catch (UnsupportedOperationException expected) {
+ assertFalse(isLoadStoreParameterSupported(keyStore));
+ } catch (IllegalArgumentException expected) {
+ // its supported, but null causes an exception
+ assertTrue(isLoadStoreParameterSupported(keyStore));
}
}
}
diff --git a/luni/src/test/java/libcore/java/util/SerializableTester.java b/luni/src/test/java/libcore/java/util/SerializableTester.java
index 1a28b64..20318d7 100644
--- a/luni/src/test/java/libcore/java/util/SerializableTester.java
+++ b/luni/src/test/java/libcore/java/util/SerializableTester.java
@@ -21,9 +21,7 @@
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
+import static junit.framework.Assert.*;
import junit.framework.AssertionFailedError;
public class SerializableTester<T> {
@@ -45,6 +43,10 @@
return a.equals(b);
}
+ /**
+ * Verifies that {@code deserialized} is valid. Implementations of this
+ * method may mutate {@code deserialized}.
+ */
protected void verify(T deserialized) {}
public void test() {
@@ -54,19 +56,19 @@
+ hexEncode(serialize(value)));
}
- // just a sanity check! if this fails, verify() is probably broken
- verify(value);
-
@SuppressWarnings("unchecked") // deserialize should return the proper type
T deserialized = (T) deserialize(hexDecode(golden));
assertTrue("User-constructed value doesn't equal deserialized golden value",
equals(value, deserialized));
- verify(deserialized);
@SuppressWarnings("unchecked") // deserialize should return the proper type
T reserialized = (T) deserialize(serialize(value));
assertTrue("User-constructed value doesn't equal itself, reserialized",
equals(value, reserialized));
+
+ // just a sanity check! if this fails, verify() is probably broken
+ verify(value);
+ verify(deserialized);
verify(reserialized);
} catch (Exception e) {
diff --git a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java
index cd6f886..29efd3a 100644
--- a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java
@@ -92,9 +92,12 @@
putKeySize("AES", 128);
putKeySize("AES", 192);
putKeySize("AES", 256);
+ putKeySize("ARC4", 1024);
+ putKeySize("ARC4", 40);
+ putKeySize("ARC4", 41);
+ putKeySize("ARCFOUR", 1024);
putKeySize("ARCFOUR", 40);
putKeySize("ARCFOUR", 41);
- putKeySize("ARCFOUR", 1024);
putKeySize("Blowfish", 32);
putKeySize("Blowfish", 32+8);
putKeySize("Blowfish", 448);
diff --git a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
new file mode 100644
index 0000000..0f38a5d
--- /dev/null
+++ b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java
@@ -0,0 +1,194 @@
+/*
+* 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 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.noStore);
+ assertEquals(new Date(1000), parsedHeaders.servedDate);
+ assertEquals(new Date(2000), parsedHeaders.expires);
+ assertEquals(new Date(3000), parsedHeaders.lastModified);
+ assertEquals("v1", parsedHeaders.etag);
+ assertTrue(parsedHeaders.noCache);
+ }
+
+ 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.noStore);
+ assertEquals(60, parsedHeaders.maxAgeSeconds);
+ assertTrue(parsedHeaders.isPublic);
+ }
+
+ public void testQuotedFieldName() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "private=\"Set-Cookie\"");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertEquals("Set-Cookie", parsedHeaders.privateField);
+ }
+
+ public void testUnquotedValue() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "private=Set-Cookie, no-store");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertEquals("Set-Cookie", parsedHeaders.privateField);
+ assertTrue(parsedHeaders.noStore);
+ }
+
+ public void testQuotedValue() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "private=\" a, no-cache, c \", no-store");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertEquals(" a, no-cache, c ", parsedHeaders.privateField);
+ assertTrue(parsedHeaders.noStore);
+ assertFalse(parsedHeaders.noCache);
+ }
+
+ public void testDanglingQuote() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "private=\"a, no-cache, c");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertEquals("a, no-cache, c", parsedHeaders.privateField);
+ assertFalse(parsedHeaders.noCache);
+ }
+
+ public void testTrailingComma() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "public,");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertTrue(parsedHeaders.isPublic);
+ assertNull(parsedHeaders.privateField);
+ }
+
+ public void testTrailingEquals() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "private=");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertEquals("", parsedHeaders.privateField);
+ }
+
+ public void testSpaceBeforeEquals() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "max-age =60");
+ RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+ assertEquals(60, parsedHeaders.maxAgeSeconds);
+ }
+
+ public void testSpaceAfterEqualsWithQuotes() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "max-age= \"60\"");
+ RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+ assertEquals(60, parsedHeaders.maxAgeSeconds);
+ }
+
+ public void testSpaceAfterEqualsWithoutQuotes() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "max-age= 60");
+ RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+ assertEquals(60, parsedHeaders.maxAgeSeconds);
+ }
+
+ 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.noCache);
+ assertEquals(60, parsedHeaders.maxAgeSeconds);
+ assertEquals(70, parsedHeaders.maxStaleSeconds);
+ assertEquals(80, parsedHeaders.minFreshSeconds);
+ assertTrue(parsedHeaders.onlyIfCached);
+ }
+
+ 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", "PRIVATE=a");
+ headers.add("Cache-Control", "MUST-REVALIDATE");
+ ResponseHeaders parsedHeaders = new ResponseHeaders(uri, headers);
+ assertTrue(parsedHeaders.noCache);
+ assertTrue(parsedHeaders.noStore);
+ assertEquals(60, parsedHeaders.maxAgeSeconds);
+ assertEquals(70, parsedHeaders.sMaxAgeSeconds);
+ assertTrue(parsedHeaders.isPublic);
+ assertEquals("a", parsedHeaders.privateField);
+ assertTrue(parsedHeaders.mustRevalidate);
+ }
+
+ public void testPragmaDirectivesAreCaseInsensitive() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Pragma", "NO-CACHE");
+ RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+ assertTrue(parsedHeaders.noCache);
+ }
+
+ public void testMissingInteger() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "max-age");
+ RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+ assertEquals(-1, parsedHeaders.maxAgeSeconds);
+ }
+
+ public void testInvalidInteger() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "MAX-AGE=pi");
+ RequestHeaders requestHeaders = new RequestHeaders(uri, headers);
+ assertEquals(-1, requestHeaders.maxAgeSeconds);
+ }
+
+ 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.maxAgeSeconds);
+ }
+
+ public void testNegativeInteger() {
+ RawHeaders headers = new RawHeaders();
+ headers.add("Cache-Control", "MAX-AGE=-2");
+ RequestHeaders parsedHeaders = new RequestHeaders(uri, headers);
+ assertEquals(0, parsedHeaders.maxAgeSeconds);
+ }
+}
diff --git a/luni/src/test/java/libcore/java/net/HttpHeadersTest.java b/luni/src/test/java/libcore/net/http/RawHeadersTest.java
similarity index 84%
rename from luni/src/test/java/libcore/java/net/HttpHeadersTest.java
rename to luni/src/test/java/libcore/net/http/RawHeadersTest.java
index 4650184..d99e851 100644
--- a/luni/src/test/java/libcore/java/net/HttpHeadersTest.java
+++ b/luni/src/test/java/libcore/net/http/RawHeadersTest.java
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-package libcore.java.net;
+package libcore.net.http;
import java.util.Arrays;
import junit.framework.TestCase;
-import libcore.net.http.HttpHeaders;
-public class HttpHeadersTest extends TestCase {
+public class RawHeadersTest extends TestCase {
// http://code.google.com/p/android/issues/detail?id=6684
public void test_caseInsensitiveButCasePreserving() {
- HttpHeaders h = new HttpHeaders();
+ RawHeaders h = new RawHeaders();
h.add("Content-Type", "text/plain");
// Case-insensitive:
assertEquals("text/plain", h.get("Content-Type"));
@@ -42,14 +41,14 @@
// 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() {
- HttpHeaders h1 = new HttpHeaders();
+ RawHeaders h1 = new RawHeaders();
h1.add("key", "value1");
h1.add("key", "value2");
- HttpHeaders h2 = HttpHeaders.fromMultimap(h1.toMultimap());
+ RawHeaders h2 = RawHeaders.fromMultimap(h1.toMultimap());
assertEquals(2, h2.length());
- assertEquals("key", h2.getKey(0));
+ assertEquals("key", h2.getFieldName(0));
assertEquals("value1", h2.getValue(0));
- assertEquals("key", h2.getKey(1));
+ assertEquals("key", h2.getFieldName(1));
assertEquals("value2", h2.getValue(1));
}
}
diff --git a/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java b/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java
index cfc46b3..f59f457 100644
--- a/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java
+++ b/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java
@@ -16,6 +16,33 @@
package libcore.xml;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
@@ -36,34 +63,6 @@
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.ErrorListener;
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMResult;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
/**
* The <a href="http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xslt">OASIS
* XSLT conformance test suite</a>, adapted for use by JUnit. To run these tests
diff --git a/luni/src/test/java/tests/security/cert/CertificateTest.java b/luni/src/test/java/tests/security/cert/CertificateTest.java
index 6570416..d13e16b 100644
--- a/luni/src/test/java/tests/security/cert/CertificateTest.java
+++ b/luni/src/test/java/tests/security/cert/CertificateTest.java
@@ -434,8 +434,8 @@
* serialization/deserialization compatibility with RI.
*/
public void testSerializationCompatibility() throws Exception {
- //create test file (once)
-// SerializationTest.createGoldenFile("device/dalvik/libcore/security/src/test/resources/serialization", this, TestUtils.rootCertificateSS);
+ // create test file (once)
+ // SerializationTest.createGoldenFile("/sdcard", this, TestUtils.rootCertificateSS);
TestUtils.initCertPathSSCertChain();
SerializationTest.verifyGolden(this, TestUtils.rootCertificateSS);
diff --git a/luni/src/test/java/tests/targets/security/KeyStoreTest.java b/luni/src/test/java/tests/targets/security/KeyStoreTest.java
index 41206c8..e5bbb62 100644
--- a/luni/src/test/java/tests/targets/security/KeyStoreTest.java
+++ b/luni/src/test/java/tests/targets/security/KeyStoreTest.java
@@ -35,7 +35,7 @@
import java.security.cert.CertificateFactory;
import junit.framework.TestCase;
-public class KeyStoreTest extends TestCase {
+public abstract class KeyStoreTest extends TestCase {
private final String algorithmName;
private final byte[] keyStoreData;
diff --git a/luni/src/test/java/tests/targets/security/cert/CertificateTest.java b/luni/src/test/java/tests/targets/security/cert/CertificateTest.java
index dde7664..c24051a 100644
--- a/luni/src/test/java/tests/targets/security/cert/CertificateTest.java
+++ b/luni/src/test/java/tests/targets/security/cert/CertificateTest.java
@@ -20,10 +20,10 @@
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
-import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorResult;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
@@ -33,6 +33,7 @@
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
+import libcore.java.security.StandardNames;
public class CertificateTest extends TestCase {
@@ -591,8 +592,7 @@
public void testVerifyMD5() throws Exception {
Provider[] providers = Security.getProviders("CertificateFactory.X509");
for (Provider provider : providers) {
- CertificateFactory certificateFactory = CertificateFactory
- .getInstance("X509", provider);
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", provider);
Certificate certificate = certificateFactory
.generateCertificate(new ByteArrayInputStream(selfSignedCertMD5
@@ -606,8 +606,7 @@
public void testVerifyMD2() throws Exception {
Provider[] providers = Security.getProviders("CertificateFactory.X509");
for (Provider provider : providers) {
- CertificateFactory certificateFactory = CertificateFactory
- .getInstance("X509", provider);
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", provider);
Certificate certificate = certificateFactory
.generateCertificate(new ByteArrayInputStream(selfSignedCertMD2
@@ -622,27 +621,21 @@
}
public void testVerifyMD2_chain() throws Exception {
- CertificateFactory certificateFactory = CertificateFactory
- .getInstance("X509");
-
- CertPath path;
- CertPathValidator certPathValidator;
- PKIXParameters params;
- CertPathValidatorResult res;
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
// First check with the trust anchor not included in the chain
- path = certificateFactory.generateCertPath(getCertList(true, false));
+ CertPath path = certificateFactory.generateCertPath(getCertList(true, false));
- certPathValidator = CertPathValidator.getInstance("PKIX");
- params = createPKIXParams();
+ CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");
+ PKIXParameters params = createPKIXParams();
- res = certPathValidator.validate(path, params);
+ CertPathValidatorResult res = certPathValidator.validate(path, params);
assertTrue("wrong result type",
- res instanceof PKIXCertPathValidatorResult);
+ res instanceof PKIXCertPathValidatorResult);
PKIXCertPathValidatorResult r = (PKIXCertPathValidatorResult) res;
- assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
- .contains(r.getTrustAnchor()));
+ assertTrue("Wrong trust anchor returned",
+ params.getTrustAnchors().contains(r.getTrustAnchor()));
// Now check with the trust anchor included in the chain
path = certificateFactory.generateCertPath(getCertList(true, true));
@@ -650,37 +643,38 @@
certPathValidator = CertPathValidator.getInstance("PKIX");
params = createPKIXParams();
- res = certPathValidator.validate(path, params);
- assertTrue("wrong result type",
- res instanceof PKIXCertPathValidatorResult);
+ if (StandardNames.IS_RI) {
+ res = certPathValidator.validate(path, params);
+ assertTrue("wrong result type", res instanceof PKIXCertPathValidatorResult);
- r = (PKIXCertPathValidatorResult) res;
- assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
- .contains(r.getTrustAnchor()));
+ r = (PKIXCertPathValidatorResult) res;
+ assertTrue("Wrong trust anchor returned",
+ params.getTrustAnchors().contains(r.getTrustAnchor()));
+ } else {
+ try {
+ certPathValidator.validate(path, params);
+ fail();
+ } catch (CertPathValidatorException expected) {
+ }
+ }
}
public void testVerifyMD5_chain() throws Exception {
- CertificateFactory certificateFactory = CertificateFactory
- .getInstance("X509");
-
- CertPath path;
- CertPathValidator certPathValidator;
- PKIXParameters params;
- CertPathValidatorResult res;
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
// First check with the trust anchor not included in the chain
- path = certificateFactory.generateCertPath(getCertList(false, false));
+ CertPath path = certificateFactory.generateCertPath(getCertList(false, false));
- certPathValidator = CertPathValidator.getInstance("PKIX");
- params = createPKIXParams();
+ CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");
+ PKIXParameters params = createPKIXParams();
- res = certPathValidator.validate(path, params);
+ CertPathValidatorResult res = certPathValidator.validate(path, params);
assertTrue("wrong result type",
- res instanceof PKIXCertPathValidatorResult);
+ res instanceof PKIXCertPathValidatorResult);
PKIXCertPathValidatorResult r = (PKIXCertPathValidatorResult) res;
- assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
- .contains(r.getTrustAnchor()));
+ assertTrue("Wrong trust anchor returned",
+ params.getTrustAnchors().contains(r.getTrustAnchor()));
// Now check with the trust anchor included in the chain
path = certificateFactory.generateCertPath(getCertList(false, true));
@@ -693,8 +687,8 @@
res instanceof PKIXCertPathValidatorResult);
r = (PKIXCertPathValidatorResult) res;
- assertTrue("Wrong trust anchor returned", params.getTrustAnchors()
- .contains(r.getTrustAnchor()));
+ assertTrue("Wrong trust anchor returned",
+ params.getTrustAnchors().contains(r.getTrustAnchor()));
}
private X509Certificate[] certs= new X509Certificate[3];
@@ -702,8 +696,7 @@
private List<Certificate> getCertList(boolean useMD2root,
boolean includeRootInChain) throws Exception {
- CertificateFactory certificateFactory = CertificateFactory
- .getInstance("X509");
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
if (useMD2root) {
certs[0] = (X509Certificate) certificateFactory
diff --git a/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser b/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser
index b5bc9e0..f7e2a9a 100644
--- a/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser
+++ b/luni/src/test/resources/serialization/tests/security/cert/CertificateTest.golden.ser
Binary files differ
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index 3f37000..9163fbe 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -330,11 +330,11 @@
unprovide("TrustManagerFactory", "PKIX");
provide("TrustManagerFactory", "X509");
- // different names: ARCFOUR vs ARC4/RC$
+ // different names: ARCFOUR vs ARC4
unprovide("Cipher", "ARCFOUR");
provide("Cipher", "ARC4");
unprovide("KeyGenerator", "ARCFOUR");
- provide("KeyGenerator", "RC4");
+ provide("KeyGenerator", "ARC4");
// different case names: Blowfish vs BLOWFISH
unprovide("AlgorithmParameters", "Blowfish");
diff --git a/support/src/test/java/libcore/net/http/HttpResponseCache.java b/support/src/test/java/libcore/net/http/HttpResponseCache.java
new file mode 100644
index 0000000..a551e27
--- /dev/null
+++ b/support/src/test/java/libcore/net/http/HttpResponseCache.java
@@ -0,0 +1,300 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.HttpURLConnection;
+import java.net.ResponseCache;
+import java.net.SecureCacheResponse;
+import java.net.URI;
+import java.net.URLConnection;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+/**
+ * Cache all responses in memory by URI.
+ *
+ * TODO: disk storage, tuning knobs, LRU
+ * TODO: move this class to android.util
+ */
+public final class HttpResponseCache extends ResponseCache {
+ private final Map<URI, Entry> entries = new HashMap<URI, Entry>();
+ private int abortCount;
+ private int successCount;
+ private int hitCount;
+ private int missCount;
+
+ @Override public synchronized CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ Entry entry = entries.get(uri);
+ if (entry == null) {
+ missCount++;
+ return null;
+ }
+
+ if (!requestMethod.equals(entry.requestMethod)) {
+ return null;
+ }
+
+ // RawHeaders headers = RawHeaders.fromMultimap(entry.responseHeaders);
+
+ hitCount++;
+ return entry.asResponse();
+ }
+
+ @Override public CacheRequest put(URI uri, URLConnection urlConnection)
+ throws IOException {
+ if (!(urlConnection instanceof HttpURLConnection)) {
+ return null;
+ }
+
+ HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
+ String requestMethod = httpConnection.getRequestMethod();
+
+ // Invalidate the cache on POST, PUT and DELETE.
+ if (requestMethod.equals("POST") || requestMethod.equals("PUT")
+ || requestMethod.equals("DELETE")) {
+ entries.remove(uri);
+ }
+
+ /*
+ * 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.
+ */
+ if (!requestMethod.equals("GET")) {
+ return null;
+ }
+
+ // For implementation simplicity, don't cache responses that have a Vary field.
+ if (httpConnection.getHeaderField("Vary") != null) {
+ return null;
+ }
+
+ return new Entry(uri, httpConnection).asRequest();
+ }
+
+ public synchronized Map<URI, Entry> getContents() {
+ return new HashMap<URI, Entry>(entries);
+ }
+
+ /**
+ * Returns the number of requests that were aborted before they were closed.
+ */
+ public synchronized int getAbortCount() {
+ return abortCount;
+ }
+
+ /**
+ * Returns the number of requests that were closed successfully.
+ */
+ public synchronized int getSuccessCount() {
+ return successCount;
+ }
+
+ /**
+ * Returns the number of responses served by the cache.
+ */
+ public synchronized int getHitCount() {
+ return hitCount;
+ }
+
+ /**
+ * Returns the number of responses that couldn't be served by the cache.
+ */
+ public synchronized int getMissCount() {
+ return missCount;
+ }
+
+ public final class Entry {
+ private final ByteArrayOutputStream body = new ByteArrayOutputStream() {
+ private boolean closed;
+ @Override public void close() throws IOException {
+ synchronized (HttpResponseCache.this) {
+ if (closed) {
+ return;
+ }
+
+ super.close();
+ entries.put(uri, Entry.this);
+ successCount++;
+ closed = true;
+ }
+ }
+ };
+
+ private final String requestMethod;
+ private final Map<String, List<String>> responseHeaders;
+ private final URI uri;
+ private final CacheResponse cacheResponse;
+
+ private Entry(URI uri, HttpURLConnection connection) {
+ this.uri = uri;
+ this.requestMethod = connection.getRequestMethod();
+ this.responseHeaders = deepCopy(connection.getHeaderFields());
+ this.cacheResponse = connection instanceof HttpsURLConnection
+ ? new SecureCacheResponseImpl(responseHeaders, body,
+ (HttpsURLConnection) connection)
+ : new CacheResponseImpl(responseHeaders, body);
+ }
+
+ public CacheRequest asRequest() {
+ return new CacheRequest() {
+ private boolean aborted;
+ @Override public void abort() {
+ synchronized (HttpResponseCache.this) {
+ if (aborted) {
+ return;
+ }
+
+ abortCount++;
+ aborted = true;
+ }
+ }
+ @Override public OutputStream getBody() throws IOException {
+ return body;
+ }
+ };
+ }
+
+ public CacheResponse asResponse() {
+ return cacheResponse;
+ }
+
+ public byte[] getBytes() {
+ return body.toByteArray();
+ }
+ }
+
+ private final class CacheResponseImpl extends CacheResponse {
+ private final Map<String, List<String>> headers;
+ private final ByteArrayOutputStream bytesOut;
+
+ public CacheResponseImpl(Map<String, List<String>> headers,
+ ByteArrayOutputStream bytesOut) {
+ this.headers = headers;
+ this.bytesOut = bytesOut;
+ }
+
+ @Override public Map<String, List<String>> getHeaders() {
+ return deepCopy(headers);
+ }
+
+ @Override public InputStream getBody() {
+ return new ByteArrayInputStream(bytesOut.toByteArray());
+ }
+ }
+
+ private final class SecureCacheResponseImpl extends SecureCacheResponse {
+ private final Map<String, List<String>> headers;
+ private final ByteArrayOutputStream bytesOut;
+ private final String cipherSuite;
+ private final Certificate[] localCertificates;
+ private final List<Certificate> serverCertificates;
+ private final Principal peerPrincipal;
+ private final Principal localPrincipal;
+
+ public SecureCacheResponseImpl(Map<String, List<String>> headers,
+ ByteArrayOutputStream bytesOut,
+ HttpsURLConnection httpsConnection) {
+ this.headers = headers;
+ this.bytesOut = bytesOut;
+
+ /*
+ * Retrieve the fields eagerly to avoid needing a strong
+ * reference to the connection. We do acrobatics for the two
+ * methods that can throw so that the cache response also
+ * throws.
+ */
+ List<Certificate> serverCertificatesNonFinal = null;
+ try {
+ serverCertificatesNonFinal = Arrays.asList(
+ httpsConnection.getServerCertificates());
+ } catch (SSLPeerUnverifiedException ignored) {
+ }
+ Principal peerPrincipalNonFinal = null;
+ try {
+ peerPrincipalNonFinal = httpsConnection.getPeerPrincipal();
+ } catch (SSLPeerUnverifiedException ignored) {
+ }
+ this.cipherSuite = httpsConnection.getCipherSuite();
+ this.localCertificates = httpsConnection.getLocalCertificates();
+ this.serverCertificates = serverCertificatesNonFinal;
+ this.peerPrincipal = peerPrincipalNonFinal;
+ this.localPrincipal = httpsConnection.getLocalPrincipal();
+ }
+
+ @Override public Map<String, List<String>> getHeaders() {
+ return deepCopy(headers);
+ }
+
+ @Override public InputStream getBody() {
+ return new ByteArrayInputStream(bytesOut.toByteArray());
+ }
+
+ @Override public String getCipherSuite() {
+ return cipherSuite;
+ }
+
+ @Override public List<Certificate> getLocalCertificateChain() {
+ return localCertificates != null
+ ? Arrays.asList(localCertificates.clone())
+ : null;
+ }
+
+ @Override public List<Certificate> getServerCertificateChain()
+ throws SSLPeerUnverifiedException {
+ if (serverCertificates == null) {
+ throw new SSLPeerUnverifiedException(null);
+ }
+ return new ArrayList<Certificate>(serverCertificates);
+ }
+
+ @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+ if (peerPrincipal == null) {
+ throw new SSLPeerUnverifiedException(null);
+ }
+ return peerPrincipal;
+ }
+
+ @Override public Principal getLocalPrincipal() {
+ return localPrincipal;
+ }
+ }
+
+ private static Map<String, List<String>> deepCopy(Map<String, List<String>> input) {
+ Map<String, List<String>> result = new LinkedHashMap<String, List<String>>(input);
+ for (Map.Entry<String, List<String>> entry : result.entrySet()) {
+ entry.setValue(new ArrayList<String>(entry.getValue()));
+ }
+ return result;
+ }
+}
diff --git a/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java b/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java
index f20f9de..8d0c3a4 100644
--- a/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java
+++ b/support/src/test/java/org/apache/harmony/testframework/serialization/SerializationTest.java
@@ -38,7 +38,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import junit.framework.Assert;
import junit.framework.TestCase;
/**
@@ -178,20 +177,16 @@
/**
* Compares deserialized and reference objects.
*
- * @param initial -
- * initial object used for creating serialized form
- * @param deserialized -
- * deserialized object
+ * @param initial - initial object used for creating serialized form
+ * @param deserialized - deserialized object
*/
void assertDeserialized(Serializable initial, Serializable deserialized);
}
// default comparator for a class that has equals(Object) method
private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() {
- public void assertDeserialized(Serializable initial,
- Serializable deserialized) {
-
- Assert.assertEquals(initial, deserialized);
+ public void assertDeserialized(Serializable initial, Serializable deserialized) {
+ assertEquals(initial, deserialized);
}
};
@@ -199,34 +194,30 @@
* Comparator for verifying that deserialized object is the same as initial.
*/
public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() {
- public void assertDeserialized(Serializable initial,
- Serializable deserialized) {
-
- Assert.assertSame(initial, deserialized);
+ public void assertDeserialized(Serializable initial, Serializable deserialized) {
+ assertSame(initial, deserialized);
}
};
/**
- * Comparator for java.lang.Throwable objects
+ * Comparator for Throwable objects
*/
public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() {
public void assertDeserialized(Serializable initial, Serializable deserialized) {
-
Throwable initThr = (Throwable) initial;
Throwable dserThr = (Throwable) deserialized;
// verify class
- Assert.assertEquals(initThr.getClass(), dserThr.getClass());
+ assertEquals(initThr.getClass(), dserThr.getClass());
// verify message
- Assert.assertEquals(initThr.getMessage(), dserThr.getMessage());
+ assertEquals(initThr.getMessage(), dserThr.getMessage());
// verify cause
if (initThr.getCause() == null) {
- Assert.assertNull(dserThr.getCause());
+ assertNull(dserThr.getCause());
} else {
- Assert.assertNotNull(dserThr.getCause());
-
+ assertNotNull(dserThr.getCause());
THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(),
dserThr.getCause());
}
@@ -234,7 +225,7 @@
};
/**
- * Comparator for java.security.PermissionCollection objects
+ * Comparator for PermissionCollection objects
*/
public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() {
public void assertDeserialized(Serializable initial, Serializable deserialized) {
@@ -243,10 +234,10 @@
PermissionCollection dserPC = (PermissionCollection) deserialized;
// verify class
- Assert.assertEquals(initPC.getClass(), dserPC.getClass());
+ assertEquals(initPC.getClass(), dserPC.getClass());
// verify 'readOnly' field
- Assert.assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
+ assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
// verify collection of permissions
Collection<Permission> refCollection = new HashSet<Permission>(
@@ -254,7 +245,7 @@
Collection<Permission> tstCollection = new HashSet<Permission>(
Collections.list(dserPC.elements()));
- Assert.assertEquals(refCollection, tstCollection);
+ assertEquals(refCollection, tstCollection);
}
};
@@ -262,20 +253,18 @@
* Returns <code>comparator</code> for provided serializable
* <code>object</code>.
*
- * The <code>comparator</code> is searched in the following order: <br>-
- * if <code>test</code> implements SerializableAssert interface then it is
+ * The <code>comparator</code> is searched in the following order: <br>
+ * - if <code>test</code> implements SerializableAssert interface then it is
* selected as </code>comparator</code>.<br>- if passed <code>object</code>
* has class in its classes hierarchy that overrides <code>equals(Object)</code>
* method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
* method tries to select one of known comparators basing on <code>object's</code>
* class,for example, if passed <code>object</code> is instance of
- * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>-
- * otherwise RuntimeException is thrown
+ * Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>
+ * - otherwise RuntimeException is thrown
*
- * @param test -
- * test case
- * @param object -
- * object to be compared
+ * @param test - test case
+ * @param object - object to be compared
* @return object's comparator
*/
public static SerializableAssert defineComparator(TestCase test, Object object)
@@ -285,9 +274,7 @@
return (SerializableAssert) test;
}
- Method m = object.getClass().getMethod("equals",
- new Class[] { Object.class });
-
+ Method m = object.getClass().getMethod("equals", new Class[] { Object.class });
if (m.getDeclaringClass() != Object.class) {
// one of classes overrides Object.equals(Object) method
// use default comparator
@@ -296,12 +283,12 @@
// TODO use generics to detect comparator
// instead of 'instanceof' for the first element
- if (object instanceof java.lang.Throwable) {
+ if (object instanceof Throwable) {
return THROWABLE_COMPARATOR;
- } else if (object instanceof java.security.PermissionCollection) {
+ }
+ if (object instanceof PermissionCollection) {
return PERMISSION_COLLECTION_COMPARATOR;
}
-
throw new RuntimeException("Failed to detect comparator");
}
@@ -311,10 +298,8 @@
* The method invokes <br>
* verifyGolden(test, object, defineComparator(test, object));
*
- * @param test -
- * test case
- * @param object -
- * to be compared
+ * @param test - test case
+ * @param object - to be compared
*/
public static void verifyGolden(TestCase test, Object object) throws Exception {
verifyGolden(test, object, defineComparator(test, object));
@@ -328,20 +313,14 @@
* folder, reads an object from the loaded file and compares it with
* <code>object</code> using specified <code>comparator</code>.
*
- * @param test-
- * test case
- * @param object-
- * to be compared
- * @param comparator -
- * for comparing (de)serialized objects
+ * @param test - test case
+ * @param object - to be compared
+ * @param comparator - for comparing (de)serialized objects
*/
public static void verifyGolden(TestCase test, Object object, SerializableAssert comparator)
throws Exception {
-
- Assert.assertNotNull("Null comparator", comparator);
-
+ assertNotNull("Null comparator", comparator);
Serializable deserialized = getObject(test, ".golden.ser");
-
comparator.assertDeserialized((Serializable) object, deserialized);
}
@@ -352,14 +331,11 @@
* The method invokes <br>
* verifyGolden(test, objects, defineComparator(test, object[0]));
*
- * @param test -
- * test case
- * @param objects -
- * array of objects to be compared
+ * @param test - test case
+ * @param objects - array of objects to be compared
*/
public static void verifyGolden(TestCase test, Object[] objects) throws Exception {
-
- Assert.assertFalse("Empty array", objects.length == 0);
+ assertFalse("Empty array", objects.length == 0);
verifyGolden(test, objects, defineComparator(test, objects[0]));
}
@@ -374,21 +350,16 @@
* using specified <code>comparator</code>. (<code>N</code> is index
* in object's array.)
*
- * @param test-
- * test case
- * @param objects -
- * array of objects to be compared
- * @param comparator -
- * for comparing (de)serialized objects
+ * @param test - test case
+ * @param objects - array of objects to be compared
+ * @param comparator - for comparing (de)serialized objects
*/
public static void verifyGolden(TestCase test, Object[] objects, SerializableAssert comparator)
throws Exception {
-
- Assert.assertFalse("Empty array", objects.length == 0);
+ assertFalse("Empty array", objects.length == 0);
for (int i = 0; i < objects.length; i++) {
Serializable deserialized = getObject(test, ".golden." + i + ".ser");
- comparator.assertDeserialized((Serializable) objects[i],
- deserialized);
+ comparator.assertDeserialized((Serializable) objects[i], deserialized);
}
}
@@ -398,11 +369,9 @@
* The method invokes <br>
* verifySelf(object, defineComparator(null, object));
*
- * @param object -
- * to be serialized/deserialized
+ * @param object - to be serialized/deserialized
*/
public static void verifySelf(Object object) throws Exception {
-
verifySelf(object, defineComparator(null, object));
}
@@ -412,16 +381,11 @@
* The method serialize/deserialize <code>object</code> and compare it
* with initial <code>object</code>.
*
- * @param object -
- * object to be serialized/deserialized
- * @param comparator -
- * for comparing serialized/deserialized object with initial
- * object
+ * @param object - object to be serialized/deserialized
+ * @param comparator - for comparing serialized/deserialized object with initial object
*/
public static void verifySelf(Object object, SerializableAssert comparator) throws Exception {
-
Serializable initial = (Serializable) object;
-
comparator.assertDeserialized(initial, copySerializable(initial));
}
@@ -432,12 +396,10 @@
* The method invokes <br>
* verifySelf(objects, defineComparator(null, object[0]));
*
- * @param objects -
- * array of objects to be serialized/deserialized
+ * @param objects - array of objects to be serialized/deserialized
*/
public static void verifySelf(Object[] objects) throws Exception {
-
- Assert.assertFalse("Empty array", objects.length == 0);
+ assertFalse("Empty array", objects.length == 0);
verifySelf(objects, defineComparator(null, objects[0]));
}
@@ -448,35 +410,25 @@
* The method serialize/deserialize each object in <code>objects</code>
* array and compare it with initial object.
*
- * @param objects -
- * array of objects to be serialized/deserialized
- * @param comparator -
- * for comparing serialized/deserialized object with initial
- * object
+ * @param objects - array of objects to be serialized/deserialized
+ * @param comparator - for comparing serialized/deserialized object with initial object
*/
public static void verifySelf(Object[] objects, SerializableAssert comparator)
throws Exception {
-
- Assert.assertFalse("Empty array", objects.length == 0);
- for(Object entry: objects){
+ assertFalse("Empty array", objects.length == 0);
+ for (Object entry: objects){
verifySelf(entry, comparator);
}
}
private static Serializable getObject(TestCase test, String toAppend) throws Exception {
-
StringBuilder path = new StringBuilder("/serialization");
-
path.append(File.separatorChar);
path.append(test.getClass().getName().replace('.', File.separatorChar));
path.append(toAppend);
- InputStream in = SerializationTest.class
- .getResourceAsStream(path.toString());
-
- Assert.assertNotNull("Failed to load serialization resource file: "
- + path, in);
-
+ InputStream in = SerializationTest.class.getResourceAsStream(path.toString());
+ assertNotNull("Failed to load serialization resource file: " + path, in);
return getObjectFromStream(in);
}
@@ -486,52 +438,41 @@
* The folder for created file is: <code>root + test's package name</code>.
* The file name is: <code>test's name + "golden.ser"</code>
*
- * @param root -
- * root directory for serialization resource files
- * @param test -
- * test case
- * @param object -
- * object to be serialized
- * @throws IOException -
- * if I/O error
+ * @param root - root directory for serialization resource files
+ * @param test - test case
+ * @param object - to be serialized
+ * @throws IOException - if I/O error
*/
public static void createGoldenFile(String root, TestCase test, Object object)
throws IOException {
-
- String goldenPath = test.getClass().getName().replace('.',
- File.separatorChar)
- + ".golden.ser";
-
+ String goldenPath = (test.getClass().getName().replace('.', File.separatorChar)
+ + ".golden.ser");
if (root != null) {
goldenPath = root + File.separatorChar + goldenPath;
}
-
File goldenFile = new File(goldenPath);
goldenFile.getParentFile().mkdirs();
+ assertTrue("Could not create " + goldenFile.getParentFile(),
+ goldenFile.getParentFile().isDirectory());
goldenFile.createNewFile();
-
putObjectToStream(object, new FileOutputStream(goldenFile));
// don't forget to remove it from test case after using
- Assert.fail("Generating golden file.\nGolden file name:"
- + goldenFile.getAbsolutePath());
+ fail("Generating golden file. Golden file name: " + goldenFile.getAbsolutePath());
}
/**
* Copies an object by serializing/deserializing it.
*
- * @param initial -
- * an object to be copied
+ * @param initial - an object to be copied
* @return copy of provided object
*/
public static Serializable copySerializable(Serializable initial)
throws IOException, ClassNotFoundException {
-
ByteArrayOutputStream out = new ByteArrayOutputStream();
putObjectToStream(initial, out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
-
return getObjectFromStream(in);
}
}
diff --git a/support/src/test/java/tests/http/DefaultResponseCache.java b/support/src/test/java/tests/http/DefaultResponseCache.java
deleted file mode 100644
index 912e879..0000000
--- a/support/src/test/java/tests/http/DefaultResponseCache.java
+++ /dev/null
@@ -1,232 +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 tests.http;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.ResponseCache;
-import java.net.SecureCacheResponse;
-import java.net.URI;
-import java.net.URLConnection;
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
-/**
- * Cache all responses in memory by URI.
- */
-public final class DefaultResponseCache extends ResponseCache {
- private final Map<URI, Entry> entries = new HashMap<URI, Entry>();
- private int abortCount;
- private int successCount;
- private int hitCount;
- private int missCount;
-
- @Override public synchronized CacheResponse get(URI uri, String requestMethod,
- Map<String, List<String>> requestHeaders) throws IOException {
- // TODO: honor the request headers in the cache key
- Entry entry = entries.get(uri);
- if (entry != null) {
- hitCount++;
- return entry.asResponse();
- } else {
- missCount++;
- return null;
- }
- }
-
- @Override public CacheRequest put(URI uri, URLConnection urlConnection)
- throws IOException {
- // TODO: honor the response headers for cache invalidation
- return new Entry(uri, urlConnection).asRequest();
- }
-
- public synchronized Map<URI, Entry> getContents() {
- return new HashMap<URI, Entry>(entries);
- }
-
- /**
- * Returns the number of requests that were aborted before they were closed.
- */
- public synchronized int getAbortCount() {
- return abortCount;
- }
-
- /**
- * Returns the number of requests that were closed successfully.
- */
- public synchronized int getSuccessCount() {
- return successCount;
- }
-
- /**
- * Returns the number of responses served by the cache.
- */
- public synchronized int getHitCount() {
- return hitCount;
- }
-
- /**
- * Returns the number of responses that couldn't be served by the cache.
- */
- public synchronized int getMissCount() {
- return missCount;
- }
-
- public final class Entry {
- private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream() {
- private boolean closed;
- @Override public void close() throws IOException {
- synchronized (DefaultResponseCache.this) {
- if (closed) {
- return;
- }
-
- super.close();
- entries.put(uri, Entry.this);
- successCount++;
- closed = true;
- }
- }
- };
- private final Map<String, List<String>> headers;
- private final URI uri;
- private final CacheResponse cacheResponse;
-
- private Entry(URI uri, URLConnection connection) {
- this.uri = uri;
- this.headers = deepCopy(connection.getHeaderFields());
- this.cacheResponse = connectionToCacheResponse(connection);
- }
-
- public CacheRequest asRequest() {
- return new CacheRequest() {
- private boolean aborted;
- @Override public void abort() {
- synchronized (DefaultResponseCache.this) {
- if (aborted) {
- return;
- }
-
- abortCount++;
- aborted = true;
- }
- }
- @Override public OutputStream getBody() throws IOException {
- return bytesOut;
- }
- };
- }
-
- private CacheResponse connectionToCacheResponse(URLConnection connection) {
- if (!(connection instanceof HttpsURLConnection)) {
- return new CacheResponse() {
- @Override public InputStream getBody() {
- return new ByteArrayInputStream(getBytes());
- }
- @Override public Map<String, List<String>> getHeaders() {
- return deepCopy(headers);
- }
- };
- }
-
- HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
-
- /*
- * Retrieve the fields eagerly to avoid needing a strong reference
- * to the connection. We do acrobatics for the two methods that can
- * throw so that the cache response also throws.
- */
- List<Certificate> serverCertificatesNonFinal = null;
- try {
- serverCertificatesNonFinal = Arrays.asList(httpsConnection.getServerCertificates());
- } catch (SSLPeerUnverifiedException ignored) {
- }
- Principal peerPrincipalNonFinal = null;
- try {
- peerPrincipalNonFinal = httpsConnection.getPeerPrincipal();
- } catch (SSLPeerUnverifiedException ignored) {
- }
- final String cipherSuite = httpsConnection.getCipherSuite();
- final Certificate[] localCertificates = httpsConnection.getLocalCertificates();
- final List<Certificate> serverCertificates = serverCertificatesNonFinal;
- final Principal peerPrincipal = peerPrincipalNonFinal;
- final Principal localPrincipal = httpsConnection.getLocalPrincipal();
-
- return new SecureCacheResponse() {
- @Override public InputStream getBody() {
- return new ByteArrayInputStream(getBytes());
- }
- @Override public Map<String, List<String>> getHeaders() {
- return deepCopy(headers);
- }
- @Override public String getCipherSuite() {
- return cipherSuite;
- }
- @Override public List<Certificate> getLocalCertificateChain() {
- return localCertificates != null
- ? Arrays.asList(localCertificates.clone())
- : null;
- }
- @Override public List<Certificate> getServerCertificateChain()
- throws SSLPeerUnverifiedException {
- if (serverCertificates == null) {
- throw new SSLPeerUnverifiedException(null);
- }
- return new ArrayList<Certificate>(serverCertificates);
- }
- @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- if (peerPrincipal == null) {
- throw new SSLPeerUnverifiedException(null);
- }
- return peerPrincipal;
- }
- @Override public Principal getLocalPrincipal() {
- return localPrincipal;
- }
- };
- }
-
- public CacheResponse asResponse() {
- return cacheResponse;
- }
-
- public byte[] getBytes() {
- return bytesOut.toByteArray();
- }
- }
-
- private static Map<String, List<String>> deepCopy(Map<String, List<String>> input) {
- Map<String, List<String>> result = new LinkedHashMap<String, List<String>>(input);
- for (Map.Entry<String, List<String>> entry : result.entrySet()) {
- entry.setValue(new ArrayList<String>(entry.getValue()));
- }
- return result;
- }
-}
diff --git a/support/src/test/java/tests/http/MockResponse.java b/support/src/test/java/tests/http/MockResponse.java
index 314bfc3..d3aaa9d 100644
--- a/support/src/test/java/tests/http/MockResponse.java
+++ b/support/src/test/java/tests/http/MockResponse.java
@@ -26,7 +26,8 @@
/**
* A scripted response to be replayed by the mock web server.
*/
-public class MockResponse {
+public class MockResponse implements Cloneable {
+ private static final String RFC_1123_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
private static final String EMPTY_BODY_HEADER = "Content-Length: 0";
private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
private static final byte[] EMPTY_BODY = new byte[0];
@@ -40,6 +41,16 @@
headers.add(EMPTY_BODY_HEADER);
}
+ @Override public MockResponse clone() {
+ try {
+ MockResponse result = (MockResponse) super.clone();
+ result.headers = new ArrayList<String>(result.headers);
+ return result;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
/**
* Returns the HTTP response line, such as "HTTP/1.1 200 OK".
*/
diff --git a/support/src/test/java/tests/http/MockWebServer.java b/support/src/test/java/tests/http/MockWebServer.java
index 2083117..2d8215c 100644
--- a/support/src/test/java/tests/http/MockWebServer.java
+++ b/support/src/test/java/tests/http/MockWebServer.java
@@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
@@ -37,7 +38,6 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -138,7 +138,7 @@
}
public void enqueue(MockResponse response) {
- responseQueue.add(response);
+ responseQueue.add(response.clone());
}
/**
@@ -155,15 +155,26 @@
}
/**
- * Starts the server, serves all enqueued requests, and shuts the server
- * down.
+ * Equivalent to {@code play(0)}.
*/
public void play() throws IOException {
+ play(0);
+ }
+
+ /**
+ * Starts the server, serves all enqueued requests, and shuts the server
+ * down.
+ *
+ * @param port the port to listen to, or 0 for any available port.
+ * Automated tests should always use port 0 to avoid flakiness when a
+ * specific port is unavailable.
+ */
+ public void play(int port) throws IOException {
executor = Executors.newCachedThreadPool();
- serverSocket = new ServerSocket(0);
+ serverSocket = new ServerSocket(port);
serverSocket.setReuseAddress(true);
- port = serverSocket.getLocalPort();
+ this.port = serverSocket.getLocalPort();
executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
public void run() {
try {
@@ -347,13 +358,15 @@
}
}
- if (request.startsWith("GET ") || request.startsWith("CONNECT ")) {
+ if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
+ || request.startsWith("HEAD ") || request.startsWith("DELETE ")
+ || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
if (hasBody) {
- throw new IllegalArgumentException("GET requests should not have a body!");
+ throw new IllegalArgumentException("Request must not have a body: " + request);
}
- } else if (request.startsWith("POST ")) {
+ } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
if (!hasBody) {
- throw new IllegalArgumentException("POST requests must have a body!");
+ throw new IllegalArgumentException("Request must have a body: " + request);
}
} else {
throw new UnsupportedOperationException("Unexpected method: " + request);
@@ -371,6 +384,13 @@
throw new IllegalStateException("Unexpected request: " + request);
}
+ // to permit interactive/browser testing, ignore requests for favicons
+ if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
+ System.out.println("served " + request.getRequestLine());
+ return new MockResponse()
+ .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
+ }
+
if (singleResponse) {
return responseQueue.peek();
} else {
diff --git a/support/src/test/java/tests/security/AlgorithmParametersTest.java b/support/src/test/java/tests/security/AlgorithmParametersTest.java
index 67255fc..e93f216 100644
--- a/support/src/test/java/tests/security/AlgorithmParametersTest.java
+++ b/support/src/test/java/tests/security/AlgorithmParametersTest.java
@@ -21,7 +21,7 @@
import java.security.spec.InvalidParameterSpecException;
import junit.framework.TestCase;
-public class AlgorithmParametersTest extends TestCase {
+public abstract class AlgorithmParametersTest extends TestCase {
private final String algorithmName;
private final TestHelper<AlgorithmParameters> helper;