diff options
172 files changed, 7391 insertions, 5756 deletions
@@ -54,5 +54,18 @@ endif ifeq ($(WITH_HOST_DALVIK),true) .PHONY: dalvik-host - dalvik-host: dalvik $(HOST_OUT)/bin/dalvikvm $(HOST_OUT)/bin/dexopt $(HOST_OUT)/lib/libsqlite.so cacerts-host $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.dat $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.idx $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.version core-hostdex bouncycastle-hostdex apache-xml-hostdex $(call intermediates-dir-for,JAVA_LIBRARIES,core-tests,,COMMON)/classes.jar + dalvik-host: \ + dalvik \ + $(HOST_OUT)/bin/dalvikvm \ + $(HOST_OUT)/bin/dexopt \ + $(HOST_OUT)/lib/libsqlite.so \ + cacerts-host \ + $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.dat \ + $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.idx \ + $(HOST_OUT)/usr/share/zoneinfo/zoneinfo.version \ + core-hostdex \ + bouncycastle-hostdex \ + apache-xml-hostdex \ + apache-harmony-tests-hostdex \ + $(call intermediates-dir-for,JAVA_LIBRARIES,core-tests,,COMMON)/classes.jar endif diff --git a/dalvik/src/main/java/dalvik/system/BlockGuard.java b/dalvik/src/main/java/dalvik/system/BlockGuard.java index a0d9828..cf9789e 100644 --- a/dalvik/src/main/java/dalvik/system/BlockGuard.java +++ b/dalvik/src/main/java/dalvik/system/BlockGuard.java @@ -25,12 +25,10 @@ import java.net.DatagramPacket; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketImpl; - import libcore.io.ErrnoException; import libcore.io.Libcore; import libcore.io.StructLinger; -import org.apache.harmony.luni.platform.INetworkSystem; - +import libcore.util.EmptyArray; import static libcore.io.OsConstants.*; /** @@ -219,8 +217,8 @@ public final class BlockGuard { throws IOException { if (!TAG_SOCKETS) return; - final byte[] tagBytes = tag != null ? tag.getBytes() : new byte[0]; - final byte[] uidBytes = uid != -1 ? Integer.toString(uid).getBytes() : new byte[0]; + final byte[] tagBytes = tag != null ? tag.getBytes() : EmptyArray.BYTE; + final byte[] uidBytes = uid != -1 ? Integer.toString(uid).getBytes() : EmptyArray.BYTE; final ByteArrayOutputStream buffer = new ByteArrayOutputStream( 4 + tagBytes.length + uidBytes.length); @@ -244,125 +242,4 @@ public final class BlockGuard { } private BlockGuard() {} - - /** - * A network wrapper that calls the policy check functions. - */ - public static class WrappedNetworkSystem implements INetworkSystem { - private final INetworkSystem mNetwork; - - public WrappedNetworkSystem(INetworkSystem network) { - mNetwork = network; - } - - public void accept(FileDescriptor serverFd, SocketImpl newSocket, - FileDescriptor clientFd) throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - mNetwork.accept(serverFd, newSocket, clientFd); - tagSocketFd(clientFd); - } - - public void bind(FileDescriptor aFD, InetAddress inetAddress, int port) - throws SocketException { - mNetwork.bind(aFD, inetAddress, port); - } - - public int read(FileDescriptor aFD, byte[] data, int offset, int count) throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.read(aFD, data, offset, count); - } - - public int readDirect(FileDescriptor aFD, int address, int count) throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.readDirect(aFD, address, count); - } - - public int write(FileDescriptor fd, byte[] data, int offset, int count) - throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.write(fd, data, offset, count); - } - - public int writeDirect(FileDescriptor fd, int address, int offset, int count) - throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.writeDirect(fd, address, offset, count); - } - - public boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.connect(fd, inetAddress, port); - } - - public boolean isConnected(FileDescriptor fd, int timeout) throws IOException { - if (timeout != 0) { - // Greater than 0 is a timeout, but zero means "poll and return immediately". - BlockGuard.getThreadPolicy().onNetwork(); - } - return mNetwork.isConnected(fd, timeout); - } - - public int send(FileDescriptor fd, byte[] data, int offset, int length, - int port, InetAddress inetAddress) throws IOException { - // Note: no BlockGuard violation. We permit datagrams - // without hostname lookups. (short, bounded amount of time) - return mNetwork.send(fd, data, offset, length, port, inetAddress); - } - - public int sendDirect(FileDescriptor fd, int address, int offset, int length, - int port, InetAddress inetAddress) throws IOException { - // Note: no BlockGuard violation. We permit datagrams - // without hostname lookups. (short, bounded amount of time) - return mNetwork.sendDirect(fd, address, offset, length, port, inetAddress); - } - - public int recv(FileDescriptor fd, DatagramPacket packet, byte[] data, int offset, - int length, boolean peek, boolean connected) throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.recv(fd, packet, data, offset, length, peek, connected); - } - - public int recvDirect(FileDescriptor fd, DatagramPacket packet, int address, int offset, - int length, boolean peek, boolean connected) throws IOException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.recvDirect(fd, packet, address, offset, length, peek, connected); - } - - public void disconnectDatagram(FileDescriptor aFD) throws SocketException { - mNetwork.disconnectDatagram(aFD); - } - - public void sendUrgentData(FileDescriptor fd, byte value) { - mNetwork.sendUrgentData(fd, value); - } - - public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs, - int numReadable, int numWritable, long timeout, int[] flags) - throws SocketException { - BlockGuard.getThreadPolicy().onNetwork(); - return mNetwork.select(readFDs, writeFDs, numReadable, numWritable, timeout, flags); - } - - 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 - // extra hoops. - if (isLingerSocket(aFD)) { - BlockGuard.getThreadPolicy().onNetwork(); - } - mNetwork.close(aFD); - } - - private boolean isLingerSocket(FileDescriptor fd) { - try { - StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER); - return linger.isOn() && linger.l_linger > 0; - } catch (Exception ignored) { - // We're called via Socket.close (which doesn't ask for us to be called), so we - // must not throw here, because Socket.close must not throw if asked to close an - // already-closed socket. - return false; - } - } - } } diff --git a/expectations/icebox.txt b/expectations/icebox.txt index 4d664a6..d85efc1 100644 --- a/expectations/icebox.txt +++ b/expectations/icebox.txt @@ -42,13 +42,13 @@ description: "Dalvik doesn't support XML Schemas, DTDs or validation", bug: 3268630, name: "libcore.xml.DeclarationTest#testGetXmlEncoding", - substring: "This implementation doesn't parse the encoding from the XML declaration expected:<ISO-8859-1> but was:<null>" + substring: "This implementation doesn't parse the encoding from the XML declaration" }, { description: "Dalvik doesn't support XML Schemas, DTDs or validation", bug: 3268630, name: "libcore.xml.DeclarationTest#testGetXmlStandalone", - substring: "This implementation doesn't parse standalone from the XML declaration expected:<true> but was:<false>" + substring: "This implementation doesn't parse standalone from the XML declaration" }, { description: "Dalvik doesn't support XML Schemas, DTDs or validation", diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt index 3d2eb5f..f784394 100644 --- a/expectations/knownfailures.txt +++ b/expectations/knownfailures.txt @@ -11,6 +11,11 @@ bug: 3483365 }, { + description: "Expat uses an unbounded number of global references", + name: "libcore.xml.ExpatSaxParserTest#testGlobalReferenceTableOverflow", + bug: 2772628 +}, +{ description: "Test fails, Intermediate certificate lacks BasicConstraints", name: "com.android.org.bouncycastle.jce.provider.PKIXCertPathValidatorSpiTest#testTrustAndRemoteCertificatesWithDifferentEncodings", bug: 3474648 diff --git a/junit/src/main/java/junit/framework/ComparisonFailure.java b/junit/src/main/java/junit/framework/ComparisonFailure.java index 0cb2cee..ccd476b 100644 --- a/junit/src/main/java/junit/framework/ComparisonFailure.java +++ b/junit/src/main/java/junit/framework/ComparisonFailure.java @@ -34,7 +34,7 @@ public class ComparisonFailure extends AssertionFailedError { int end= Math.min(fExpected.length(), fActual.length()); int i= 0; - for(; i < end; i++) { + for (; i < end; i++) { if (fExpected.charAt(i) != fActual.charAt(i)) break; } diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java index 5b26792..6c2da1e 100644 --- a/luni/src/main/java/java/io/FileInputStream.java +++ b/luni/src/main/java/java/io/FileInputStream.java @@ -23,6 +23,7 @@ import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.util.Arrays; import libcore.io.ErrnoException; +import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.Streams; @@ -76,7 +77,7 @@ public class FileInputStream extends InputStream implements Closeable { if (file == null) { throw new NullPointerException("file == null"); } - this.fd = IoUtils.open(file.getAbsolutePath(), O_RDONLY); + this.fd = IoBridge.open(file.getAbsolutePath(), O_RDONLY); this.ownedFd = fd; guard.open("close"); } @@ -108,7 +109,7 @@ public class FileInputStream extends InputStream implements Closeable { @Override public int available() throws IOException { - return IoUtils.available(fd); + return IoBridge.available(fd); } @Override @@ -171,7 +172,7 @@ public class FileInputStream extends InputStream implements Closeable { } @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { - return IoUtils.read(fd, buffer, byteOffset, byteCount); + return IoBridge.read(fd, buffer, byteOffset, byteCount); } @Override diff --git a/luni/src/main/java/java/io/FileOutputStream.java b/luni/src/main/java/java/io/FileOutputStream.java index 8c61281..cc57571 100644 --- a/luni/src/main/java/java/io/FileOutputStream.java +++ b/luni/src/main/java/java/io/FileOutputStream.java @@ -21,6 +21,7 @@ import dalvik.system.CloseGuard; import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.util.Arrays; +import libcore.io.IoBridge; import libcore.io.IoUtils; import static libcore.io.OsConstants.*; @@ -88,7 +89,7 @@ public class FileOutputStream extends OutputStream implements Closeable { throw new NullPointerException("file == null"); } this.mode = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC); - this.fd = IoUtils.open(file.getAbsolutePath(), mode); + this.fd = IoBridge.open(file.getAbsolutePath(), mode); this.ownedFd = fd; this.guard.open("close"); } @@ -175,7 +176,7 @@ public class FileOutputStream extends OutputStream implements Closeable { @Override public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException { - IoUtils.write(fd, buffer, byteOffset, byteCount); + IoBridge.write(fd, buffer, byteOffset, byteCount); } @Override diff --git a/luni/src/main/java/java/io/InterruptedIOException.java b/luni/src/main/java/java/io/InterruptedIOException.java index 8d79ba5..af307bd 100644 --- a/luni/src/main/java/java/io/InterruptedIOException.java +++ b/luni/src/main/java/java/io/InterruptedIOException.java @@ -32,20 +32,23 @@ public class InterruptedIOException extends IOException { public int bytesTransferred; /** - * Constructs a new {@code InterruptedIOException} with its stack trace - * filled in. + * Constructs a new instance. */ public InterruptedIOException() { } /** - * Constructs a new {@code InterruptedIOException} with its stack trace and - * detail message filled in. - * - * @param detailMessage - * the detail message for this exception. + * Constructs a new instance with the given detail message. */ public InterruptedIOException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public InterruptedIOException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/io/ObjectStreamClass.java b/luni/src/main/java/java/io/ObjectStreamClass.java index 386cfb0..e87fcd4 100644 --- a/luni/src/main/java/java/io/ObjectStreamClass.java +++ b/luni/src/main/java/java/io/ObjectStreamClass.java @@ -810,7 +810,7 @@ public class ObjectStreamClass implements Serializable { return loadFields == null ? fields().clone() : loadFields.clone(); } - private volatile List<ObjectStreamClass> cachedHierarchy; + private transient volatile List<ObjectStreamClass> cachedHierarchy; List<ObjectStreamClass> getHierarchy() { List<ObjectStreamClass> result = cachedHierarchy; diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java index 8517604..dde779e 100644 --- a/luni/src/main/java/java/io/RandomAccessFile.java +++ b/luni/src/main/java/java/io/RandomAccessFile.java @@ -24,6 +24,7 @@ import java.nio.channels.FileChannel; import java.nio.charset.ModifiedUtf8; import java.util.Arrays; import libcore.io.ErrnoException; +import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.Memory; @@ -114,7 +115,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { throw new IllegalArgumentException("Invalid mode: " + mode); } this.mode = flags; - this.fd = IoUtils.open(file.getAbsolutePath(), flags); + this.fd = IoBridge.open(file.getAbsolutePath(), flags); // if we are in "rws" mode, attempt to sync file+metadata if (syncMetadata) { @@ -286,7 +287,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { * if this file is closed or another I/O error occurs. */ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { - return IoUtils.read(fd, buffer, byteOffset, byteCount); + return IoBridge.read(fd, buffer, byteOffset, byteCount); } /** @@ -688,7 +689,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { * if an I/O error occurs while writing to this file. */ public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException { - IoUtils.write(fd, buffer, byteOffset, byteCount); + IoBridge.write(fd, buffer, byteOffset, byteCount); // if we are in "rws" mode, attempt to sync file+metadata if (syncMetadata) { fd.sync(); diff --git a/luni/src/main/java/java/lang/Boolean.java b/luni/src/main/java/java/lang/Boolean.java index f95a38b..2c8ffc8 100644 --- a/luni/src/main/java/java/lang/Boolean.java +++ b/luni/src/main/java/java/lang/Boolean.java @@ -101,9 +101,9 @@ public final class Boolean implements Serializable, Comparable<Boolean> { * {@code Boolean}; {@code false} otherwise. */ @Override + @FindBugsSuppressWarnings("RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN") public boolean equals(Object o) { - return (o == this) - || ((o instanceof Boolean) && (value == ((Boolean) o).value)); + return (o == this) || ((o instanceof Boolean) && (((Boolean) o).value == value)); } /** diff --git a/luni/src/main/java/java/lang/Byte.java b/luni/src/main/java/java/lang/Byte.java index 7e0b293..bd1a1e4 100644 --- a/luni/src/main/java/java/lang/Byte.java +++ b/luni/src/main/java/java/lang/Byte.java @@ -22,6 +22,7 @@ package java.lang; * * @since 1.1 */ +@FindBugsSuppressWarnings("DM_NUMBER_CTOR") public final class Byte extends Number implements Comparable<Byte> { private static final long serialVersionUID = -7183698231559129828L; @@ -155,9 +156,9 @@ public final class Byte extends Number implements Comparable<Byte> { * {@code Byte}; {@code false} otherwise. */ @Override + @FindBugsSuppressWarnings("RC_REF_COMPARISON") public boolean equals(Object object) { - return (object == this) || (object instanceof Byte) - && (value == ((Byte) object).value); + return (object == this) || ((object instanceof Byte) && (((Byte) object).value == value)); } @Override diff --git a/luni/src/main/java/java/lang/Character.java b/luni/src/main/java/java/lang/Character.java index 1e39428..67c5c71 100644 --- a/luni/src/main/java/java/lang/Character.java +++ b/luni/src/main/java/java/lang/Character.java @@ -96,6 +96,7 @@ import java.util.Arrays; * * @since 1.0 */ +@FindBugsSuppressWarnings("DM_NUMBER_CTOR") public final class Character implements Serializable, Comparable<Character> { private static final long serialVersionUID = 3786198910865385080L; @@ -1641,7 +1642,7 @@ public final class Character implements Serializable, Comparable<Character> { private static final Character[] SMALL_VALUES = new Character[128]; static { - for(int i = 0; i < 128; i++) { + for (int i = 0; i < 128; i++) { SMALL_VALUES[i] = new Character((char) i); } } @@ -2405,7 +2406,7 @@ public final class Character implements Serializable, Comparable<Character> { */ @Override public boolean equals(Object object) { - return (object instanceof Character) && (value == ((Character) object).value); + return (object instanceof Character) && (((Character) object).value == value); } /** diff --git a/luni/src/main/java/java/lang/ClassLoader.java b/luni/src/main/java/java/lang/ClassLoader.java index 2ec066f..0cdc448 100644 --- a/luni/src/main/java/java/lang/ClassLoader.java +++ b/luni/src/main/java/java/lang/ClassLoader.java @@ -747,6 +747,7 @@ class BootClassLoader extends ClassLoader { private static BootClassLoader instance; + @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); diff --git a/luni/src/main/java/java/lang/Double.java b/luni/src/main/java/java/lang/Double.java index 1748128..456529b 100644 --- a/luni/src/main/java/java/lang/Double.java +++ b/luni/src/main/java/java/lang/Double.java @@ -166,31 +166,18 @@ public final class Double extends Number implements Comparable<Double> { } /** - * Converts the specified double value to a binary representation conforming - * to the IEEE 754 floating-point double precision bit layout. All - * <em>Not-a-Number (NaN)</em> values are converted to a single NaN - * representation ({@code 0x7ff8000000000000L}). - * - * @param value - * the double value to convert. - * @return the IEEE 754 floating-point double precision representation of - * {@code value}. - * @see #doubleToRawLongBits(double) - * @see #longBitsToDouble(long) + * Returns an integer corresponding to the bits of the given + * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> double precision + * {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN + * representation ({@code 0x7ff8000000000000L}) (compare to {@link #doubleToRawLongBits}). */ public static native long doubleToLongBits(double value); /** - * Converts the specified double value to a binary representation conforming - * to the IEEE 754 floating-point double precision bit layout. - * <em>Not-a-Number (NaN)</em> values are preserved. - * - * @param value - * the double value to convert. - * @return the IEEE 754 floating-point double precision representation of - * {@code value}. - * @see #doubleToLongBits(double) - * @see #longBitsToDouble(long) + * Returns an integer corresponding to the bits of the given + * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> double precision + * {@code value}. <em>Not-a-Number (NaN)</em> values are preserved (compare + * to {@link #doubleToLongBits}). */ public static native long doubleToRawLongBits(double value); @@ -219,9 +206,8 @@ public final class Double extends Number implements Comparable<Double> { */ @Override public boolean equals(Object object) { - return (object == this) - || (object instanceof Double) - && (doubleToLongBits(this.value) == doubleToLongBits(((Double) object).value)); + return (object instanceof Double) && + (doubleToLongBits(this.value) == doubleToLongBits(((Double) object).value)); } @Override @@ -286,15 +272,8 @@ public final class Double extends Number implements Comparable<Double> { } /** - * Converts the specified IEEE 754 floating-point double precision bit - * pattern to a Java double value. - * - * @param bits - * the IEEE 754 floating-point double precision representation of - * a double value. - * @return the double value converted from {@code bits}. - * @see #doubleToLongBits(double) - * @see #doubleToRawLongBits(double) + * Returns the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> + * double precision float corresponding to the given {@code bits}. */ public static native double longBitsToDouble(long bits); @@ -313,7 +292,7 @@ public final class Double extends Number implements Comparable<Double> { * if {@code string} cannot be parsed as a double value. */ public static double parseDouble(String string) throws NumberFormatException { - return org.apache.harmony.luni.util.FloatingPointParser.parseDouble(string); + return StringToReal.parseDouble(string); } @Override @@ -421,7 +400,7 @@ public final class Double extends Number implements Comparable<Double> { */ public static String toHexString(double d) { /* - * Reference: http://en.wikipedia.org/wiki/IEEE_754 + * Reference: http://en.wikipedia.org/wiki/IEEE_754-1985 */ if (d != d) { return "NaN"; diff --git a/luni/src/main/java/java/lang/FinalizerThread.java b/luni/src/main/java/java/lang/FinalizerThread.java index 5f478df..978332c 100644 --- a/luni/src/main/java/java/lang/FinalizerThread.java +++ b/luni/src/main/java/java/lang/FinalizerThread.java @@ -67,6 +67,7 @@ public final class FinalizerThread extends Thread { } } + @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") private void doFinalize(FinalizerReference<Object> reference) { FinalizerReference.remove(reference); Object obj = reference.get(); diff --git a/luni/src/main/java/java/lang/FindBugsSuppressWarnings.java b/luni/src/main/java/java/lang/FindBugsSuppressWarnings.java new file mode 100644 index 0000000..ac753bd --- /dev/null +++ b/luni/src/main/java/java/lang/FindBugsSuppressWarnings.java @@ -0,0 +1,46 @@ +/* + * 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; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.CLASS; +import java.lang.annotation.Target; + +/** + * Suppress FindBugs warnings on the annotated element. FindBugs will recognize + * any annotation that has class retention and whose name ends with + * "SuppressWarnings". + * + * @hide + */ +@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) +@Retention(CLASS) +public @interface FindBugsSuppressWarnings { + + /** + * The <a href="http://findbugs.sourceforge.net/bugDescriptions.html">FindBugs + * Patterns</a> to suppress, such as {@code SE_TRANSIENT_FIELD_NOT_RESTORED} + * or {@code Se}. Full, upper case names are preferred. + */ + String[] value(); +} diff --git a/luni/src/main/java/java/lang/Float.java b/luni/src/main/java/java/lang/Float.java index bbbb7f7..900b2a0 100644 --- a/luni/src/main/java/java/lang/Float.java +++ b/luni/src/main/java/java/lang/Float.java @@ -189,37 +189,23 @@ public final class Float extends Number implements Comparable<Float> { */ @Override public boolean equals(Object object) { - return (object == this) - || (object instanceof Float) - && (floatToIntBits(this.value) == floatToIntBits(((Float) object).value)); + return (object instanceof Float) && + (floatToIntBits(this.value) == floatToIntBits(((Float) object).value)); } /** - * Converts the specified float value to a binary representation conforming - * to the IEEE 754 floating-point single precision bit layout. All - * <em>Not-a-Number (NaN)</em> values are converted to a single NaN - * representation ({@code 0x7fc00000}). - * - * @param value - * the float value to convert. - * @return the IEEE 754 floating-point single precision representation of - * {@code value}. - * @see #floatToRawIntBits(float) - * @see #intBitsToFloat(int) + * Returns an integer corresponding to the bits of the given + * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> single precision + * float {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN + * representation ({@code 0x7fc00000}) (compare to {@link #floatToRawIntBits}). */ public static native int floatToIntBits(float value); /** - * Converts the specified float value to a binary representation conforming - * to the IEEE 754 floating-point single precision bit layout. - * <em>Not-a-Number (NaN)</em> values are preserved. - * - * @param value - * the float value to convert. - * @return the IEEE 754 floating-point single precision representation of - * {@code value}. - * @see #floatToIntBits(float) - * @see #intBitsToFloat(int) + * Returns an integer corresponding to the bits of the given + * <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> single precision + * float {@code value}. <em>Not-a-Number (NaN)</em> values are preserved (compare + * to {@link #floatToIntBits}). */ public static native int floatToRawIntBits(float value); @@ -239,15 +225,8 @@ public final class Float extends Number implements Comparable<Float> { } /** - * Converts the specified IEEE 754 floating-point single precision bit - * pattern to a Java float value. - * - * @param bits - * the IEEE 754 floating-point single precision representation of - * a float value. - * @return the float value converted from {@code bits}. - * @see #floatToIntBits(float) - * @see #floatToRawIntBits(float) + * Returns the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> + * single precision float corresponding to the given {@code bits}. */ public static native float intBitsToFloat(int bits); @@ -318,8 +297,7 @@ public final class Float extends Number implements Comparable<Float> { * @since 1.2 */ public static float parseFloat(String string) throws NumberFormatException { - return org.apache.harmony.luni.util.FloatingPointParser - .parseFloat(string); + return StringToReal.parseFloat(string); } @Override @@ -429,7 +407,7 @@ public final class Float extends Number implements Comparable<Float> { */ public static String toHexString(float f) { /* - * Reference: http://en.wikipedia.org/wiki/IEEE_754 + * Reference: http://en.wikipedia.org/wiki/IEEE_754-1985 */ if (f != f) { return "NaN"; diff --git a/luni/src/main/java/org/apache/harmony/luni/util/HexStringParser.java b/luni/src/main/java/java/lang/HexStringParser.java index 9e20a16..8593b99 100644 --- a/luni/src/main/java/org/apache/harmony/luni/util/HexStringParser.java +++ b/luni/src/main/java/java/lang/HexStringParser.java @@ -15,13 +15,17 @@ * limitations under the License. */ -package org.apache.harmony.luni.util; +package java.lang; import java.util.regex.Matcher; import java.util.regex.Pattern; /* * Parses hex string to a single or double precision floating point number. + * + * TODO: rewrite this! + * + * @hide */ final class HexStringParser { diff --git a/luni/src/main/java/java/lang/Integer.java b/luni/src/main/java/java/lang/Integer.java index 06d7f90..15511a8 100644 --- a/luni/src/main/java/java/lang/Integer.java +++ b/luni/src/main/java/java/lang/Integer.java @@ -29,6 +29,7 @@ package java.lang; * @see java.lang.Long * @since 1.0 */ +@FindBugsSuppressWarnings("DM_NUMBER_CTOR") public final class Integer extends Number implements Comparable<Integer> { private static final long serialVersionUID = 1360826667806852920L; @@ -205,7 +206,7 @@ public final class Integer extends Number implements Comparable<Integer> { */ @Override public boolean equals(Object o) { - return o instanceof Integer && ((Integer) o).value == value; + return (o instanceof Integer) && (((Integer) o).value == value); } @Override @@ -712,7 +713,7 @@ public final class Integer extends Number implements Comparable<Integer> { private static final Integer[] SMALL_VALUES = new Integer[256]; static { - for(int i = -128; i < 128; i++) { + for (int i = -128; i < 128; i++) { SMALL_VALUES[i + 128] = new Integer(i); } } diff --git a/luni/src/main/java/java/lang/IntegralToString.java b/luni/src/main/java/java/lang/IntegralToString.java index c858a37..03a5359 100644 --- a/luni/src/main/java/java/lang/IntegralToString.java +++ b/luni/src/main/java/java/lang/IntegralToString.java @@ -462,6 +462,17 @@ public final class IntegralToString { return new String(0, 2, buf); } + public static String bytesToHexString(byte[] bytes, boolean upperCase) { + char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS; + char[] buf = new char[bytes.length * 2]; + int c = 0; + for (byte b : bytes) { + buf[c++] = digits[(b >> 4) & 0xf]; + buf[c++] = digits[b & 0xf]; + } + return new String(buf); + } + public static String intToHexString(int i, boolean upperCase, int minWidth) { int bufLen = 8; // Max number of hex digits in an int char[] buf = new char[bufLen]; diff --git a/luni/src/main/java/java/lang/Long.java b/luni/src/main/java/java/lang/Long.java index d6605e6..8b592e2 100644 --- a/luni/src/main/java/java/lang/Long.java +++ b/luni/src/main/java/java/lang/Long.java @@ -29,6 +29,7 @@ package java.lang; * @see java.lang.Integer * @since 1.0 */ +@FindBugsSuppressWarnings("DM_NUMBER_CTOR") public final class Long extends Number implements Comparable<Long> { private static final long serialVersionUID = 4290774380558885855L; @@ -193,7 +194,7 @@ public final class Long extends Number implements Comparable<Long> { */ @Override public boolean equals(Object o) { - return o instanceof Long && ((Long) o).value == value; + return (o instanceof Long) && (((Long) o).value == value); } @Override @@ -724,8 +725,7 @@ public final class Long extends Number implements Comparable<Long> { * @since 1.5 */ public static Long valueOf(long v) { - return v >= 128 || v < -128 ? new Long(v) - : SMALL_VALUES[((int) v) + 128]; + return v >= 128 || v < -128 ? new Long(v) : SMALL_VALUES[((int) v) + 128]; } /** @@ -734,7 +734,7 @@ public final class Long extends Number implements Comparable<Long> { private static final Long[] SMALL_VALUES = new Long[256]; static { - for(int i = -128; i < 128; i++) { + for (int i = -128; i < 128; i++) { SMALL_VALUES[i + 128] = new Long(i); } } diff --git a/luni/src/main/java/java/lang/Object.java b/luni/src/main/java/java/lang/Object.java index f6c01fe..7f4b490 100644 --- a/luni/src/main/java/java/lang/Object.java +++ b/luni/src/main/java/java/lang/Object.java @@ -219,6 +219,7 @@ public class Object { * * See <i>Effective Java</i> Item 7, "Avoid finalizers" for more. */ + @FindBugsSuppressWarnings("FI_EMPTY") protected void finalize() throws Throwable { } diff --git a/luni/src/main/java/java/lang/ProcessManager.java b/luni/src/main/java/java/lang/ProcessManager.java index cdcbb8a..f5afedb 100644 --- a/luni/src/main/java/java/lang/ProcessManager.java +++ b/luni/src/main/java/java/lang/ProcessManager.java @@ -31,31 +31,13 @@ import java.util.Map; import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; +import libcore.util.MutableInt; import static libcore.io.OsConstants.*; /** * Manages child processes. */ final class ProcessManager { - - /** - * constant communicated from native code indicating that a - * child died, but it was unable to determine the status - */ - private static final int WAIT_STATUS_UNKNOWN = -1; - - /** - * constant communicated from native code indicating that there - * are currently no children to wait for - */ - private static final int WAIT_STATUS_NO_CHILDREN = -2; - - /** - * constant communicated from native code indicating that a wait() - * call returned -1 and set an undocumented (and hence unexpected) errno - */ - private static final int WAIT_STATUS_STRANGE_ERRNO = -3; - /** * 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 @@ -71,13 +53,13 @@ final class ProcessManager { private ProcessManager() { // Spawn a thread to listen for signals from child processes. - Thread processThread = new Thread(ProcessManager.class.getName()) { + Thread reaperThread = new Thread(ProcessManager.class.getName()) { @Override public void run() { - watchChildren(ProcessManager.this); + watchChildren(); } }; - processThread.setDaemon(true); - processThread.start(); + reaperThread.setDaemon(true); + reaperThread.start(); } /** @@ -94,10 +76,40 @@ final class ProcessManager { } /** - * Listens for signals from processes and calls back to - * {@link #onExit(int,int)}. + * Loops indefinitely and calls ProcessManager.onExit() when children exit. */ - private static native void watchChildren(ProcessManager manager); + private void watchChildren() { + MutableInt status = new MutableInt(-1); + while (true) { + try { + // Wait for children in our process group. + int pid = Libcore.os.waitpid(0, status, 0); + + // Work out what onExit wants to hear. + int exitValue; + if (WIFEXITED(status.value)) { + exitValue = WEXITSTATUS(status.value); + } else if (WIFSIGNALED(status.value)) { + exitValue = WTERMSIG(status.value); + } else if (WIFSTOPPED(status.value)) { + exitValue = WSTOPSIG(status.value); + } else { + throw new AssertionError("unexpected status from waitpid: " + status.value); + } + + onExit(pid, exitValue); + } catch (ErrnoException errnoException) { + if (errnoException.errno == ECHILD) { + // Expected errno: there are no children to wait for. + // onExit will sleep until it is informed of another child coming to life. + waitForMoreChildren(); + continue; + } else { + throw errnoException; + } + } + } + } /** * Called by {@link #watchChildren()} when a child process exits. @@ -107,38 +119,10 @@ final class ProcessManager { */ private void onExit(int pid, int exitValue) { ProcessReference processReference = null; - synchronized (processReferences) { cleanUp(); - if (pid >= 0) { - processReference = processReferences.remove(pid); - } else if (exitValue == WAIT_STATUS_NO_CHILDREN) { - if (processReferences.isEmpty()) { - /* - * There are no eligible children; wait for one to be - * added. The wait() will return due to the - * notifyAll() call below. - */ - try { - processReferences.wait(); - } catch (InterruptedException ex) { - // This should never happen. - throw new AssertionError("unexpected interrupt"); - } - } else { - /* - * A new child was spawned just before we entered - * the synchronized block. We can just fall through - * without doing anything special and land back in - * the native wait(). - */ - } - } else { - // Something weird is happening; abort! - throw new AssertionError("unexpected wait() behavior"); - } + processReference = processReferences.remove(pid); } - if (processReference != null) { ProcessImpl process = processReference.get(); if (process != null) { @@ -147,6 +131,28 @@ final class ProcessManager { } } + private void waitForMoreChildren() { + synchronized (processReferences) { + if (processReferences.isEmpty()) { + // There are no eligible children; wait for one to be added. + // This wait will return because of the notifyAll call in exec. + try { + processReferences.wait(); + } catch (InterruptedException ex) { + // This should never happen. + throw new AssertionError("unexpected interrupt"); + } + } else { + /* + * A new child was spawned just before we entered + * the synchronized block. We can just fall through + * without doing anything special and land back in + * the native waitpid(). + */ + } + } + } + /** * Executes a native process. Fills in in, out, and err and returns the * new process ID upon success. diff --git a/luni/src/main/java/java/lang/Short.java b/luni/src/main/java/java/lang/Short.java index 02f7e65..1101805 100644 --- a/luni/src/main/java/java/lang/Short.java +++ b/luni/src/main/java/java/lang/Short.java @@ -23,6 +23,7 @@ package java.lang; * @see java.lang.Number * @since 1.1 */ +@FindBugsSuppressWarnings("DM_NUMBER_CTOR") public final class Short extends Number implements Comparable<Short> { private static final long serialVersionUID = 7515723908773894738L; @@ -156,8 +157,7 @@ public final class Short extends Number implements Comparable<Short> { */ @Override public boolean equals(Object object) { - return (object instanceof Short) - && (value == ((Short) object).value); + return (object instanceof Short) && (((Short) object).value == value); } @Override @@ -314,7 +314,7 @@ public final class Short extends Number implements Comparable<Short> { private static final Short[] SMALL_VALUES = new Short[256]; static { - for(int i = -128; i < 128; i++) { + for (int i = -128; i < 128; i++) { SMALL_VALUES[i + 128] = new Short((short) i); } } diff --git a/luni/src/main/java/java/lang/String.java b/luni/src/main/java/java/lang/String.java index 56b99f4..5991f7a 100644 --- a/luni/src/main/java/java/lang/String.java +++ b/luni/src/main/java/java/lang/String.java @@ -136,6 +136,7 @@ public final class String implements Serializable, Comparable<String>, CharSeque * Converts the byte array to a string using the system's * {@link java.nio.charset.Charset#defaultCharset default charset}. */ + @FindBugsSuppressWarnings("DM_DEFAULT_ENCODING") public String(byte[] data) { this(data, 0, data.length); } @@ -762,6 +763,7 @@ outer: * @return {@code true} if the specified string is equal to this string, * {@code false} otherwise. */ + @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ") public boolean equalsIgnoreCase(String string) { if (string == this) { return true; @@ -803,6 +805,7 @@ outer: */ @Deprecated public void getBytes(int start, int end, byte[] data, int index) { + // Note: last character not copied! if (start >= 0 && start <= end && end <= count) { end += offset; try { @@ -812,8 +815,9 @@ outer: } catch (ArrayIndexOutOfBoundsException ignored) { throw failedBoundsCheck(data.length, index, end - start); } + } else { + throw startEndAndLength(start, end); } - throw startEndAndLength(start, end); } /** @@ -890,7 +894,7 @@ outer: * index} */ public void getChars(int start, int end, char[] buffer, int index) { - // NOTE last character not copied! + // Note: last character not copied! if (start >= 0 && start <= end && end <= count) { System.arraycopy(value, start + offset, buffer, index, end - start); } else { @@ -2020,6 +2024,7 @@ outer: * where the needle is a constant string, may compute the values cache, md2 * and lastChar, and change the call to the following method. */ + @FindBugsSuppressWarnings("UPM_UNCALLED_PRIVATE_METHOD") @SuppressWarnings("unused") private static int indexOf(String haystackString, String needleString, int cache, int md2, char lastChar) { diff --git a/luni/src/main/java/org/apache/harmony/luni/util/FloatingPointParser.java b/luni/src/main/java/java/lang/StringToReal.java index e08e560..97f6d6b 100644 --- a/luni/src/main/java/org/apache/harmony/luni/util/FloatingPointParser.java +++ b/luni/src/main/java/java/lang/StringToReal.java @@ -15,14 +15,14 @@ * limitations under the License. */ -package org.apache.harmony.luni.util; - +package java.lang; /** * Used to parse a string and return either a single or double precision * floating point number. + * @hide */ -public final class FloatingPointParser { +final class StringToReal { private static final class StringExponentPair { String s; diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java index 203e5e3..c65ebbb 100644 --- a/luni/src/main/java/java/lang/System.java +++ b/luni/src/main/java/java/lang/System.java @@ -272,9 +272,13 @@ public final class System { p.put("java.boot.class.path", runtime.bootClassPath()); p.put("java.class.path", runtime.classPath()); - p.put("java.class.version", "46.0"); + // None of these four are meaningful on Android, but these keys are guaranteed + // to be present for System.getProperty. For java.class.version, we use the maximum + // class file version that dx currently supports. + p.put("java.class.version", "50.0"); p.put("java.compiler", ""); p.put("java.ext.dirs", ""); + p.put("java.version", "0"); p.put("java.home", getenv("JAVA_HOME", "/system")); @@ -287,7 +291,6 @@ public final class System { p.put("java.vendor", projectName); p.put("java.vendor.url", projectUrl); - p.put("java.version", "0"); p.put("java.vm.name", "Dalvik"); p.put("java.vm.specification.name", "Dalvik Virtual Machine Specification"); p.put("java.vm.specification.vendor", projectName); @@ -358,7 +361,7 @@ public final class System { * <tr><td>file.separator</td> <td>{@link java.io.File#separator}</td> <td>{@code /}</td></tr> * * <tr><td>java.class.path</td> <td>System class path</td> <td>{@code .}</td></tr> - * <tr><td>java.class.version</td> <td>Maximum supported .class file version</td> <td>{@code 46.0}</td></tr> + * <tr><td>java.class.version</td> <td>(Not useful on Android)</td> <td>{@code 50.0}</td></tr> * <tr><td>java.compiler</td> <td>(Not useful on Android)</td> <td>Empty</td></tr> * <tr><td>java.ext.dirs</td> <td>(Not useful on Android)</td> <td>Empty</td></tr> * <tr><td>java.home</td> <td>Location of the VM on the file system</td> <td>{@code /system}</td></tr> diff --git a/luni/src/main/java/java/lang/ThreadGroup.java b/luni/src/main/java/java/lang/ThreadGroup.java index d0e593f..e99e99f 100644 --- a/luni/src/main/java/java/lang/ThreadGroup.java +++ b/luni/src/main/java/java/lang/ThreadGroup.java @@ -75,7 +75,6 @@ public class ThreadGroup implements Thread.UncaughtExceptionHandler { * @param name the name * @see Thread#currentThread */ - public ThreadGroup(String name) { this(Thread.currentThread().getThreadGroup(), name); } diff --git a/luni/src/main/java/java/lang/UnsafeByteSequence.java b/luni/src/main/java/java/lang/UnsafeByteSequence.java index 66f904b..228bb01 100644 --- a/luni/src/main/java/java/lang/UnsafeByteSequence.java +++ b/luni/src/main/java/java/lang/UnsafeByteSequence.java @@ -67,6 +67,7 @@ public class UnsafeByteSequence { bytes[count++] = (byte) b; } + @FindBugsSuppressWarnings("EI_EXPOSE_REP") public byte[] toByteArray() { if (count == bytes.length) { return bytes; diff --git a/luni/src/main/java/java/lang/reflect/Constructor.java b/luni/src/main/java/java/lang/reflect/Constructor.java index ceb54c4..b03e28b 100644 --- a/luni/src/main/java/java/lang/reflect/Constructor.java +++ b/luni/src/main/java/java/lang/reflect/Constructor.java @@ -344,7 +344,7 @@ public final class Constructor<T> extends AccessibleObject implements GenericDec StringBuilder result = new StringBuilder(); result.append('('); - for(int i = 0; i < parameterTypes.length; i++) { + for (int i = 0; i < parameterTypes.length; i++) { result.append(getSignature(parameterTypes[i])); } result.append(")V"); diff --git a/luni/src/main/java/java/lang/reflect/Method.java b/luni/src/main/java/java/lang/reflect/Method.java index de1dcbd..8a9c0f1 100644 --- a/luni/src/main/java/java/lang/reflect/Method.java +++ b/luni/src/main/java/java/lang/reflect/Method.java @@ -571,7 +571,7 @@ public final class Method extends AccessibleObject implements GenericDeclaration StringBuilder result = new StringBuilder(); result.append('('); - for(int i = 0; i < parameterTypes.length; i++) { + for (int i = 0; i < parameterTypes.length; i++) { result.append(getSignature(parameterTypes[i])); } result.append(')'); diff --git a/luni/src/main/java/java/math/BitLevel.java b/luni/src/main/java/java/math/BitLevel.java index 607358f..91f7a9b 100644 --- a/luni/src/main/java/java/math/BitLevel.java +++ b/luni/src/main/java/java/math/BitLevel.java @@ -113,7 +113,7 @@ class BitLevel { static void shiftLeftOneBit(int[] result, int[] source, int srcLen) { int carry = 0; - for(int i = 0; i < srcLen; i++) { + for (int i = 0; i < srcLen; i++) { int val = source[i]; result[i] = (val << 1) | carry; carry = val >>> 31; diff --git a/luni/src/main/java/java/math/Conversion.java b/luni/src/main/java/java/math/Conversion.java index 731f972..585fff4 100644 --- a/luni/src/main/java/java/math/Conversion.java +++ b/luni/src/main/java/java/math/Conversion.java @@ -350,7 +350,7 @@ class Conversion { if (exponent >= 0) { // special case 1 int insertPoint = currentChar + (int) exponent ; - for(int j=resLengthInChars-1; j>=insertPoint; j--) { + for (int j=resLengthInChars-1; j>=insertPoint; j--) { result[j+1] = result[j]; } result[++insertPoint]='.'; diff --git a/luni/src/main/java/java/math/Logical.java b/luni/src/main/java/java/math/Logical.java index 229a5e8..9de0924 100644 --- a/luni/src/main/java/java/math/Logical.java +++ b/luni/src/main/java/java/math/Logical.java @@ -207,7 +207,7 @@ class Logical { resDigits[i] = longer.digits[i] | shorter.digits[i]; } // shorter has only the remaining virtual sign bits - for( ; i < longer.numberLength; i++){ + for ( ; i < longer.numberLength; i++){ resDigits[i] = longer.digits[i]; } @@ -533,7 +533,7 @@ class Logical { i = iPos; resDigits[i] = -positive.digits[i]; limit = Math.min(positive.numberLength, iNeg); - for(i++; i < limit; i++ ) { + for (i++; i < limit; i++ ) { resDigits[i] = ~positive.digits[i]; } if (i != positive.numberLength) { @@ -558,7 +558,7 @@ class Logical { // resDigits[i] = ~(~negative.digits[i] | positive.digits[i] ); resDigits[i] = negative.digits[i] & ~positive.digits[i]; } - for( ; i < negative.numberLength; i++) { + for ( ; i < negative.numberLength; i++) { resDigits[i] = negative.digits[i]; } @@ -611,7 +611,7 @@ class Logical { for ( ; i < shorter.numberLength; i++) { resDigits[i] = longer.digits[i] ^ shorter.digits[i]; } - for( ; i < longer.numberLength; i++ ){ + for ( ; i < longer.numberLength; i++ ){ resDigits[i] = longer.digits[i]; } diff --git a/luni/src/main/java/java/math/MathContext.java b/luni/src/main/java/java/math/MathContext.java index 24ccb29..6f3f1ed 100644 --- a/luni/src/main/java/java/math/MathContext.java +++ b/luni/src/main/java/java/math/MathContext.java @@ -30,21 +30,21 @@ public final class MathContext implements Serializable { private static final long serialVersionUID = 5579720004786848255L; /** - * A {@code MathContext} which corresponds to the IEEE 754r quadruple + * A {@code MathContext} which corresponds to the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> quadruple * decimal precision format: 34 digit precision and * {@link RoundingMode#HALF_EVEN} rounding. */ public static final MathContext DECIMAL128 = new MathContext(34, RoundingMode.HALF_EVEN); /** - * A {@code MathContext} which corresponds to the IEEE 754r single decimal + * A {@code MathContext} which corresponds to the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> single decimal * precision format: 7 digit precision and {@link RoundingMode#HALF_EVEN} * rounding. */ public static final MathContext DECIMAL32 = new MathContext(7, RoundingMode.HALF_EVEN); /** - * A {@code MathContext} which corresponds to the IEEE 754r double decimal + * A {@code MathContext} which corresponds to the <a href="http://en.wikipedia.org/wiki/IEEE_754-1985">IEEE 754</a> double decimal * precision format: 16 digit precision and {@link RoundingMode#HALF_EVEN} * rounding. */ diff --git a/luni/src/main/java/java/net/BindException.java b/luni/src/main/java/java/net/BindException.java index 498ead6..4f035cc 100644 --- a/luni/src/main/java/java/net/BindException.java +++ b/luni/src/main/java/java/net/BindException.java @@ -26,15 +26,23 @@ public class BindException extends SocketException { private static final long serialVersionUID = -5945005768251722951L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public BindException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public BindException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with the given detail message and cause. + * @hide + */ + public BindException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/ConnectException.java b/luni/src/main/java/java/net/ConnectException.java index 554316f..45f60be 100644 --- a/luni/src/main/java/java/net/ConnectException.java +++ b/luni/src/main/java/java/net/ConnectException.java @@ -26,18 +26,23 @@ public class ConnectException extends SocketException { private static final long serialVersionUID = 3831404271622369215L; /** - * This implementation does nothing. + * Constructs a new instance. */ public ConnectException() { } /** - * This implementation does nothing. - * - * @param detailMessage - * detail message of the exception. + * Constructs a new instance with the given detail message. */ public ConnectException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with the given detail message and cause. + * @hide + */ + public ConnectException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java index 47ac0ff..70e3db9 100644 --- a/luni/src/main/java/java/net/DatagramSocket.java +++ b/luni/src/main/java/java/net/DatagramSocket.java @@ -161,8 +161,7 @@ public class DatagramSocket { isConnected = false; } - synchronized void createSocket(int aPort, InetAddress addr) - throws SocketException { + synchronized void createSocket(int aPort, InetAddress addr) throws SocketException { impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl(); impl.create(); diff --git a/luni/src/main/java/java/net/DatagramSocketImpl.java b/luni/src/main/java/java/net/DatagramSocketImpl.java index 4485b18..6e706c3 100644 --- a/luni/src/main/java/java/net/DatagramSocketImpl.java +++ b/luni/src/main/java/java/net/DatagramSocketImpl.java @@ -19,7 +19,7 @@ package java.net; import java.io.FileDescriptor; import java.io.IOException; -import libcore.io.IoUtils; +import libcore.io.IoBridge; /** * The abstract superclass for datagram and multicast socket implementations. @@ -85,7 +85,7 @@ public abstract class DatagramSocketImpl implements SocketOptions { * Returns the local address to which the socket is bound. */ InetAddress getLocalAddress() { - return IoUtils.getSocketLocalAddress(fd); + return IoBridge.getSocketLocalAddress(fd); } /** diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java index 8376220..a54e11a 100644 --- a/luni/src/main/java/java/net/HttpURLConnection.java +++ b/luni/src/main/java/java/net/HttpURLConnection.java @@ -251,7 +251,7 @@ public abstract class HttpURLConnection extends URLConnection { /** * The subset of HTTP methods that the user may select via {@link - * libcore.net.http.HttpURLConnectionImpl#setRequestMethod(String)}. + * #setRequestMethod(String)}. */ private static final String[] PERMITTED_USER_METHODS = { HttpEngine.OPTIONS, @@ -472,7 +472,11 @@ public abstract class HttpURLConnection extends URLConnection { public static final int HTTP_SERVER_ERROR = 500; /** - * Numeric status code, 305: Use proxy + * Numeric status code, 305: Use proxy. + * + * <p>Like Firefox and Chrome, this class doesn't honor this response code. + * Other implementations respond to this status code by retrying the request + * using the HTTP proxy named by the response's Location header field. */ public static final int HTTP_USE_PROXY = 305; diff --git a/luni/src/main/java/java/net/Inet4Address.java b/luni/src/main/java/java/net/Inet4Address.java index a12eacd..8f8f4f8 100644 --- a/luni/src/main/java/java/net/Inet4Address.java +++ b/luni/src/main/java/java/net/Inet4Address.java @@ -20,6 +20,7 @@ package java.net; import java.io.ObjectStreamException; import java.nio.ByteOrder; import libcore.io.Memory; +import static libcore.io.OsConstants.*; /** * An IPv4 address. See {@link InetAddress}. @@ -28,8 +29,6 @@ public final class Inet4Address extends InetAddress { private static final long serialVersionUID = 3286316764910316507L; - private static final int AF_INET = 2; - /** * @hide */ diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java index c253a50..71c8e4a 100644 --- a/luni/src/main/java/java/net/Inet6Address.java +++ b/luni/src/main/java/java/net/Inet6Address.java @@ -23,6 +23,7 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.util.Arrays; import java.util.Enumeration; +import static libcore.io.OsConstants.*; /** * An IPv6 address. See {@link InetAddress}. @@ -31,8 +32,6 @@ public final class Inet6Address extends InetAddress { private static final long serialVersionUID = 6880410070516793377L; - private static final int AF_INET6 = 10; - /** * @hide */ @@ -46,11 +45,11 @@ public final class Inet6Address extends InetAddress { 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; + private boolean scope_id_set; + private int scope_id; - boolean scope_ifname_set; - String ifname; + private boolean scope_ifname_set; + private String ifname; /** * Constructs an {@code InetAddress} representing the {@code address} and @@ -269,7 +268,7 @@ public final class Inet6Address extends InetAddress { */ public NetworkInterface getScopedInterface() { try { - return scope_ifname_set ? NetworkInterface.getByName(ifname) : null; + return (scope_ifname_set && ifname != null) ? NetworkInterface.getByName(ifname) : null; } catch (SocketException ex) { return null; } diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java index 463bff4..a507f82 100644 --- a/luni/src/main/java/java/net/InetAddress.java +++ b/luni/src/main/java/java/net/InetAddress.java @@ -31,12 +31,12 @@ import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; +import libcore.io.ErrnoException; import libcore.io.GaiException; import libcore.io.Libcore; -import libcore.io.IoUtils; +import libcore.io.IoBridge; import libcore.io.Memory; import libcore.io.StructAddrinfo; -import org.apache.harmony.luni.platform.Platform; import static libcore.io.OsConstants.*; /** @@ -143,8 +143,6 @@ public class InetAddress implements Serializable { /** Our Java-side DNS cache. */ private static final AddressCache addressCache = new AddressCache(); - private static final String ERRMSG_CONNECTION_REFUSED = "Connection refused"; - private static final long serialVersionUID = 3286316764910316507L; private transient Object waitReachable = new Object(); @@ -160,16 +158,15 @@ public class InetAddress implements Serializable { String hostName; /** + * Used by the DatagramSocket.disconnect implementation. + * @hide internal use only + */ + public static final InetAddress UNSPECIFIED = new InetAddress(AF_UNSPEC, null, null); + + /** * Constructs an {@code InetAddress}. * - * Note: this constructor should not be used. Creating an InetAddress - * without specifying whether it's an IPv4 or IPv6 address does not make - * sense, because subsequent code cannot know which of of the subclasses' - * methods need to be called to implement a given InetAddress method. The - * proper way to create an InetAddress is to call new Inet4Address or - * Inet6Address or to use one of the static methods that return - * InetAddresses (e.g., getByAddress). That is why the API does not have - * public constructors for any of these classes. + * Note: this constructor is for subclasses only. */ InetAddress(int family, byte[] ipaddress, String hostName) { this.family = family; @@ -810,53 +807,39 @@ public class InetAddress implements Serializable { } private boolean isReachableByTCP(InetAddress destination, InetAddress source, int timeout) throws IOException { - FileDescriptor fd = IoUtils.socket(true); + FileDescriptor fd = IoBridge.socket(true); boolean reached = false; try { if (source != null) { - Platform.NETWORK.bind(fd, source, 0); + IoBridge.bind(fd, source, 0); } - IoUtils.connect(fd, destination, 7, timeout); + IoBridge.connect(fd, destination, 7, timeout); reached = true; } catch (IOException e) { - if (ERRMSG_CONNECTION_REFUSED.equals(e.getMessage())) { - // Connection refused means the IP is reachable - reached = true; + if (e.getCause() instanceof ErrnoException) { + // "Connection refused" means the IP address was reachable. + reached = (((ErrnoException) e.getCause()).errno == ECONNREFUSED); } } - Platform.NETWORK.close(fd); + IoBridge.closeSocket(fd); return reached; } /** - * Equivalent to {@code getByAddress(null, ipAddress, 0)}. Handy for IPv4 addresses with + * Equivalent to {@code getByAddress(null, ipAddress)}. Handy for 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.) */ public static InetAddress getByAddress(byte[] ipAddress) throws UnknownHostException { - return getByAddressInternal(null, ipAddress, 0); - } - - /** - * Equivalent to {@code getByAddress(hostName, ipAddress, 0)}. Handy for IPv4 addresses - * with an 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.) - */ - public static InetAddress getByAddress(String hostName, byte[] ipAddress) throws UnknownHostException { - return getByAddressInternal(hostName, ipAddress, 0); + return getByAddress(null, ipAddress, 0); } /** * Returns an {@code InetAddress} corresponding to the given network-order * bytes {@code ipAddress} and {@code scopeId}. * - * <p>For an IPv4 address, the byte array must be of length 4, and the scopeId is ignored. + * <p>For an IPv4 address, the byte array must be of length 4. * For IPv6, the byte array must be of length 16. Any other length will cause an {@code * UnknownHostException}. * @@ -868,8 +851,11 @@ public class InetAddress implements Serializable { * * @throws UnknownHostException if {@code ipAddress} is null or the wrong length. */ - private static InetAddress getByAddressInternal(String hostName, byte[] ipAddress, int scopeId) - throws UnknownHostException { + public static InetAddress getByAddress(String hostName, byte[] ipAddress) throws UnknownHostException { + return getByAddress(hostName, ipAddress, 0); + } + + private static InetAddress getByAddress(String hostName, byte[] ipAddress, int scopeId) throws UnknownHostException { if (ipAddress == null) { throw new UnknownHostException("ipAddress == null"); } @@ -913,7 +899,7 @@ public class InetAddress implements Serializable { private static byte[] ipv4MappedToIPv4(byte[] mappedAddress) { byte[] ipv4Address = new byte[4]; - for(int i = 0; i < 4; i++) { + for (int i = 0; i < 4; i++) { ipv4Address[i] = mappedAddress[12 + i]; } return ipv4Address; diff --git a/luni/src/main/java/java/net/InetSocketAddress.java b/luni/src/main/java/java/net/InetSocketAddress.java index 7b33a44..01b5301 100644 --- a/luni/src/main/java/java/net/InetSocketAddress.java +++ b/luni/src/main/java/java/net/InetSocketAddress.java @@ -34,6 +34,16 @@ public class InetSocketAddress extends SocketAddress { private final int port; /** + * @hide internal use only + */ + public InetSocketAddress() { + // These will be filled in the native implementation of recvfrom. + this.addr = null; + this.hostname = null; + this.port = -1; + } + + /** * Creates a socket endpoint with the given port number {@code port} and * no specified address. The range for valid port numbers is between 0 and * 65535 inclusive. diff --git a/luni/src/main/java/java/net/MalformedURLException.java b/luni/src/main/java/java/net/MalformedURLException.java index 77965e8..c6b82c4 100644 --- a/luni/src/main/java/java/net/MalformedURLException.java +++ b/luni/src/main/java/java/net/MalformedURLException.java @@ -30,15 +30,23 @@ public class MalformedURLException extends IOException { private static final long serialVersionUID = -182787522200415866L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public MalformedURLException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public MalformedURLException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public MalformedURLException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java index 9aa20a0..c6d9af4 100644 --- a/luni/src/main/java/java/net/NetworkInterface.java +++ b/luni/src/main/java/java/net/NetworkInterface.java @@ -184,6 +184,7 @@ public final class NetworkInterface extends Object { } } + @FindBugsSuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") 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()) { @@ -261,6 +262,7 @@ public final class NetworkInterface extends Object { return Collections.enumeration(getNetworkInterfacesList()); } + @FindBugsSuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") private static List<NetworkInterface> getNetworkInterfacesList() throws SocketException { String[] interfaceNames = new File("/sys/class/net").list(); NetworkInterface[] interfaces = new NetworkInterface[interfaceNames.length]; diff --git a/luni/src/main/java/java/net/NoRouteToHostException.java b/luni/src/main/java/java/net/NoRouteToHostException.java index 3d46821..8d6d941 100644 --- a/luni/src/main/java/java/net/NoRouteToHostException.java +++ b/luni/src/main/java/java/net/NoRouteToHostException.java @@ -27,15 +27,23 @@ public class NoRouteToHostException extends SocketException { private static final long serialVersionUID = -1897550894873493790L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public NoRouteToHostException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public NoRouteToHostException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public NoRouteToHostException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java index 0eb413f..f27db94 100644 --- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java +++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java @@ -28,10 +28,11 @@ import java.net.NetworkInterface; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; -import libcore.io.IoUtils; +import libcore.io.IoBridge; +import libcore.io.Libcore; import libcore.io.StructGroupReq; import libcore.util.EmptyArray; -import org.apache.harmony.luni.platform.Platform; +import static libcore.io.OsConstants.*; /** * @hide used in java.nio. @@ -62,15 +63,13 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { fd = new FileDescriptor(); } - @Override - public void bind(int port, InetAddress addr) throws SocketException { - Platform.NETWORK.bind(fd, addr, port); + @Override public void bind(int port, InetAddress address) throws SocketException { + IoBridge.bind(fd, address, port); if (port != 0) { localPort = port; } else { - localPort = IoUtils.getSocketLocalPort(fd); + localPort = IoBridge.getSocketLocalPort(fd); } - try { setOption(SocketOptions.SO_BROADCAST, Boolean.TRUE); } catch (IOException ignored) { @@ -81,14 +80,14 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { public synchronized void close() { guard.close(); try { - Platform.NETWORK.close(fd); + IoBridge.closeSocket(fd); } catch (IOException ignored) { } } @Override public void create() throws SocketException { - this.fd = IoUtils.socket(false); + this.fd = IoBridge.socket(false); } @Override protected void finalize() throws Throwable { @@ -103,12 +102,12 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { } @Override public Object getOption(int option) throws SocketException { - return IoUtils.getSocketOption(fd, option); + return IoBridge.getSocketOption(fd, option); } @Override public int getTimeToLive() throws IOException { - return (Integer) getOption(IoUtils.JAVA_IP_MULTICAST_TTL); + return (Integer) getOption(IoBridge.JAVA_IP_MULTICAST_TTL); } @Override @@ -123,27 +122,27 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override public void join(InetAddress addr) throws IOException { - setOption(IoUtils.JAVA_MCAST_JOIN_GROUP, makeGroupReq(addr, null)); + setOption(IoBridge.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.JAVA_MCAST_JOIN_GROUP, makeGroupReq(groupAddr, netInterface)); + setOption(IoBridge.JAVA_MCAST_JOIN_GROUP, makeGroupReq(groupAddr, netInterface)); } } @Override public void leave(InetAddress addr) throws IOException { - setOption(IoUtils.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(addr, null)); + setOption(IoBridge.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.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(groupAddr, netInterface)); + setOption(IoBridge.JAVA_MCAST_LEAVE_GROUP, makeGroupReq(groupAddr, netInterface)); } } @@ -152,14 +151,13 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { // We don't actually want the data: we just want the DatagramPacket's filled-in address. DatagramPacket packet = new DatagramPacket(EmptyArray.BYTE, 0); int result = peekData(packet); - // TODO: maybe recv should do this? + // Note: evil side-effect on InetAddress! This method should have returned InetSocketAddress! sender.ipaddress = packet.getAddress().getAddress(); return result; } - private void doRecv(DatagramPacket pack, boolean peek) throws IOException { - Platform.NETWORK.recv(fd, pack, pack.getData(), pack.getOffset(), pack.getLength(), peek, - isNativeConnected); + private void doRecv(DatagramPacket pack, int flags) throws IOException { + IoBridge.recvfrom(false, fd, pack.getData(), pack.getOffset(), pack.getLength(), flags, pack, isNativeConnected); if (isNativeConnected) { updatePacketRecvAddress(pack); } @@ -167,12 +165,12 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override public void receive(DatagramPacket pack) throws IOException { - doRecv(pack, false); + doRecv(pack, 0); } @Override public int peekData(DatagramPacket pack) throws IOException { - doRecv(pack, true); + doRecv(pack, MSG_PEEK); return pack.getPort(); } @@ -180,17 +178,16 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { public void send(DatagramPacket packet) throws IOException { int port = isNativeConnected ? 0 : packet.getPort(); InetAddress address = isNativeConnected ? null : packet.getAddress(); - Platform.NETWORK.send(fd, packet.getData(), packet.getOffset(), packet.getLength(), - port, address); + IoBridge.sendto(fd, packet.getData(), packet.getOffset(), packet.getLength(), 0, address, port); } public void setOption(int option, Object value) throws SocketException { - IoUtils.setSocketOption(fd, option, value); + IoBridge.setSocketOption(fd, option, value); } @Override public void setTimeToLive(int ttl) throws IOException { - setOption(IoUtils.JAVA_IP_MULTICAST_TTL, Integer.valueOf(ttl)); + setOption(IoBridge.JAVA_IP_MULTICAST_TTL, Integer.valueOf(ttl)); } @Override @@ -200,7 +197,7 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override public void connect(InetAddress inetAddr, int port) throws SocketException { - IoUtils.connect(fd, inetAddr, port); // Throws on failure. + IoBridge.connect(fd, inetAddr, port); // Throws on failure. try { connectedAddress = InetAddress.getByAddress(inetAddr.getAddress()); } catch (UnknownHostException e) { @@ -214,10 +211,7 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override public void disconnect() { - try { - Platform.NETWORK.disconnectDatagram(fd); - } catch (Exception ignored) { - } + Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0); connectedPort = -1; connectedAddress = null; isNativeConnected = false; diff --git a/luni/src/main/java/java/net/PlainServerSocketImpl.java b/luni/src/main/java/java/net/PlainServerSocketImpl.java index cb45010..4fc1a9d 100644 --- a/luni/src/main/java/java/net/PlainServerSocketImpl.java +++ b/luni/src/main/java/java/net/PlainServerSocketImpl.java @@ -26,8 +26,8 @@ import java.net.SocketOptions; * @hide used in java.nio. */ public class PlainServerSocketImpl extends PlainSocketImpl { - - public PlainServerSocketImpl() {} + public PlainServerSocketImpl() { + } public PlainServerSocketImpl(FileDescriptor fd) { super(fd); diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java index ccdbb5b..20dfcc2 100644 --- a/luni/src/main/java/java/net/PlainSocketImpl.java +++ b/luni/src/main/java/java/net/PlainSocketImpl.java @@ -34,11 +34,10 @@ import java.net.UnknownHostException; import java.nio.ByteOrder; import java.util.Arrays; import libcore.io.ErrnoException; -import libcore.io.IoUtils; +import libcore.io.IoBridge; import libcore.io.Libcore; import libcore.io.Memory; import libcore.io.Streams; -import org.apache.harmony.luni.platform.Platform; import static libcore.io.OsConstants.*; /** @@ -93,7 +92,28 @@ public class PlainSocketImpl extends SocketImpl { ((PlainSocketImpl) newImpl).socksAccept(); return; } - Platform.NETWORK.accept(fd, newImpl, newImpl.getFileDescriptor()); + + try { + InetSocketAddress peerAddress = new InetSocketAddress(); + FileDescriptor clientFd = Libcore.os.accept(fd, peerAddress); + + // TODO: we can't just set newImpl.fd to clientFd because a nio SocketChannel may + // be sharing the FileDescriptor. http://b//4452981. + newImpl.fd.setInt$(clientFd.getInt$()); + + newImpl.address = peerAddress.getAddress(); + newImpl.port = peerAddress.getPort(); + } catch (ErrnoException errnoException) { + if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) { + throw new SocketTimeoutException(errnoException); + } + throw errnoException.rethrowAsSocketException(); + } + + // Reset the client's inherited read timeout to the Java-specified default of 0. + newImpl.setOption(SocketOptions.SO_TIMEOUT, Integer.valueOf(0)); + + newImpl.localport = IoBridge.getSocketLocalPort(newImpl.fd); } private boolean usingSocks() { @@ -123,24 +143,23 @@ public class PlainSocketImpl extends SocketImpl { if (shutdownInput) { return 0; } - return IoUtils.available(fd); + return IoBridge.available(fd); } - @Override - protected void bind(InetAddress address, int port) throws IOException { - Platform.NETWORK.bind(fd, address, port); + @Override protected void bind(InetAddress address, int port) throws IOException { + IoBridge.bind(fd, address, port); this.address = address; if (port != 0) { this.localport = port; } else { - this.localport = IoUtils.getSocketLocalPort(fd); + this.localport = IoBridge.getSocketLocalPort(fd); } } @Override protected synchronized void close() throws IOException { guard.close(); - Platform.NETWORK.close(fd); + IoBridge.closeSocket(fd); } @Override @@ -167,14 +186,10 @@ public class PlainSocketImpl extends SocketImpl { */ private void connect(InetAddress anAddr, int aPort, int timeout) throws IOException { InetAddress normalAddr = anAddr.isAnyLocalAddress() ? InetAddress.getLocalHost() : anAddr; - try { - if (streaming && usingSocks()) { - socksConnect(anAddr, aPort, 0); - } else { - IoUtils.connect(fd, normalAddr, aPort, timeout); - } - } catch (ConnectException e) { - throw new ConnectException(anAddr + " (port " + aPort + "): " + e.getMessage()); + if (streaming && usingSocks()) { + socksConnect(anAddr, aPort, 0); + } else { + IoBridge.connect(fd, normalAddr, aPort, timeout); } super.address = normalAddr; super.port = aPort; @@ -183,7 +198,7 @@ public class PlainSocketImpl extends SocketImpl { @Override protected void create(boolean streaming) throws IOException { this.streaming = streaming; - this.fd = IoUtils.socket(streaming); + this.fd = IoBridge.socket(streaming); } @Override protected void finalize() throws Throwable { @@ -227,7 +242,7 @@ public class PlainSocketImpl extends SocketImpl { } @Override public Object getOption(int option) throws SocketException { - return IoUtils.getSocketOption(fd, option); + return IoBridge.getSocketOption(fd, option); } @Override protected synchronized OutputStream getOutputStream() throws IOException { @@ -271,7 +286,7 @@ public class PlainSocketImpl extends SocketImpl { @Override public void setOption(int option, Object value) throws SocketException { - IoUtils.setSocketOption(fd, option, value); + IoBridge.setSocketOption(fd, option, value); } /** @@ -306,7 +321,7 @@ public class PlainSocketImpl extends SocketImpl { */ private void socksConnect(InetAddress applicationServerAddress, int applicationServerPort, int timeout) throws IOException { try { - IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout); + IoBridge.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout); } catch (Exception e) { throw new SocketException("SOCKS connection failed", e); } @@ -371,7 +386,7 @@ public class PlainSocketImpl extends SocketImpl { */ private void socksBind() throws IOException { try { - IoUtils.connect(fd, socksGetServerAddress(), socksGetServerPort()); + IoBridge.connect(fd, socksGetServerAddress(), socksGetServerPort()); } catch (Exception e) { throw new IOException("Unable to connect to SOCKS server", e); } @@ -451,7 +466,12 @@ public class PlainSocketImpl extends SocketImpl { @Override protected void sendUrgentData(int value) throws IOException { - Platform.NETWORK.sendUrgentData(fd, (byte) value); + try { + byte[] buffer = new byte[] { (byte) value }; + Libcore.os.sendto(fd, buffer, 0, 1, MSG_OOB, null, 0); + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsSocketException(); + } } /** @@ -465,16 +485,16 @@ public class PlainSocketImpl extends SocketImpl { if (shutdownInput) { return -1; } - int read = Platform.NETWORK.read(fd, buffer, offset, byteCount); + int readCount = IoBridge.recvfrom(true, fd, buffer, offset, byteCount, 0, null, false); // Return of zero bytes for a blocking socket means a timeout occurred - if (read == 0) { + if (readCount == 0) { throw new SocketTimeoutException(); } // Return of -1 indicates the peer was closed - if (read == -1) { + if (readCount == -1) { shutdownInput = true; } - return read; + return readCount; } /** @@ -484,7 +504,7 @@ public class PlainSocketImpl extends SocketImpl { Arrays.checkOffsetAndCount(buffer.length, offset, byteCount); if (streaming) { while (byteCount > 0) { - int bytesWritten = Platform.NETWORK.write(fd, buffer, offset, byteCount); + int bytesWritten = IoBridge.sendto(fd, buffer, offset, byteCount, 0, null, 0); byteCount -= bytesWritten; offset += bytesWritten; } @@ -492,7 +512,7 @@ public class PlainSocketImpl extends SocketImpl { // Unlike writes to a streaming socket, writes to a datagram // socket are all-or-nothing, so we don't need a loop here. // http://code.google.com/p/android/issues/detail?id=15304 - Platform.NETWORK.send(fd, buffer, offset, byteCount, port, address); + IoBridge.sendto(fd, buffer, offset, byteCount, 0, address, port); } } } diff --git a/luni/src/main/java/java/net/PortUnreachableException.java b/luni/src/main/java/java/net/PortUnreachableException.java index b8e8fa9..0cb4114 100644 --- a/luni/src/main/java/java/net/PortUnreachableException.java +++ b/luni/src/main/java/java/net/PortUnreachableException.java @@ -26,15 +26,23 @@ public class PortUnreachableException extends SocketException { private static final long serialVersionUID = 8462541992376507323L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public PortUnreachableException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public PortUnreachableException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public PortUnreachableException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/ProtocolException.java b/luni/src/main/java/java/net/ProtocolException.java index 979f7d9..e0bec50 100644 --- a/luni/src/main/java/java/net/ProtocolException.java +++ b/luni/src/main/java/java/net/ProtocolException.java @@ -27,15 +27,23 @@ public class ProtocolException extends java.io.IOException { private static final long serialVersionUID = -6098449442062388080L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public ProtocolException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public ProtocolException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public ProtocolException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/ServerSocket.java b/luni/src/main/java/java/net/ServerSocket.java index 663cddf..ba8d626 100644 --- a/luni/src/main/java/java/net/ServerSocket.java +++ b/luni/src/main/java/java/net/ServerSocket.java @@ -27,99 +27,79 @@ import java.nio.channels.ServerSocketChannel; * implemented by an internal {@code SocketImpl} instance. */ public class ServerSocket { + /** + * The RI specifies that where the caller doesn't give an explicit backlog, + * the default is 50. The OS disagrees, so we need to explicitly call listen(2). + */ + private static final int DEFAULT_BACKLOG = 50; - SocketImpl impl; + private final SocketImpl impl; - static SocketImplFactory factory; + /** + * @hide internal use only + */ + public SocketImpl getImpl$() { + return impl; + } - private volatile boolean isCreated; + static SocketImplFactory factory; private boolean isBound; private boolean isClosed; /** - * Constructs a new {@code ServerSocket} instance which is not bound to any - * port. The default number of pending connections may be backlogged. + * Constructs a new unbound {@code ServerSocket}. * - * @throws IOException - * if an error occurs while creating the server socket. + * @throws IOException if an error occurs while creating the socket. */ public ServerSocket() throws IOException { - impl = factory != null ? factory.createSocketImpl() + this.impl = factory != null ? factory.createSocketImpl() : new PlainServerSocketImpl(); + impl.create(true); } /** - * Unspecified constructor needed by ServerSocketChannelImpl.ServerSocketAdapter. + * Constructs a new {@code ServerSocket} instance bound to the given {@code port}. + * The backlog is set to 50. If {@code port == 0}, a port will be assigned by the OS. * - * @hide + * @throws IOException if an error occurs while creating the socket. */ - protected ServerSocket(SocketImpl impl) { - this.impl = impl; + public ServerSocket(int port) throws IOException { + this(port, DEFAULT_BACKLOG, Inet4Address.ANY); } /** - * Constructs a new {@code ServerSocket} instance bound to the nominated - * port on the localhost. The default number of pending connections may be - * backlogged. If {@code aport} is 0 a free port is assigned to the socket. + * Constructs a new {@code ServerSocket} instance bound to the given {@code port}. + * The backlog is set to {@code backlog}. + * If {@code port == 0}, a port will be assigned by the OS. * - * @param aport - * the port number to listen for connection requests on. - * @throws IOException - * if an error occurs while creating the server socket. + * @throws IOException if an error occurs while creating the socket. */ - public ServerSocket(int aport) throws IOException { - this(aport, defaultBacklog(), Inet4Address.ANY); + public ServerSocket(int port, int backlog) throws IOException { + this(port, backlog, Inet4Address.ANY); } /** - * Constructs a new {@code ServerSocket} instance bound to the nominated - * port on the localhost. The number of pending connections that may be - * backlogged is specified by {@code backlog}. If {@code aport} is 0 a free - * port is assigned to the socket. + * Constructs a new {@code ServerSocket} instance bound to the given {@code localAddress} + * and {@code port}. The backlog is set to {@code backlog}. + * If {@code localAddress == null}, the ANY address is used. + * If {@code port == 0}, a port will be assigned by the OS. * - * @param aport - * the port number to listen for connection requests on. - * @param backlog - * the number of pending connection requests, before requests - * will be rejected. - * @throws IOException - * if an error occurs while creating the server socket. + * @throws IOException if an error occurs while creating the socket. */ - public ServerSocket(int aport, int backlog) throws IOException { - this(aport, backlog, Inet4Address.ANY); - } - - /** - * Constructs a new {@code ServerSocket} instance bound to the nominated - * local host address and port. The number of pending connections that may - * be backlogged is specified by {@code backlog}. If {@code aport} is 0 a - * free port is assigned to the socket. - * - * @param aport - * the port number to listen for connection requests on. - * @param localAddr - * the local machine address to bind on. - * @param backlog - * the number of pending connection requests, before requests - * will be rejected. - * @throws IOException - * if an error occurs while creating the server socket. - */ - public ServerSocket(int aport, int backlog, InetAddress localAddr) throws IOException { - checkListen(aport); - impl = factory != null ? factory.createSocketImpl() + public ServerSocket(int port, int backlog, InetAddress localAddress) throws IOException { + checkListen(port); + this.impl = factory != null ? factory.createSocketImpl() : new PlainServerSocketImpl(); - InetAddress addr = localAddr == null ? Inet4Address.ANY : localAddr; + InetAddress addr = (localAddress == null) ? Inet4Address.ANY : localAddress; synchronized (this) { impl.create(true); - isCreated = true; try { - impl.bind(addr, aport); + impl.bind(addr, port); isBound = true; - impl.listen(backlog > 0 ? backlog : defaultBacklog()); + impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG); } catch (IOException e) { close(); throw e; @@ -137,7 +117,7 @@ public class ServerSocket { * if an error occurs while accepting a new connection. */ public Socket accept() throws IOException { - checkClosedAndCreate(false); + checkOpen(); if (!isBound()) { throw new SocketException("Socket is not bound"); } @@ -171,17 +151,6 @@ public class ServerSocket { } /** - * Returns the default number of pending connections on a server socket. If - * the backlog value maximum is reached, any subsequent incoming request is - * rejected. - * - * @return int the default number of pending connection requests - */ - static int defaultBacklog() { - return 50; - } - - /** * Gets the local IP address of this server socket or {@code null} if the * socket is unbound. This is useful for multihomed hosts. * @@ -214,20 +183,7 @@ public class ServerSocket { * if the option cannot be retrieved. */ public synchronized int getSoTimeout() throws IOException { - if (!isCreated) { - synchronized (this) { - if (!isCreated) { - try { - impl.create(true); - } catch (SocketException e) { - throw e; - } catch (IOException e) { - throw new SocketException(e.toString()); - } - isCreated = true; - } - } - } + checkOpen(); return ((Integer) impl.getOption(SocketOptions.SO_TIMEOUT)).intValue(); } @@ -259,8 +215,7 @@ public class ServerSocket { * @throws IOException * if the factory could not be set or is already set. */ - public static synchronized void setSocketFactory(SocketImplFactory aFactory) - throws IOException { + public static synchronized void setSocketFactory(SocketImplFactory aFactory) throws IOException { if (factory != null) { throw new SocketException("Factory already set"); } @@ -279,7 +234,7 @@ public class ServerSocket { * if an error occurs while setting the option. */ public synchronized void setSoTimeout(int timeout) throws SocketException { - checkClosedAndCreate(true); + checkOpen(); if (timeout < 0) { throw new IllegalArgumentException("timeout < 0"); } @@ -323,7 +278,7 @@ public class ServerSocket { * binding. */ public void bind(SocketAddress localAddr) throws IOException { - bind(localAddr, defaultBacklog()); + bind(localAddr, DEFAULT_BACKLOG); } /** @@ -340,7 +295,7 @@ public class ServerSocket { * during binding. */ public void bind(SocketAddress localAddr, int backlog) throws IOException { - checkClosedAndCreate(true); + checkOpen(); if (isBound()) { throw new BindException("Socket is already bound"); } @@ -362,7 +317,7 @@ public class ServerSocket { try { impl.bind(addr, port); isBound = true; - impl.listen(backlog > 0 ? backlog : defaultBacklog()); + impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG); } catch (IOException e) { close(); throw e; @@ -402,31 +357,10 @@ public class ServerSocket { return isClosed; } - /** - * Checks whether the socket is closed, and throws an exception. - */ - private void checkClosedAndCreate(boolean create) throws SocketException { + private void checkOpen() throws SocketException { if (isClosed()) { throw new SocketException("Socket is closed"); } - - if (!create || isCreated) { - return; - } - - synchronized (this) { - if (isCreated) { - return; - } - try { - impl.create(true); - } catch (SocketException e) { - throw e; - } catch (IOException e) { - throw new SocketException(e.toString()); - } - isCreated = true; - } } /** @@ -438,7 +372,7 @@ public class ServerSocket { * if an error occurs while setting the option value. */ public void setReuseAddress(boolean reuse) throws SocketException { - checkClosedAndCreate(true); + checkOpen(); impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(reuse)); } @@ -450,16 +384,15 @@ public class ServerSocket { * if an error occurs while reading the option value. */ public boolean getReuseAddress() throws SocketException { - checkClosedAndCreate(true); - return ((Boolean) impl.getOption(SocketOptions.SO_REUSEADDR)) - .booleanValue(); + checkOpen(); + return ((Boolean) impl.getOption(SocketOptions.SO_REUSEADDR)).booleanValue(); } /** * Sets this socket's {@link SocketOptions#SO_SNDBUF receive buffer size}. */ public void setReceiveBufferSize(int size) throws SocketException { - checkClosedAndCreate(true); + checkOpen(); if (size < 1) { throw new IllegalArgumentException("size < 1"); } @@ -470,7 +403,7 @@ public class ServerSocket { * Returns this socket's {@link SocketOptions#SO_RCVBUF receive buffer size}. */ public int getReceiveBufferSize() throws SocketException { - checkClosedAndCreate(true); + checkOpen(); return ((Integer) impl.getOption(SocketOptions.SO_RCVBUF)).intValue(); } @@ -498,8 +431,7 @@ public class ServerSocket { * @param bandwidth * the value representing the importance of high bandwidth. */ - public void setPerformancePreferences(int connectionTime, int latency, - int bandwidth) { + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { // Our socket implementation only provide one protocol: TCP/IP, so // we do nothing for this method } diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java index 06e1ab3..f7788b5 100644 --- a/luni/src/main/java/java/net/Socket.java +++ b/luni/src/main/java/java/net/Socket.java @@ -22,7 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.SocketChannel; -import libcore.io.IoUtils; +import libcore.io.IoBridge; /** * Provides a client-side TCP socket. @@ -947,7 +947,7 @@ public class Socket { } private void cacheLocalAddress() { - this.localAddress = IoUtils.getSocketLocalAddress(impl.fd); + this.localAddress = IoBridge.getSocketLocalAddress(impl.fd); } /** diff --git a/luni/src/main/java/java/net/SocketException.java b/luni/src/main/java/java/net/SocketException.java index b7366b3..23e5c01 100644 --- a/luni/src/main/java/java/net/SocketException.java +++ b/luni/src/main/java/java/net/SocketException.java @@ -28,19 +28,19 @@ public class SocketException extends IOException { private static final long serialVersionUID = -5935874303556886934L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public SocketException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public SocketException(String detailMessage) { super(detailMessage); } /** - * Constructs a new instance with the current stack trace and given cause. + * Constructs a new instance with the given cause. * @hide internal use only */ public SocketException(Throwable cause) { @@ -48,7 +48,7 @@ public class SocketException extends IOException { } /** - * Constructs a new instance with the current stack trace, given detail message and given cause. + * Constructs a new instance with given detail message and cause. * @hide internal use only */ public SocketException(String detailMessage, Throwable cause) { diff --git a/luni/src/main/java/java/net/SocketImpl.java b/luni/src/main/java/java/net/SocketImpl.java index 3d0507c..92de9cf 100644 --- a/luni/src/main/java/java/net/SocketImpl.java +++ b/luni/src/main/java/java/net/SocketImpl.java @@ -142,6 +142,13 @@ public abstract class SocketImpl implements SocketOptions { } /** + * @hide used by java.nio + */ + public FileDescriptor getFD$() { + return fd; + } + + /** * Gets the remote address this socket is connected to. * * @return the remote address of this socket. diff --git a/luni/src/main/java/java/net/SocketTimeoutException.java b/luni/src/main/java/java/net/SocketTimeoutException.java index 62e8731..8f8ef82 100644 --- a/luni/src/main/java/java/net/SocketTimeoutException.java +++ b/luni/src/main/java/java/net/SocketTimeoutException.java @@ -28,15 +28,31 @@ public class SocketTimeoutException extends InterruptedIOException { private static final long serialVersionUID = -8846654841826352300L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public SocketTimeoutException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public SocketTimeoutException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given cause. + * @hide internal use only + */ + public SocketTimeoutException(Throwable cause) { + super(null, cause); + } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public SocketTimeoutException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java index 8779cca..4e68b39 100644 --- a/luni/src/main/java/java/net/URL.java +++ b/luni/src/main/java/java/net/URL.java @@ -19,9 +19,12 @@ package java.net; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.Serializable; import java.util.Hashtable; import java.util.Locale; +import java.util.jar.JarFile; import libcore.net.http.HttpHandler; import libcore.net.http.HttpsHandler; import libcore.net.url.FileHandler; @@ -29,175 +32,126 @@ import libcore.net.url.FtpHandler; import libcore.net.url.JarHandler; /** - * A URL instance specifies the location of a resource on the internet as - * 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. + * A Uniform Resource Locator that identifies the location of an Internet + * resource as specified by <a href="http://www.ietf.org/rfc/rfc1738.txt">RFC + * 1738</a>. + * + * <h3>Parts of a URL</h3> + * A URL is composed of many parts. This class can both parse URL strings into + * parts and compose URL strings from parts. For example, consider the parts of + * this URL: + * {@code http://username:password@host:8080/directory/file?query#ref}: + * <table> + * <tr><th>Component</th><th>Example value</th><th>Also known as</th></tr> + * <tr><td>{@link #getProtocol() Protocol}</td><td>{@code http}</td><td>scheme</td></tr> + * <tr><td>{@link #getAuthority() Authority}</td><td>{@code username:password@host:8080}</td><td></td></tr> + * <tr><td>{@link #getUserInfo() User Info}</td><td>{@code username:password}</td><td></td></tr> + * <tr><td>{@link #getHost() Host}</td><td>{@code host}</td><td></td></tr> + * <tr><td>{@link #getPort() Port}</td><td>{@code 8080}</td><td></td></tr> + * <tr><td>{@link #getFile() File}</td><td>{@code /directory/file?query}</td><td></td></tr> + * <tr><td>{@link #getPath() Path}</td><td>{@code /directory/file}</td><td></td></tr> + * <tr><td>{@link #getQuery() Query}</td><td>{@code query}</td><td></td></tr> + * <tr><td>{@link #getRef() Ref}</td><td>{@code ref}</td><td>fragment</td></tr> + * </table> + * + * <h3>Supported Protocols</h3> + * This class may be used to construct URLs with the following protocols: + * <ul> + * <li><strong>file</strong>: read files from the local filesystem. + * <li><strong>ftp</strong>: <a href="http://www.ietf.org/rfc/rfc959.txt">File + * Transfer Protocol</a> + * <li><strong>http</strong>: <a href="http://www.ietf.org/rfc/rfc2616.txt">Hypertext + * Transfer Protocol</a> + * <li><strong>https</strong>: <a href="http://www.ietf.org/rfc/rfc2818.txt">HTTP + * over TLS</a> + * <li><strong>jar</strong>: read {@link JarFile Jar files} from the + * filesystem</li> + * </ul> + * In general, attempts to create URLs with any other protocol will fail with a + * {@link MalformedURLException}. Applications may install handlers for other + * schemes using {@link #setURLStreamHandlerFactory} or with the {@code + * java.protocol.handler.pkgs} system property. + * + * <p>The {@link URI} class can be used to manipulate URLs of any protocol. */ -public final class URL implements java.io.Serializable { +public final class URL implements Serializable { private static final long serialVersionUID = -7627629688361524110L; - private static final NetPermission specifyStreamHandlerPermission = new NetPermission( - "specifyStreamHandler"); - - private int hashCode; - - /** - * The receiver's filename. - * - * @serial the file of this URL - * - */ - private String file; + private static URLStreamHandlerFactory streamHandlerFactory; - /** - * The receiver's protocol identifier. - * - * @serial the protocol of this URL (http, file) - * - */ - private String protocol = null; + /** Cache of protocols to their handlers */ + private static final Hashtable<String, URLStreamHandler> streamHandlers + = new Hashtable<String, URLStreamHandler>(); - /** - * The receiver's host name. - * - * @serial the host of this URL - * - */ + private String protocol; + private String authority; private String host; - - /** - * The receiver's port number. - * - * @serial the port of this URL - * - */ private int port = -1; + private String file; + private String ref; - /** - * The receiver's authority. - * - * @serial the authority of this URL - * - */ - private String authority = null; - - /** - * The receiver's userInfo. - */ - private transient String userInfo = null; - - /** - * The receiver's path. - */ - private transient String path = null; - - /** - * The receiver's query. - */ - private transient String query = null; - - /** - * The receiver's reference. - * - * @serial the reference of this URL - * - */ - private String ref = null; - - /** - * Cache for storing protocol handler - */ - private static final Hashtable<String, URLStreamHandler> streamHandlers - = new Hashtable<String, URLStreamHandler>(); + private transient String userInfo; + private transient String path; + private transient String query; - /** - * The URL Stream (protocol) Handler - */ - transient URLStreamHandler strmHandler; + transient URLStreamHandler streamHandler; /** - * The factory responsible for producing URL Stream (protocol) Handler + * The cached hash code, or 0 if it hasn't been computed yet. Unlike the RI, + * this implementation's hashCode is transient because the hash code is + * unspecified and may vary between VMs or versions. */ - private static URLStreamHandlerFactory streamHandlerFactory; + private transient int hashCode; /** - * Sets the {@code URLStreamHandlerFactory} which creates protocol specific - * stream handlers. This method can be invoked only once during an - * application's lifetime. If the {@code URLStreamHandlerFactory} is already - * set an {@link Error} will be thrown. - * <p> - * A security check is performed to verify whether the current policy allows - * to set the stream handler factory. + * Sets the stream handler factory for this VM. * - * @param streamFactory - * the factory to be used for creating stream protocol handlers. + * @throws Error if a URLStreamHandlerFactory has already been installed + * for the current VM. */ - public static synchronized void setURLStreamHandlerFactory( - URLStreamHandlerFactory streamFactory) { + public static synchronized void setURLStreamHandlerFactory(URLStreamHandlerFactory factory) { if (streamHandlerFactory != null) { throw new Error("Factory already set"); } streamHandlers.clear(); - streamHandlerFactory = streamFactory; + streamHandlerFactory = factory; } /** - * Creates a new URL instance by parsing the string {@code spec}. + * Creates a new URL instance by parsing {@code spec}. * - * @param spec - * the URL string representation which has to be parsed. - * @throws MalformedURLException - * if the given string {@code spec} could not be parsed as a - * URL. + * @throws MalformedURLException if {@code spec} could not be parsed as a + * URL. */ public URL(String spec) throws MalformedURLException { - this((URL) null, spec, (URLStreamHandler) null); + this((URL) null, spec, null); } /** - * Creates a new URL to the specified resource {@code spec}. This URL is - * relative to the given {@code context}. If the protocol of the parsed URL - * does not match with the protocol of the context URL, then the newly - * created URL is absolute and bases only on the given URL represented by - * {@code spec}. Otherwise the protocol is defined by the context URL. + * Creates a new URL by resolving {@code spec} relative to {@code context}. * - * @param context - * the URL which is used as the context. - * @param spec - * the URL string representation which has to be parsed. - * @throws MalformedURLException - * if the given string {@code spec} could not be parsed as a URL - * or an invalid protocol has been found. + * @param context the URL to which {@code spec} is relative, or null for + * no context in which case {@code spec} must be an absolute URL. + * @throws MalformedURLException if {@code spec} could not be parsed as a + * URL or has an unsupported protocol. */ public URL(URL context, String spec) throws MalformedURLException { - this(context, spec, (URLStreamHandler) null); + this(context, spec, null); } /** - * Creates a new URL to the specified resource {@code spec}. This URL is - * relative to the given {@code context}. The {@code handler} will be used - * to parse the URL string representation. If this argument is {@code null} - * the default {@code URLStreamHandler} will be used. If the protocol of the - * parsed URL does not match with the protocol of the context URL, then the - * newly created URL is absolute and bases only on the given URL represented - * by {@code spec}. Otherwise the protocol is defined by the context URL. + * Creates a new URL by resolving {@code spec} relative to {@code context}. * - * @param context - * the URL which is used as the context. - * @param spec - * the URL string representation which has to be parsed. - * @param handler - * the specific stream handler to be used by this URL. - * @throws MalformedURLException - * if the given string {@code spec} could not be parsed as a URL - * or an invalid protocol has been found. + * @param context the URL to which {@code spec} is relative, or null for + * no context in which case {@code spec} must be an absolute URL. + * @param handler the stream handler for this URL, or null for the + * protocol's default stream handler. + * @throws MalformedURLException if the given string {@code spec} could not + * be parsed as a URL or an invalid protocol has been found. */ public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException { if (handler != null) { - strmHandler = handler; + streamHandler = handler; } if (spec == null) { @@ -208,15 +162,10 @@ public final class URL implements java.io.Serializable { // The spec includes a protocol if it includes a colon character // before the first occurrence of a slash character. Note that, // "protocol" is the field which holds this URLs protocol. - int index; - try { - index = spec.indexOf(':'); - } catch (NullPointerException e) { - throw new MalformedURLException(e.toString()); - } - int startIPv6Addr = spec.indexOf('['); + int index = spec.indexOf(':'); + int startIpv6Address = spec.indexOf('['); if (index >= 0) { - if ((startIPv6Addr == -1) || (index < startIPv6Addr)) { + if ((startIpv6Address == -1) || (index < startIpv6Address)) { protocol = spec.substring(0, index); // According to RFC 2396 scheme part should match // the following expression: @@ -254,8 +203,8 @@ public final class URL implements java.io.Serializable { .getAuthority(), context.getUserInfo(), cPath, context.getQuery(), null); } - if (strmHandler == null) { - strmHandler = context.strmHandler; + if (streamHandler == null) { + streamHandler = context.streamHandler; } } } else { @@ -269,16 +218,16 @@ public final class URL implements java.io.Serializable { set(context.getProtocol(), context.getHost(), context.getPort(), context.getAuthority(), context.getUserInfo(), context .getPath(), context.getQuery(), null); - if (strmHandler == null) { - strmHandler = context.strmHandler; + if (streamHandler == null) { + streamHandler = context.streamHandler; } } // If the stream handler has not been determined, set it // to the default for the specified protocol. - if (strmHandler == null) { + if (streamHandler == null) { setupStreamHandler(); - if (strmHandler == null) { + if (streamHandler == null) { throw new MalformedURLException("Unknown protocol: " + protocol); } } @@ -292,7 +241,7 @@ public final class URL implements java.io.Serializable { // increment it to point at either character 0 or the character // after the colon. try { - strmHandler.parseURL(this, spec, ++index, spec.length()); + streamHandler.parseURL(this, spec, ++index, spec.length()); } catch (Exception e) { throw new MalformedURLException(e.toString()); } @@ -303,64 +252,41 @@ public final class URL implements java.io.Serializable { } /** - * Creates a new URL instance using the given arguments. The URL uses the - * default port for the specified protocol. + * Creates a new URL of the given component parts. The URL uses the + * protocol's default port. * - * @param protocol - * the protocol of the new URL. - * @param host - * the host name or IP address of the new URL. - * @param file - * the name of the resource. - * @throws MalformedURLException - * if the combination of all arguments do not represent a valid - * URL or the protocol is invalid. + * @throws MalformedURLException if the combination of all arguments do not + * represent a valid URL or if the protocol is invalid. */ - public URL(String protocol, String host, String file) - throws MalformedURLException { - this(protocol, host, -1, file, (URLStreamHandler) null); + public URL(String protocol, String host, String file) throws MalformedURLException { + this(protocol, host, -1, file, null); } /** - * Creates a new URL instance using the given arguments. The URL uses the - * specified port instead of the default port for the given protocol. + * Creates a new URL of the given component parts. The URL uses the + * protocol's default port. * - * @param protocol - * the protocol of the new URL. - * @param host - * the host name or IP address of the new URL. - * @param port - * the specific port number of the URL. {@code -1} represents the - * default port of the protocol. - * @param file - * the name of the resource. - * @throws MalformedURLException - * if the combination of all arguments do not represent a valid - * URL or the protocol is invalid. - */ - public URL(String protocol, String host, int port, String file) - throws MalformedURLException { - this(protocol, host, port, file, (URLStreamHandler) null); - } - - /** - * Creates a new URL instance using the given arguments. The URL uses the - * specified port instead of the default port for the given protocol. + * @param host the host name or IP address of the new URL. + * @param port the port, or {@code -1} for the protocol's default port. + * @param file the name of the resource. + * @throws MalformedURLException if the combination of all arguments do not + * represent a valid URL or if the protocol is invalid. + */ + public URL(String protocol, String host, int port, String file) throws MalformedURLException { + this(protocol, host, port, file, null); + } + + /** + * Creates a new URL of the given component parts. The URL uses the + * protocol's default port. * - * @param protocol - * the protocol of the new URL. - * @param host - * the host name or IP address of the new URL. - * @param port - * the specific port number of the URL. {@code -1} represents the - * default port of the protocol. - * @param file - * the name of the resource. - * @param handler - * the stream handler to be used by this URL. - * @throws MalformedURLException - * if the combination of all arguments do not represent a valid - * URL or the protocol is invalid. + * @param host the host name or IP address of the new URL. + * @param port the port, or {@code -1} for the protocol's default port. + * @param file the name of the resource. + * @param handler the stream handler for this URL, or null for the + * protocol's default stream handler. + * @throws MalformedURLException if the combination of all arguments do not + * represent a valid URL or if the protocol is invalid. */ public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException { @@ -368,7 +294,7 @@ public final class URL implements java.io.Serializable { throw new MalformedURLException("Port out of range: " + port); } - if (host != null && host.indexOf(":") != -1 && host.charAt(0) != '[') { + if (host != null && host.contains(":") && host.charAt(0) != '[') { host = "[" + host + "]"; } @@ -397,11 +323,11 @@ public final class URL implements java.io.Serializable { // receiver's protocol if the handler was null. if (handler == null) { setupStreamHandler(); - if (strmHandler == null) { + if (streamHandler == null) { throw new MalformedURLException("Unknown protocol: " + protocol); } } else { - strmHandler = handler; + streamHandler = handler; } } @@ -434,20 +360,8 @@ public final class URL implements java.io.Serializable { * Sets the properties of this URL using the provided arguments. Only a * {@code URLStreamHandler} can use this method to set fields of the * existing URL instance. A URL is generally constant. - * - * @param protocol - * the protocol to be set. - * @param host - * the host name to be set. - * @param port - * the port number to be set. - * @param file - * the file to be set. - * @param ref - * the reference to be set. - */ - protected void set(String protocol, String host, int port, String file, - String ref) { + */ + protected void set(String protocol, String host, int port, String file, String ref) { if (this.protocol == null) { this.protocol = protocol; } @@ -497,59 +411,48 @@ public final class URL implements java.io.Serializable { if (this.getClass() != o.getClass()) { return false; } - return strmHandler.equals(this, (URL) o); + return streamHandler.equals(this, (URL) o); } /** - * Returns whether this URL refers to the same resource as the given - * argument {@code otherURL}. All URL components except the reference field - * are compared. - * - * @param otherURL - * the URL to compare against. - * @return {@code true} if both instances refer to the same resource, - * {@code false} otherwise. + * Returns true if this URL refers to the same resource as {@code otherURL}. + * All URL components except the reference field are compared. */ public boolean sameFile(URL otherURL) { - return strmHandler.sameFile(this, otherURL); + return streamHandler.sameFile(this, otherURL); } - /** - * Gets the hashcode value of this URL instance. - * - * @return the appropriate hashcode value. - */ - @Override - public int hashCode() { + @Override public int hashCode() { if (hashCode == 0) { - hashCode = strmHandler.hashCode(this); + hashCode = streamHandler.hashCode(this); } return hashCode; } /** * Sets the receiver's stream handler to one which is appropriate for its - * protocol. Throws a MalformedURLException if no reasonable handler is - * available. - * <p> - * Note that this will overwrite any existing stream handler with the new - * one. Senders must check if the strmHandler is null before calling the + * protocol. + * + * <p>Note that this will overwrite any existing stream handler with the new + * one. Senders must check if the streamHandler is null before calling the * method if they do not want this behavior (a speed optimization). + * + * @throws MalformedURLException if no reasonable handler is available. */ void setupStreamHandler() { // Check for a cached (previously looked up) handler for // the requested protocol. - strmHandler = streamHandlers.get(protocol); - if (strmHandler != null) { + streamHandler = streamHandlers.get(protocol); + if (streamHandler != null) { return; } // If there is a stream handler factory, then attempt to // use it to create the handler. if (streamHandlerFactory != null) { - strmHandler = streamHandlerFactory.createURLStreamHandler(protocol); - if (strmHandler != null) { - streamHandlers.put(protocol, strmHandler); + streamHandler = streamHandlerFactory.createURLStreamHandler(protocol); + if (streamHandler != null) { + streamHandlers.put(protocol, streamHandler); return; } } @@ -561,10 +464,10 @@ public final class URL implements java.io.Serializable { for (String packageName : packageList.split("\\|")) { String className = packageName + "." + protocol + ".Handler"; try { - Class<?> klass = Class.forName(className, true, ClassLoader.getSystemClassLoader()); - strmHandler = (URLStreamHandler) klass.newInstance(); - if (strmHandler != null) { - streamHandlers.put(protocol, strmHandler); + Class<?> c = Class.forName(className, true, ClassLoader.getSystemClassLoader()); + streamHandler = (URLStreamHandler) c.newInstance(); + if (streamHandler != null) { + streamHandlers.put(protocol, streamHandler); } return; } catch (IllegalAccessException ignored) { @@ -576,87 +479,75 @@ public final class URL implements java.io.Serializable { // Fall back to a built-in stream handler if the user didn't supply one if (protocol.equals("file")) { - strmHandler = new FileHandler(); + streamHandler = new FileHandler(); } else if (protocol.equals("ftp")) { - strmHandler = new FtpHandler(); + streamHandler = new FtpHandler(); } else if (protocol.equals("http")) { - strmHandler = new HttpHandler(); + streamHandler = new HttpHandler(); } else if (protocol.equals("https")) { - strmHandler = new HttpsHandler(); + streamHandler = new HttpsHandler(); } else if (protocol.equals("jar")) { - strmHandler = new JarHandler(); + streamHandler = new JarHandler(); } - if (strmHandler != null) { - streamHandlers.put(protocol, strmHandler); + if (streamHandler != null) { + streamHandlers.put(protocol, streamHandler); } } /** - * Gets the content of the resource which is referred by this URL. By - * default one of the following object types will be returned: - * <p> - * <li>Image for pictures</li> - * <li>AudioClip for audio sequences</li> - * <li>{@link InputStream} for all other data</li> - * - * @return the content of the referred resource. - * @throws IOException - * if an error occurs obtaining the content. + * Returns the content of the resource which is referred by this URL. By + * default, this returns an {@code InputStream} or null if the content type + * of the response is unknown. */ public final Object getContent() throws IOException { return openConnection().getContent(); } /** - * Gets the content of the resource which is referred by this URL. The - * argument {@code types} is an array of allowed or expected object types. - * {@code null} will be returned if the obtained object type does not match - * with one from this list. Otherwise the first type that matches will be - * used. - * - * @param types - * the list of allowed or expected object types. - * @return the object representing the resource referred by this URL, - * {@code null} if the content does not match to a specified content - * type. - * @throws IOException - * if an error occurs obtaining the content. - */ - // Param not generic in spec - @SuppressWarnings("unchecked") + * Equivalent to {@code openConnection().getContent(types)}. + */ + @SuppressWarnings("unchecked") // Param not generic in spec public final Object getContent(Class[] types) throws IOException { return openConnection().getContent(types); } /** - * Opens an InputStream to read the resource referred by this URL. - * - * @return the stream which allows to read the resource. - * @throws IOException - * if an error occurs while opening the InputStream. + * Equivalent to {@code openConnection().getInputStream(types)}. */ - public final InputStream openStream() throws java.io.IOException { + public final InputStream openStream() throws IOException { return openConnection().getInputStream(); } /** - * Opens a connection to the remote resource specified by this URL. This - * connection allows bidirectional data transfer. + * Returns a new connection to the resource referred to by this URL. * - * @return the connection to this URL. - * @throws IOException - * if an error occurs while opening the connection. + * @throws IOException if an error occurs while opening the connection. */ public URLConnection openConnection() throws IOException { - return strmHandler.openConnection(this); + return streamHandler.openConnection(this); + } + + /** + * Returns a new connection to the resource referred to by this URL. + * + * @param proxy the proxy through which the connection will be established. + * @throws IOException if an I/O error occurs while opening the connection. + * @throws IllegalArgumentException if the argument proxy is null or of is + * an invalid type. + * @throws UnsupportedOperationException if the protocol handler does not + * support opening connections through proxies. + */ + public URLConnection openConnection(Proxy proxy) throws IOException { + if (proxy == null) { + throw new IllegalArgumentException("proxy == null"); + } + return streamHandler.openConnection(this, proxy); } /** - * Converts this URL instance into an equivalent URI object. + * Returns the URI equivalent to this URL. * - * @return the URI instance that represents this URL. - * @throws URISyntaxException - * if this URL cannot be converted into a URI. + * @throws URISyntaxException if this URL cannot be converted into a URI. */ public URI toURI() throws URISyntaxException { return new URI(toExternalForm()); @@ -669,74 +560,33 @@ public final class URL implements java.io.Serializable { * @hide */ public URI toURILenient() throws URISyntaxException { - if (strmHandler == null) { + if (streamHandler == null) { throw new IllegalStateException(protocol); } - return new URI(strmHandler.toExternalForm(this, true)); - } - - /** - * Opens a connection to the remote resource specified by this URL. The - * connection will be established through the given proxy and allows - * bidirectional data transfer. - * - * @param proxy - * the proxy through which the connection will be established. - * @return the appropriate URLConnection instance representing the - * connection to this URL. - * @throws IOException - * if an I/O error occurs while opening the connection. - * @throws IllegalArgumentException - * if the argument proxy is {@code null} or is an invalid type. - * @throws UnsupportedOperationException - * if the protocol handler does not support opening connections - * through proxies. - */ - public URLConnection openConnection(Proxy proxy) throws IOException { - if (proxy == null) { - throw new IllegalArgumentException("proxy == null"); - } - return strmHandler.openConnection(this, proxy); + return new URI(streamHandler.toExternalForm(this, true)); } /** * Returns a string containing a concise, human-readable representation of * this URL. The returned string is the same as the result of the method * {@code toExternalForm()}. - * - * @return the string representation of this URL. */ - @Override - public String toString() { + @Override public String toString() { return toExternalForm(); } /** * Returns a string containing a concise, human-readable representation of * this URL. - * - * @return the string representation of this URL. */ public String toExternalForm() { - if (strmHandler == null) { + if (streamHandler == null) { return "unknown protocol(" + protocol + ")://" + host + file; } - return strmHandler.toExternalForm(this); + return streamHandler.toExternalForm(this); } - /** - * This method is called to restore the state of a URL object that has been - * serialized. The stream handler is determined from the URL's protocol. - * - * @param stream - * the stream to read from. - * - * @throws IOException - * if an IO Exception occurs while reading the stream or the - * handler can not be found. - */ - private void readObject(java.io.ObjectInputStream stream) - throws java.io.IOException { + private void readObject(ObjectInputStream stream) throws IOException { try { stream.defaultReadObject(); if (host != null && authority == null) { @@ -754,142 +604,111 @@ public final class URL implements java.io.Serializable { } } setupStreamHandler(); - if (strmHandler == null) { + if (streamHandler == null) { throw new IOException("Unknown protocol: " + protocol); } + hashCode = 0; // necessary until http://b/4471249 is fixed } catch (ClassNotFoundException e) { - throw new IOException(e.toString()); + throw new IOException(e); } } - /** - * This method is called to write any non-transient, non-static variables - * into the output stream. - * <p> - * Note that, we really only need the readObject method but the spec that - * says readObject will be ignored if no writeObject is present. - * - * @param s - * the stream to write on. - * @throws IOException - * if an IO Exception occurs during the write. - */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); } + /** @hide */ + public int getEffectivePort() { + return URI.getEffectivePort(protocol, port); + } + /** - * Gets the value of the file part of this URL. - * - * @return the file name this URL refers to or an empty string if the file - * part is not set. + * Returns the protocol of this URL like "http" or "file". This is also + * known as the scheme. The returned string is lower case. */ - public String getFile() { - return file; + public String getProtocol() { + return protocol; } /** - * Gets the value of the host part of this URL. - * - * @return the host name or IP address of this URL. + * Returns the authority part of this URL. */ - public String getHost() { - return host; + public String getAuthority() { + return authority; } /** - * Gets the port number of this URL or {@code -1} if the port is not set. - * - * @return the port number of this URL. + * Returns the user-info part of this URL. */ - public int getPort() { - return port; + public String getUserInfo() { + return userInfo; } - /** @hide */ - public int getEffectivePort() { - return URI.getEffectivePort(protocol, port); + /** + * Returns the host name or IP address of this URL. + */ + public String getHost() { + return host; } /** - * Gets the protocol of this URL. + * Returns the port number of this URL, or {@code -1} if this URL has no + * explicit port. * - * @return the protocol type of this URL. + * <p>If this URL has no explicit port, connections opened using this URL + * will use its {@link #getDefaultPort() default port}. */ - public String getProtocol() { - return protocol; + public int getPort() { + return port; } /** - * Gets the value of the reference part of this URL. + * Returns the default port number of the protocol used by this URL. If no + * default port is defined by the protocol or the {@code URLStreamHandler}, + * {@code -1} will be returned. * - * @return the reference part of this URL. + * @see URLStreamHandler#getDefaultPort */ - public String getRef() { - return ref; + public int getDefaultPort() { + return streamHandler.getDefaultPort(); } /** - * Gets the value of the query part of this URL. - * - * @return the query part of this URL. + * Returns the file of this URL, or an empty string if the file is not set. */ - public String getQuery() { - return query; + public String getFile() { + return file; } /** - * Gets the value of the path part of this URL. - * - * @return the path part of this URL. + * Returns the path part of this URL. */ public String getPath() { return path; } /** - * Gets the value of the user-info part of this URL. - * - * @return the user-info part of this URL. + * Returns the query part of this URL. */ - public String getUserInfo() { - return userInfo; + public String getQuery() { + return query; } /** - * Gets the value of the authority part of this URL. - * - * @return the authority part of this URL. + * Returns the value of the reference part of this URL. This is also known + * as the fragment. */ - public String getAuthority() { - return authority; + public String getRef() { + return ref; } /** * Sets the properties of this URL using the provided arguments. Only a * {@code URLStreamHandler} can use this method to set fields of the * existing URL instance. A URL is generally constant. - * - * @param protocol - * the protocol to be set. - * @param host - * the host name to be set. - * @param port - * the port number to be set. - * @param authority - * the authority to be set. - * @param userInfo - * the user-info to be set. - * @param path - * the path to be set. - * @param query - * the query to be set. - * @param ref - * the reference to be set. - */ - protected void set(String protocol, String host, int port, - String authority, String userInfo, String path, String query, - String ref) { + */ + protected void set(String protocol, String host, int port, String authority, String userInfo, + String path, String query, String ref) { String filePart = path; if (query != null && !query.isEmpty()) { if (filePart != null) { @@ -904,16 +723,4 @@ public final class URL implements java.io.Serializable { this.path = path; this.query = query; } - - /** - * Gets the default port number of the protocol used by this URL. If no - * default port is defined by the protocol or the {@code URLStreamHandler}, - * {@code -1} will be returned. - * - * @return the default port number according to the protocol of this URL. - * @see URLStreamHandler#getDefaultPort - */ - public int getDefaultPort() { - return strmHandler.getDefaultPort(); - } } diff --git a/luni/src/main/java/java/net/URLClassLoader.java b/luni/src/main/java/java/net/URLClassLoader.java index 735793c..1c8bc43 100644 --- a/luni/src/main/java/java/net/URLClassLoader.java +++ b/luni/src/main/java/java/net/URLClassLoader.java @@ -51,6 +51,7 @@ import libcore.io.Streams; * loaded by this {@code URLClassLoader} are granted permission to access the * URLs contained in the URL search list. */ +@FindBugsSuppressWarnings({ "DMI_COLLECTION_OF_URLS", "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" }) public class URLClassLoader extends SecureClassLoader { ArrayList<URL> originalUrls; @@ -174,13 +175,11 @@ public class URLClassLoader extends SecureClassLoader { if (is == null) { return null; } - byte[] clBuf = null; + byte[] clBuf; try { - clBuf = getBytes(is); + clBuf = Streams.readFully(is); } catch (IOException e) { return null; - } finally { - IoUtils.closeQuietly(is); } if (packageName != null) { String packageDotName = packageName.replace('/', '.'); @@ -329,15 +328,12 @@ public class URLClassLoader extends SecureClassLoader { } private Class<?> createClass(JarEntry entry, Manifest manifest, String packageName, String origName) { - InputStream is = null; - byte[] clBuf = null; + byte[] clBuf; try { - is = jf.getInputStream(entry); - clBuf = getBytes(is); + InputStream is = jf.getInputStream(entry); + clBuf = Streams.readFully(is); } catch (IOException e) { return null; - } finally { - IoUtils.closeQuietly(is); } if (packageName != null) { String packageDotName = packageName.replace('/', '.'); @@ -600,17 +596,6 @@ public class URLClassLoader extends SecureClassLoader { } /** - * Converts an input stream into a byte array. - * - * @param is - * the input stream - * @return byte[] the byte array - */ - private static byte[] getBytes(InputStream is) throws IOException { - return Streams.readFully(is); - } - - /** * Gets all permissions for the specified {@code codesource}. First, this * method retrieves the permissions from the system policy. If the protocol * is "file:/" then a new permission, {@code FilePermission}, granting the diff --git a/luni/src/main/java/java/net/URLConnection.java b/luni/src/main/java/java/net/URLConnection.java index 26b805f..a8d967c 100644 --- a/luni/src/main/java/java/net/URLConnection.java +++ b/luni/src/main/java/java/net/URLConnection.java @@ -353,13 +353,8 @@ public abstract class URLConnection { } /** - * Returns the default value for the specified request {@code field} or {@code - * null} if the field could not be found. The base implementation of this - * method returns always {@code null}. + * Returns null. * - * @param field - * the request field whose default value shall be returned. - * @return the default value for the given field. * @deprecated Use {@link #getRequestProperty} */ @Deprecated @@ -856,16 +851,9 @@ public abstract class URLConnection { } /** - * Sets the default value of the specified request header field. This value - * will be used for the specific field of every newly created connection. - * The base implementation of this method does nothing. + * Does nothing. * - * @param field - * the request header field to be set. - * @param value - * the default value to be used. - * @deprecated Use {@link #setRequestProperty} of an existing {@code - * URLConnection} instance. + * @deprecated Use {@link URLConnection#setRequestProperty(String, String)}. */ @Deprecated public static void setDefaultRequestProperty(String field, String value) { diff --git a/luni/src/main/java/java/net/URLStreamHandler.java b/luni/src/main/java/java/net/URLStreamHandler.java index 7a86644..dc1c680 100644 --- a/luni/src/main/java/java/net/URLStreamHandler.java +++ b/luni/src/main/java/java/net/URLStreamHandler.java @@ -90,7 +90,7 @@ public abstract class URLStreamHandler { throw new StringIndexOutOfBoundsException(end - 2 - start); } if (end < start) { - if (this != u.strmHandler) { + if (this != u.streamHandler) { throw new SecurityException(); } return; @@ -251,7 +251,7 @@ public abstract class URLStreamHandler { @Deprecated protected void setURL(URL u, String protocol, String host, int port, String file, String ref) { - if (this != u.strmHandler) { + if (this != u.streamHandler) { throw new SecurityException(); } u.set(protocol, host, port, file, ref); @@ -283,7 +283,7 @@ public abstract class URLStreamHandler { protected void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, String query, String ref) { - if (this != u.strmHandler) { + if (this != u.streamHandler) { throw new SecurityException(); } u.set(protocol, host, port, authority, userInfo, file, query, ref); diff --git a/luni/src/main/java/java/net/UnknownServiceException.java b/luni/src/main/java/java/net/UnknownServiceException.java index a8afa41..bdcf6a5 100644 --- a/luni/src/main/java/java/net/UnknownServiceException.java +++ b/luni/src/main/java/java/net/UnknownServiceException.java @@ -30,15 +30,23 @@ public class UnknownServiceException extends IOException { private static final long serialVersionUID = -4169033248853639508L; /** - * Constructs a new instance with the current stack trace. + * Constructs a new instance. */ public UnknownServiceException() { } /** - * Constructs a new instance with the current stack trace and given detail message. + * Constructs a new instance with the given detail message. */ public UnknownServiceException(String detailMessage) { super(detailMessage); } + + /** + * Constructs a new instance with given detail message and cause. + * @hide internal use only + */ + public UnknownServiceException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } } diff --git a/luni/src/main/java/java/nio/Buffer.java b/luni/src/main/java/java/nio/Buffer.java index c0ba368..bc152ef 100644 --- a/luni/src/main/java/java/nio/Buffer.java +++ b/luni/src/main/java/java/nio/Buffer.java @@ -303,7 +303,7 @@ public abstract class Buffer { */ void limitImpl(int newLimit) { if (newLimit < 0 || newLimit > capacity) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Bad limit (capacity " + capacity + "): " + newLimit); } limit = newLimit; @@ -355,7 +355,7 @@ public abstract class Buffer { void positionImpl(int newPosition) { if (newPosition < 0 || newPosition > limit) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Bad position (limit " + limit + "): " + newPosition); } position = newPosition; @@ -383,7 +383,7 @@ public abstract class Buffer { */ public final Buffer reset() { if (mark == UNSET_MARK) { - throw new InvalidMarkException(); + throw new InvalidMarkException("Mark not set"); } position = mark; return this; diff --git a/luni/src/main/java/java/nio/CharBuffer.java b/luni/src/main/java/java/nio/CharBuffer.java index cb041f5..03bac04 100644 --- a/luni/src/main/java/java/nio/CharBuffer.java +++ b/luni/src/main/java/java/nio/CharBuffer.java @@ -630,8 +630,6 @@ public abstract class CharBuffer extends Buffer implements /** * Returns a string representing the current remaining chars of this buffer. - * - * @return a string representing the current remaining chars of this buffer. */ @Override public String toString() { diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java index 970c116..a626857 100644 --- a/luni/src/main/java/java/nio/DatagramChannelImpl.java +++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java @@ -36,9 +36,11 @@ import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.spi.SelectorProvider; import java.util.Arrays; +import libcore.io.ErrnoException; +import libcore.io.IoBridge; import libcore.io.IoUtils; +import libcore.io.Libcore; import libcore.util.EmptyArray; -import org.apache.harmony.luni.platform.Platform; /* * The default implementation class of java.nio.channels.DatagramChannel. @@ -70,7 +72,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann */ protected DatagramChannelImpl(SelectorProvider selectorProvider) throws IOException { super(selectorProvider); - fd = IoUtils.socket(false); + fd = IoBridge.socket(false); } /* @@ -99,7 +101,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann * Returns the local address to which the socket is bound. */ InetAddress getLocalAddress() { - return IoUtils.getSocketLocalAddress(fd); + return IoBridge.getSocketLocalAddress(fd); } /** @@ -126,7 +128,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address); try { begin(); - IoUtils.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort()); + IoBridge.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort()); } catch (ConnectException e) { // ConnectException means connect fail, not exception } finally { @@ -150,7 +152,11 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann } connected = false; connectAddress = null; - Platform.NETWORK.disconnectDatagram(fd); + try { + Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0); + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsIOException(); + } if (socket != null) { socket.disconnect(); } @@ -193,21 +199,15 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann DatagramPacket receivePacket; int oldposition = target.position(); int received = 0; + // TODO: disallow mapped buffers and lose this conditional? if (target.hasArray()) { - receivePacket = new DatagramPacket(target.array(), target - .position() - + target.arrayOffset(), target.remaining()); + receivePacket = new DatagramPacket(target.array(), target.position() + target.arrayOffset(), target.remaining()); } else { - receivePacket = new DatagramPacket(new byte[target.remaining()], - target.remaining()); + receivePacket = new DatagramPacket(new byte[target.remaining()], target.remaining()); } do { - received = Platform.NETWORK.recv(fd, receivePacket, - receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), - false, isConnected()); - + received = IoBridge.recvfrom(false, fd, receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), 0, receivePacket, isConnected()); if (receivePacket != null && receivePacket.getAddress() != null) { - if (received > 0) { if (target.hasArray()) { target.position(oldposition + received); @@ -229,10 +229,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann int oldposition = target.position(); int received = 0; do { - int address = NioUtils.getDirectBufferAddress(target); - received = Platform.NETWORK.recvDirect(fd, receivePacket, address, - target.position(), target.remaining(), false, isConnected()); - + received = IoBridge.recvfrom(false, fd, target, 0, receivePacket, isConnected()); if (receivePacket != null && receivePacket.getAddress() != null) { // copy the data of received packet if (received > 0) { @@ -245,18 +242,11 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann return retAddr; } - /** - * @see java.nio.channels.DatagramChannel#send(java.nio.ByteBuffer, - * java.net.SocketAddress) - */ @Override public int send(ByteBuffer source, SocketAddress socketAddress) throws IOException { - // must not null checkNotNull(source); - // must open checkOpen(); - // transfer socketAddress InetSocketAddress isa = (InetSocketAddress) socketAddress; if (isa.getAddress() == null) { throw new IOException(); @@ -266,38 +256,19 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann throw new IllegalArgumentException(); } - // the return value. - int sendCount = 0; - try { - begin(); - byte[] array = null; - int length = source.remaining(); - int oldposition = source.position(); - int start = oldposition; - if (source.isDirect()) { - synchronized (writeLock) { - int address = NioUtils.getDirectBufferAddress(source); - sendCount = Platform.NETWORK.sendDirect(fd, address, start, length, - isa.getPort(), isa.getAddress()); - } - } else { - if (source.hasArray()) { - array = source.array(); - start += source.arrayOffset(); - } else { - array = new byte[length]; - source.get(array); - start = 0; - } - synchronized (writeLock) { - sendCount = Platform.NETWORK.send(fd, array, start, length, - isa.getPort(), isa.getAddress()); + synchronized (writeLock) { + int sendCount = 0; + try { + begin(); + int oldPosition = source.position(); + sendCount = IoBridge.sendto(fd, source, 0, isa.getAddress(), isa.getPort()); + if (sendCount > 0) { + source.position(oldPosition + sendCount); } + } finally { + end(sendCount >= 0); } - source.position(oldposition + sendCount); return sendCount; - } finally { - end(sendCount >= 0); } } @@ -360,64 +331,34 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann /* * read from channel, and store the result in the target. */ - private int readImpl(ByteBuffer readBuffer) throws IOException { + private int readImpl(ByteBuffer dst) throws IOException { synchronized (readLock) { int readCount = 0; try { begin(); - int start = readBuffer.position(); - int length = readBuffer.remaining(); - if (readBuffer.isDirect()) { - int address = NioUtils.getDirectBufferAddress(readBuffer); - readCount = Platform.NETWORK.recvDirect(fd, null, address, start, length, - false, isConnected()); - } else { - // the target is assured to have array. - byte[] target = readBuffer.array(); - start += readBuffer.arrayOffset(); - readCount = Platform.NETWORK.recv(fd, null, target, start, length, false, - isConnected()); - } - return readCount; + readCount = IoBridge.recvfrom(false, fd, dst, 0, null, isConnected()); } catch (InterruptedIOException e) { // InterruptedIOException will be thrown when timeout. return 0; } finally { end(readCount > 0); } + return readCount; } } - /** - * @see java.nio.channels.DatagramChannel#write(java.nio.ByteBuffer) - */ - @Override - public int write(ByteBuffer source) throws IOException { - // source buffer must be not null - checkNotNull(source); - // status must be open and connected + @Override public int write(ByteBuffer src) throws IOException { + checkNotNull(src); checkOpenConnected(); - // return immediately if source is full - if (!source.hasRemaining()) { + if (!src.hasRemaining()) { return 0; } - ByteBuffer writeBuffer = null; - byte[] writeArray = null; - int oldposition = source.position(); - int result; - if (source.isDirect() || source.hasArray()) { - writeBuffer = source; - } else { - writeArray = new byte[source.remaining()]; - source.get(writeArray); - writeBuffer = ByteBuffer.wrap(writeArray); + int writeCount = writeImpl(src); + if (writeCount > 0) { + src.position(src.position() + writeCount); } - result = writeImpl(writeBuffer); - if (result > 0) { - source.position(oldposition + result); - } - return result; + return writeCount; } /** @@ -455,50 +396,32 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann return written; } - /* - * Write the source. Return the count of bytes written. - */ private int writeImpl(ByteBuffer buf) throws IOException { synchronized (writeLock) { int result = 0; try { begin(); - int length = buf.remaining(); - int start = buf.position(); - - if (buf.isDirect()) { - int address = NioUtils.getDirectBufferAddress(buf); - result = Platform.NETWORK.sendDirect(fd, address, start, length, 0, null); - } else { - // buf is assured to have array. - start += buf.arrayOffset(); - result = Platform.NETWORK.send(fd, buf.array(), start, length, 0, null); - } - return result; + result = IoBridge.sendto(fd, buf, 0, null, 0); } finally { end(result > 0); } + return result; } } - /* - * Do really closing action here. - */ - @Override - protected synchronized void implCloseSelectableChannel() throws IOException { + @Override protected synchronized void implCloseSelectableChannel() throws IOException { connected = false; if (socket != null && !socket.isClosed()) { socket.close(); } else { - Platform.NETWORK.close(fd); + IoBridge.closeSocket(fd); } } - @Override - protected void implConfigureBlocking(boolean blockingMode) throws IOException { - // Do nothing here. For real read/write operation in nonblocking mode, - // it uses select system call. Whether a channel is blocking can be - // decided by isBlocking() method. + @Override protected void implConfigureBlocking(boolean blocking) throws IOException { + synchronized (blockingLock()) { + IoUtils.setBlocking(fd, blocking); + } } /* diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java index acc3725..53c1e4d 100644 --- a/luni/src/main/java/java/nio/FileChannelImpl.java +++ b/luni/src/main/java/java/nio/FileChannelImpl.java @@ -238,9 +238,6 @@ final class FileChannelImpl extends FileChannel { public long position() throws IOException { checkOpen(); - if ((mode & O_APPEND) != 0) { - return size(); - } try { return Libcore.os.lseek(fd, 0L, SEEK_CUR); } catch (ErrnoException errnoException) { @@ -300,6 +297,9 @@ final class FileChannelImpl extends FileChannel { begin(); try { bytesRead = Libcore.os.read(fd, buffer); + if (bytesRead == 0) { + bytesRead = -1; + } } catch (ErrnoException errnoException) { if (errnoException.errno == EAGAIN) { // We don't throw if we try to read from an empty non-blocking pipe. @@ -388,12 +388,12 @@ final class FileChannelImpl extends FileChannel { } } - public long transferTo(long position, long count, WritableByteChannel target) - throws IOException { + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { checkOpen(); if (!target.isOpen()) { throw new ClosedChannelException(); } + checkReadable(); if (target instanceof FileChannelImpl) { ((FileChannelImpl) target).checkWritable(); } @@ -482,9 +482,6 @@ final class FileChannelImpl extends FileChannel { public int write(ByteBuffer buffer) throws IOException { checkOpen(); checkWritable(); - if ((mode & O_APPEND) != 0) { - position(size()); - } return writeImpl(buffer); } diff --git a/luni/src/main/java/java/nio/InvalidMarkException.java b/luni/src/main/java/java/nio/InvalidMarkException.java index cb1050e..349cf3a 100644 --- a/luni/src/main/java/java/nio/InvalidMarkException.java +++ b/luni/src/main/java/java/nio/InvalidMarkException.java @@ -29,4 +29,12 @@ public class InvalidMarkException extends IllegalStateException { */ public InvalidMarkException() { } + + /** + * Constructs an {@code InvalidMarkException} with the given detail message. + * @hide + */ + public InvalidMarkException(String detailMessage) { + super(detailMessage); + } } diff --git a/luni/src/main/java/java/nio/IoVec.java b/luni/src/main/java/java/nio/IoVec.java index 77bddb6..e548548 100644 --- a/luni/src/main/java/java/nio/IoVec.java +++ b/luni/src/main/java/java/nio/IoVec.java @@ -22,7 +22,7 @@ import libcore.io.Libcore; import libcore.io.ErrnoException; /** - * Used to implement java.nio read(ByteBuffer[])/write(ByteBuffer[]) operations to POSIX readv(2) + * Used to implement java.nio read(ByteBuffer[])/write(ByteBuffer[]) operations as POSIX readv(2) * and writev(2) calls. */ final class IoVec { @@ -92,7 +92,7 @@ final class IoVec { b.position(b.limit()); byteCount -= byteCounts[i]; } else { - b.position((direction == Direction.WRITEV ? b.position() : 0) + byteCounts[i]); + b.position((direction == Direction.WRITEV ? b.position() : 0) + byteCount); byteCount = 0; } } diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java index 925c1e2..dfd3270 100644 --- a/luni/src/main/java/java/nio/SelectorImpl.java +++ b/luni/src/main/java/java/nio/SelectorImpl.java @@ -34,36 +34,20 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.UnsafeArrayList; import libcore.io.ErrnoException; +import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; +import libcore.io.StructPollfd; import libcore.util.EmptyArray; -import org.apache.harmony.luni.platform.Platform; +import static libcore.io.OsConstants.*; /* * Default implementation of java.nio.channels.Selector */ final class SelectorImpl extends AbstractSelector { - static final FileDescriptor[] EMPTY_FILE_DESCRIPTORS_ARRAY = new FileDescriptor[0]; - - private static final SelectionKeyImpl[] EMPTY_SELECTION_KEY_IMPLS_ARRAY - = new SelectionKeyImpl[0]; - - private static final int CONNECT_OR_WRITE = OP_CONNECT | OP_WRITE; - - private static final int ACCEPT_OR_READ = OP_ACCEPT | OP_READ; - - private static final int NA = 0; - - private static final int READABLE = 1; - - private static final int WRITABLE = 2; - - private static final int SELECT_BLOCK = -1; - - private static final int SELECT_NOW = 0; - /** * Used to synchronize when a key's interest ops change. */ @@ -94,33 +78,7 @@ final class SelectorImpl extends AbstractSelector { private final FileDescriptor wakeupIn; private final FileDescriptor wakeupOut; - /** - * File descriptors we're interested in reading from. When actively - * selecting, the first element is always the wakeup channel's file - * descriptor, and the other elements are user-specified file descriptors. - * Otherwise, all elements are null. - */ - private FileDescriptor[] readableFDs = EMPTY_FILE_DESCRIPTORS_ARRAY; - - /** - * File descriptors we're interested in writing from. May be empty. When not - * actively selecting, all elements are null. - */ - private FileDescriptor[] writableFDs = EMPTY_FILE_DESCRIPTORS_ARRAY; - - /** - * Selection keys that correspond to the concatenation of readableFDs and - * writableFDs. This is used to interpret the results returned by select(). - * When not actively selecting, all elements are null. - */ - private SelectionKeyImpl[] readyKeys = EMPTY_SELECTION_KEY_IMPLS_ARRAY; - - /** - * Selection flags that define the ready ops on the ready keys. When not - * actively selecting, all elements are 0. Corresponds to the ready keys - * set. - */ - private int[] flags = EmptyArray.INT; + private final UnsafeArrayList<StructPollfd> pollFds = new UnsafeArrayList<StructPollfd>(StructPollfd.class, 8); public SelectorImpl(SelectorProvider selectorProvider) throws IOException { super(selectorProvider); @@ -135,6 +93,8 @@ final class SelectorImpl extends AbstractSelector { wakeupIn = pipeFds[0]; wakeupOut = pipeFds[1]; IoUtils.setBlocking(wakeupIn, false); + pollFds.add(new StructPollfd()); + setPollFd(0, wakeupIn, POLLIN, null); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } @@ -163,9 +123,10 @@ final class SelectorImpl extends AbstractSelector { } synchronized (this) { synchronized (unmodifiableKeys) { - SelectionKeyImpl selectionKey = new SelectionKeyImpl( - channel, operations, attachment, this); + SelectionKeyImpl selectionKey = new SelectionKeyImpl(channel, operations, + attachment, this); mutableKeys.add(selectionKey); + ensurePollFdsCapacity(); return selectionKey; } } @@ -183,18 +144,20 @@ final class SelectorImpl extends AbstractSelector { } @Override public int select() throws IOException { - return selectInternal(SELECT_BLOCK); + // Blocks until some fd is ready. + return selectInternal(-1); } @Override public int select(long timeout) throws IOException { if (timeout < 0) { throw new IllegalArgumentException(); } - return selectInternal((timeout == 0) ? SELECT_BLOCK : timeout); + // Our timeout is interpreted differently to Unix's --- 0 means block. See selectNow. + return selectInternal((timeout == 0) ? -1 : timeout); } @Override public int selectNow() throws IOException { - return selectInternal(SELECT_NOW); + return selectInternal(0); } private int selectInternal(long timeout) throws IOException { @@ -203,146 +166,113 @@ final class SelectorImpl extends AbstractSelector { synchronized (unmodifiableKeys) { synchronized (selectedKeys) { doCancel(); - boolean isBlock = (SELECT_NOW != timeout); - int readableKeysCount = 1; // first is always the wakeup channel - int writableKeysCount = 0; + boolean isBlock = (timeout != 0); synchronized (keysLock) { - for (SelectionKeyImpl key : mutableKeys) { - int ops = key.interestOpsNoCheck(); - if ((ACCEPT_OR_READ & ops) != 0) { - readableKeysCount++; - } - if ((CONNECT_OR_WRITE & ops) != 0) { - writableKeysCount++; - } - } - prepareChannels(readableKeysCount, writableKeysCount); + preparePollFds(); } - boolean success; + int rc; try { if (isBlock) { begin(); } - success = Platform.NETWORK.select(readableFDs, writableFDs, - readableKeysCount, writableKeysCount, timeout, flags); + rc = Libcore.os.poll(pollFds.array(), (int) timeout); } finally { if (isBlock) { end(); } } - int selected = success ? processSelectResult() : 0; - - Arrays.fill(readableFDs, null); - Arrays.fill(writableFDs, null); - Arrays.fill(readyKeys, null); - Arrays.fill(flags, 0); - - selected -= doCancel(); - - return selected; + int readyCount = (rc > 0) ? processPollFds() : 0; + readyCount -= doCancel(); + return readyCount; } } } } - private int getReadyOps(SelectionKeyImpl key) { - SelectableChannel channel = key.channel(); - return ((channel instanceof SocketChannel) && !((SocketChannel) channel).isConnectionPending()) ? - OP_WRITE : CONNECT_OR_WRITE; + private void setPollFd(int i, FileDescriptor fd, int events, Object object) { + StructPollfd pollFd = pollFds.get(i); + pollFd.fd = fd; + pollFd.events = (short) events; + pollFd.userData = object; } - /** - * Prepare the readableFDs, writableFDs, readyKeys and flags arrays in - * preparation for a call to {@code INetworkSystem#select()}. After they're - * used, the array elements must be cleared. - */ - private void prepareChannels(int numReadable, int numWritable) { - // grow each array to sufficient capacity. Always grow to at least 1.5x - // to avoid growing too frequently - if (readableFDs.length < numReadable) { - int newSize = Math.max((int) (readableFDs.length * 1.5f), numReadable); - readableFDs = new FileDescriptor[newSize]; - } - if (writableFDs.length < numWritable) { - int newSize = Math.max((int) (writableFDs.length * 1.5f), numWritable); - writableFDs = new FileDescriptor[newSize]; - } - int total = numReadable + numWritable; - if (readyKeys.length < total) { - int newSize = Math.max((int) (readyKeys.length * 1.5f), total); - readyKeys = new SelectionKeyImpl[newSize]; - flags = new int[newSize]; - } - - // populate the FDs, including the wakeup channel - readableFDs[0] = wakeupIn; - int r = 1; - int w = 0; + private void preparePollFds() { + int i = 1; // Our wakeup pipe comes before all the user's fds. for (SelectionKeyImpl key : mutableKeys) { int interestOps = key.interestOpsNoCheck(); - if ((ACCEPT_OR_READ & interestOps) != 0) { - readableFDs[r] = ((FileDescriptorChannel) key.channel()).getFD(); - readyKeys[r] = key; - r++; + short eventMask = 0; + if (((OP_ACCEPT | OP_READ) & interestOps) != 0) { + eventMask |= POLLIN; } - if ((getReadyOps(key) & interestOps) != 0) { - writableFDs[w] = ((FileDescriptorChannel) key.channel()).getFD(); - readyKeys[w + numReadable] = key; - w++; + if (((OP_CONNECT | OP_WRITE) & interestOps) != 0) { + eventMask |= POLLOUT; + } + if (eventMask != 0) { + setPollFd(i++, ((FileDescriptorChannel) key.channel()).getFD(), eventMask, key); } } } + private void ensurePollFdsCapacity() { + // We need one slot for each element of mutableKeys, plus one for the wakeup pipe. + while (pollFds.size() < mutableKeys.size() + 1) { + pollFds.add(new StructPollfd()); + } + } + /** - * Updates the key ready ops and selected key set with data from the flags - * array. + * Updates the key ready ops and selected key set. */ - private int processSelectResult() throws IOException { - if (flags[0] == READABLE) { + private int processPollFds() throws IOException { + if (pollFds.get(0).revents == POLLIN) { // Read bytes from the wakeup pipe until the pipe is empty. byte[] buffer = new byte[8]; - while (IoUtils.read(wakeupIn, buffer, 0, 1) > 0) { + while (IoBridge.read(wakeupIn, buffer, 0, 1) > 0) { } } - int selected = 0; - for (int i = 1; i < flags.length; i++) { - if (flags[i] == NA) { + int readyKeyCount = 0; + for (int i = 1; i < pollFds.size(); ++i) { + StructPollfd pollFd = pollFds.get(i); + if (pollFd.revents == 0) { continue; } + if (pollFd.fd == null) { + break; + } + + SelectionKeyImpl key = (SelectionKeyImpl) pollFd.userData; + + pollFd.fd = null; + pollFd.userData = null; - SelectionKeyImpl key = readyKeys[i]; int ops = key.interestOpsNoCheck(); int selectedOp = 0; - - switch (flags[i]) { - case READABLE: - selectedOp = ACCEPT_OR_READ & ops; - break; - case WRITABLE: - if (key.isConnected()) { - selectedOp = OP_WRITE & ops; - } else { - selectedOp = OP_CONNECT & ops; - } - break; + if ((pollFd.revents & POLLIN) != 0) { + selectedOp = ops & (OP_ACCEPT | OP_READ); + } else if ((pollFd.revents & POLLOUT) != 0) { + if (key.isConnected()) { + selectedOp = ops & OP_WRITE; + } else { + selectedOp = ops & OP_CONNECT; + } } if (selectedOp != 0) { boolean wasSelected = mutableSelectedKeys.contains(key); if (wasSelected && key.readyOps() != selectedOp) { key.setReadyOps(key.readyOps() | selectedOp); - selected++; + ++readyKeyCount; } else if (!wasSelected) { key.setReadyOps(selectedOp); mutableSelectedKeys.add(key); - selected++; + ++readyKeyCount; } } } - return selected; + return readyKeyCount; } @Override public synchronized Set<SelectionKey> selectedKeys() { diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java index e3103b3..5331158 100644 --- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java +++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java @@ -31,16 +31,15 @@ import java.nio.channels.NotYetBoundException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import org.apache.harmony.luni.platform.Platform; +import libcore.io.IoUtils; /** * The default ServerSocketChannel. */ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileDescriptorChannel { - private final FileDescriptor fd = new FileDescriptor(); - private final SocketImpl impl = new PlainServerSocketImpl(fd); - private final ServerSocketAdapter socket = new ServerSocketAdapter(impl, this); + private final ServerSocketAdapter socket; + private final SocketImpl impl; private boolean isBound = false; @@ -48,12 +47,8 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD public ServerSocketChannelImpl(SelectorProvider sp) throws IOException { super(sp); - } - - // for native call - @SuppressWarnings("unused") - private ServerSocketChannelImpl() throws IOException { - this(SelectorProvider.provider()); + this.socket = new ServerSocketAdapter(this); + this.impl = socket.getImpl$(); } @Override public ServerSocket socket() { @@ -68,47 +63,35 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD throw new NotYetBoundException(); } - // TODO: pass in the SelectorProvider used to create this ServerSocketChannelImpl? // Create an empty socket channel. This will be populated by ServerSocketAdapter.accept. - SocketChannelImpl result = new SocketChannelImpl(SelectorProvider.provider(), false); - Socket resultSocket = result.socket(); - + SocketChannelImpl result = new SocketChannelImpl(provider(), false); + boolean connected = false; try { begin(); synchronized (acceptLock) { synchronized (blockingLock()) { - boolean isBlocking = isBlocking(); - if (!isBlocking) { - int[] tryResult = new int[1]; - boolean success = Platform.NETWORK.select(new FileDescriptor[] { fd }, - SelectorImpl.EMPTY_FILE_DESCRIPTORS_ARRAY, 1, 0, 0, tryResult); - if (!success || tryResult[0] == 0) { - // no pending connections, returns immediately. - return null; - } - } - // do accept. do { try { - socket.accept(resultSocket, result); + socket.implAccept(result); // select successfully, break out immediately. break; } catch (SocketTimeoutException e) { // continue to accept if the channel is in blocking mode. + // TODO: does this make sense? why does blocking imply no timeouts? } - } while (isBlocking); + } while (isBlocking()); } } } finally { - end(resultSocket.isConnected()); + end(result.socket().isConnected()); } - return result; + return result.socket().isConnected() ? result : null; } - protected void implConfigureBlocking(boolean blockingMode) throws IOException { - // Do nothing here. For real accept() operation in non-blocking mode, - // it uses INetworkSystem.select. Whether a channel is blocking can be - // decided by isBlocking() method. + @Override protected void implConfigureBlocking(boolean blocking) throws IOException { + synchronized (blockingLock()) { + IoUtils.setBlocking(impl.getFD$(), blocking); + } } synchronized protected void implCloseSelectableChannel() throws IOException { @@ -118,14 +101,13 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD } public FileDescriptor getFD() { - return fd; + return impl.getFD$(); } private static class ServerSocketAdapter extends ServerSocket { private final ServerSocketChannelImpl channelImpl; - ServerSocketAdapter(SocketImpl impl, ServerSocketChannelImpl aChannelImpl) { - super(impl); + ServerSocketAdapter(ServerSocketChannelImpl aChannelImpl) throws IOException { this.channelImpl = aChannelImpl; } @@ -145,22 +127,23 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD return sc.socket(); } - private Socket accept(Socket socket, SocketChannelImpl sockChannel) throws IOException { + public Socket implAccept(SocketChannelImpl clientSocketChannel) throws IOException { + Socket clientSocket = clientSocketChannel.socket(); boolean connectOK = false; try { synchronized (this) { - super.implAccept(socket); - sockChannel.setConnected(); - sockChannel.setBound(true); - sockChannel.finishAccept(); + super.implAccept(clientSocket); + clientSocketChannel.setConnected(); + clientSocketChannel.setBound(true); + clientSocketChannel.finishAccept(); } connectOK = true; } finally { if (!connectOK) { - socket.close(); + clientSocket.close(); } } - return socket; + return clientSocket; } @Override public ServerSocketChannel getChannel() { diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java index 51cda16..9f8c690 100644 --- a/luni/src/main/java/java/nio/SocketChannelImpl.java +++ b/luni/src/main/java/java/nio/SocketChannelImpl.java @@ -44,52 +44,43 @@ import java.nio.channels.spi.SelectorProvider; import java.util.Arrays; import libcore.io.ErrnoException; import libcore.io.Libcore; +import libcore.io.IoBridge; import libcore.io.IoUtils; -import org.apache.harmony.luni.platform.Platform; import static libcore.io.OsConstants.*; /* * The default implementation class of java.nio.channels.SocketChannel. */ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { - - private static final int EOF = -1; - - // Status un-init, not initialized. - static final int SOCKET_STATUS_UNINIT = EOF; + private static final int SOCKET_STATUS_UNINITIALIZED = -1; // Status before connect. - static final int SOCKET_STATUS_UNCONNECTED = 0; + private static final int SOCKET_STATUS_UNCONNECTED = 0; // Status connection pending. - static final int SOCKET_STATUS_PENDING = 1; + private static final int SOCKET_STATUS_PENDING = 1; // Status after connection success. - static final int SOCKET_STATUS_CONNECTED = 2; + private static final int SOCKET_STATUS_CONNECTED = 2; // Status closed. - static final int SOCKET_STATUS_CLOSED = 3; + private static final int SOCKET_STATUS_CLOSED = 3; - // The descriptor to interact with native code. - final FileDescriptor fd; + private final FileDescriptor fd; // Our internal Socket. private SocketAdapter socket = null; // The address to be connected. - InetSocketAddress connectAddress = null; + private InetSocketAddress connectAddress = null; - // Local address of the this socket (package private for adapter). - InetAddress localAddress = null; + private InetAddress localAddress = null; + private int localPort; - // Local port number. - int localPort; - - // At first, uninitialized. - int status = SOCKET_STATUS_UNINIT; + private int status = SOCKET_STATUS_UNINITIALIZED; // Whether the socket is bound. - volatile boolean isBound = false; + private volatile boolean isBound = false; private final Object readLock = new Object(); @@ -108,7 +99,7 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { public SocketChannelImpl(SelectorProvider selectorProvider, boolean connect) throws IOException { super(selectorProvider); status = SOCKET_STATUS_UNCONNECTED; - fd = (connect ? IoUtils.socket(true) : new FileDescriptor()); + fd = (connect ? IoBridge.socket(true) : new FileDescriptor()); } /* @@ -174,7 +165,7 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { if (isBlocking()) { begin(); } - finished = IoUtils.connect(fd, normalAddr, port); + finished = IoBridge.connect(fd, normalAddr, port); isBound = finished; } catch (IOException e) { if (e instanceof ConnectException && !isBlocking()) { @@ -236,7 +227,9 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { boolean finished = false; try { begin(); - finished = Platform.NETWORK.isConnected(fd, 0); + InetAddress inetAddress = connectAddress.getAddress(); + int port = connectAddress.getPort(); + finished = IoBridge.isConnected(fd, inetAddress, port, 0, 0); // Return immediately. isBound = finished; } catch (ConnectException e) { if (isOpen()) { @@ -260,30 +253,13 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { } @Override - public int read(ByteBuffer target) throws IOException { - FileChannelImpl.checkWritable(target); + public int read(ByteBuffer dst) throws IOException { + FileChannelImpl.checkWritable(dst); checkOpenConnected(); - if (!target.hasRemaining()) { + if (!dst.hasRemaining()) { return 0; } - - int readCount; - if (target.isDirect() || target.hasArray()) { - readCount = readImpl(target); - if (readCount > 0) { - target.position(target.position() + readCount); - } - } else { - ByteBuffer readBuffer = null; - byte[] readArray = null; - readArray = new byte[target.remaining()]; - readBuffer = ByteBuffer.wrap(readArray); - readCount = readImpl(readBuffer); - if (readCount > 0) { - target.put(readArray, 0, readCount); - } - } - return readCount; + return readImpl(dst); } @Override @@ -297,9 +273,9 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { byte[] readArray = new byte[totalCount]; ByteBuffer readBuffer = ByteBuffer.wrap(readArray); int readCount; - // read data to readBuffer, and then transfer data from readBuffer to - // targets. + // read data to readBuffer, and then transfer data from readBuffer to targets. readCount = readImpl(readBuffer); + readBuffer.flip(); if (readCount > 0) { int left = readCount; int index = offset; @@ -314,49 +290,36 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { return readCount; } - /** - * Read from channel, and store the result in the target. - * - * @param target - * output parameter - */ - private int readImpl(ByteBuffer target) throws IOException { + private int readImpl(ByteBuffer dst) throws IOException { synchronized (readLock) { int readCount = 0; try { if (isBlocking()) { begin(); } - int offset = target.position(); - int length = target.remaining(); - if (target.isDirect()) { - int address = NioUtils.getDirectBufferAddress(target); - readCount = Platform.NETWORK.readDirect(fd, address + offset, length); - } else { - // target is assured to have array. - byte[] array = target.array(); - offset += target.arrayOffset(); - readCount = Platform.NETWORK.read(fd, array, offset, length); + readCount = IoBridge.recvfrom(true, fd, dst, 0, null, false); + if (readCount > 0) { + dst.position(dst.position() + readCount); } - return readCount; } finally { if (isBlocking()) { end(readCount > 0); } } + return readCount; } } @Override - public int write(ByteBuffer source) throws IOException { - if (source == null) { + public int write(ByteBuffer src) throws IOException { + if (src == null) { throw new NullPointerException(); } checkOpenConnected(); - if (!source.hasRemaining()) { + if (!src.hasRemaining()) { return 0; } - return writeImpl(source); + return writeImpl(src); } @Override @@ -388,33 +351,20 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { return written; } - /* - * Write the source. return the count of bytes written. - */ - private int writeImpl(ByteBuffer source) throws IOException { + private int writeImpl(ByteBuffer src) throws IOException { synchronized (writeLock) { - if (!source.hasRemaining()) { + if (!src.hasRemaining()) { return 0; } int writeCount = 0; try { - int pos = source.position(); - int length = source.remaining(); if (isBlocking()) { begin(); } - if (source.isDirect()) { - int address = NioUtils.getDirectBufferAddress(source); - writeCount = Platform.NETWORK.writeDirect(fd, address, pos, length); - } else if (source.hasArray()) { - pos += source.arrayOffset(); - writeCount = Platform.NETWORK.write(fd, source.array(), pos, length); - } else { - byte[] array = new byte[length]; - source.get(array); - writeCount = Platform.NETWORK.write(fd, array, 0, length); + writeCount = IoBridge.sendto(fd, src, 0, null, 0); + if (writeCount > 0) { + src.position(src.position() + writeCount); } - source.position(pos + writeCount); } finally { if (isBlocking()) { end(writeCount >= 0); @@ -486,15 +436,14 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { if (socket != null && !socket.isClosed()) { socket.close(); } else { - Platform.NETWORK.close(fd); + IoBridge.closeSocket(fd); } } } - @Override - protected void implConfigureBlocking(boolean blockMode) throws IOException { + @Override protected void implConfigureBlocking(boolean blocking) throws IOException { synchronized (blockingLock()) { - IoUtils.setBlocking(fd, blockMode); + IoUtils.setBlocking(fd, blocking); } } 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 8e98a02..ab76e8e 100644 --- a/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java +++ b/luni/src/main/java/java/nio/channels/spi/AbstractSelectableChannel.java @@ -250,15 +250,13 @@ public abstract class AbstractSelectableChannel extends SelectableChannel { } /** - * Implements the setting of the blocking mode. + * Implements the configuration of blocking/non-blocking mode. * - * @param blockingMode - * {@code true} for setting this channel's mode to blocking, - * {@code false} to set it to non-blocking. + * @param blocking true for blocking, false for non-blocking. * @throws IOException * if an I/O error occurs. */ - protected abstract void implConfigureBlocking(boolean blockingMode) throws IOException; + protected abstract void implConfigureBlocking(boolean blocking) throws IOException; /* * package private for deregister method in AbstractSelector. diff --git a/luni/src/main/java/java/security/KeyStore.java b/luni/src/main/java/java/security/KeyStore.java index 40f7f9d..c233a5b 100644 --- a/luni/src/main/java/java/security/KeyStore.java +++ b/luni/src/main/java/java/security/KeyStore.java @@ -1226,8 +1226,8 @@ public class KeyStore { // clone chain - this.chain = (Certificate[])chain.clone(); boolean isAllX509Certificates = true; // assert chain length > 0 - for(Certificate cert: chain){ - if(!(cert instanceof X509Certificate)){ + for (Certificate cert: chain) { + if (!(cert instanceof X509Certificate)) { isAllX509Certificates = false; break; } diff --git a/luni/src/main/java/java/sql/Date.java b/luni/src/main/java/java/sql/Date.java index adc35aa..2434fbd 100644 --- a/luni/src/main/java/java/sql/Date.java +++ b/luni/src/main/java/java/sql/Date.java @@ -32,6 +32,7 @@ package java.sql; * java.sql.Date} class are "normalized" to the time 00:00:00.000 GMT on the * date implied by the time value. */ +@FindBugsSuppressWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") public class Date extends java.util.Date { private static final long serialVersionUID = 1511598038487230103L; diff --git a/luni/src/main/java/java/util/AbstractList.java b/luni/src/main/java/java/util/AbstractList.java index f6e5903..3b522c0 100644 --- a/luni/src/main/java/java/util/AbstractList.java +++ b/luni/src/main/java/java/util/AbstractList.java @@ -27,8 +27,7 @@ package java.util; * * @since 1.2 */ -public abstract class AbstractList<E> extends AbstractCollection<E> implements - List<E> { +public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { /** * A counter for changes to the list. diff --git a/luni/src/main/java/java/util/EventObject.java b/luni/src/main/java/java/util/EventObject.java index e8a169f..058e7e8 100644 --- a/luni/src/main/java/java/util/EventObject.java +++ b/luni/src/main/java/java/util/EventObject.java @@ -30,9 +30,7 @@ public class EventObject implements Serializable { private static final long serialVersionUID = 5516075349620653480L; - /** - * The event source. - */ + @FindBugsSuppressWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") protected transient Object source; /** @@ -42,17 +40,14 @@ public class EventObject implements Serializable { * the object which fired the event. */ public EventObject(Object source) { - if (source != null) { - this.source = source; - } else { + if (source == null) { throw new IllegalArgumentException(); } + this.source = source; } /** - * Returns the event source. - * - * @return the object which fired the event. + * Returns the object which fired the event. */ public Object getSource() { return source; @@ -60,11 +55,8 @@ public class EventObject implements Serializable { /** * Returns the string representation of this {@code EventObject}. - * - * @return the string representation of this {@code EventObject}. */ - @Override - public String toString() { + @Override public String toString() { return getClass().getName() + "[source=" + source + ']'; } } diff --git a/luni/src/main/java/java/util/ResourceBundle.java b/luni/src/main/java/java/util/ResourceBundle.java index a34b34a..ff38b5b 100644 --- a/luni/src/main/java/java/util/ResourceBundle.java +++ b/luni/src/main/java/java/util/ResourceBundle.java @@ -24,6 +24,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.Charsets; +import static java.nio.charset.Charsets.UTF_8; import libcore.io.IoUtils; /** @@ -514,7 +516,7 @@ public abstract class ResourceBundle { : ClassLoader.getSystemResourceAsStream(fileName); if (stream != null) { try { - bundle = new PropertyResourceBundle(new InputStreamReader(stream)); + bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8)); bundle.setLocale(locale); } catch (IOException ignored) { } finally { diff --git a/luni/src/main/java/java/util/TreeMap.java b/luni/src/main/java/java/util/TreeMap.java index 8d878cd..7809110 100644 --- a/luni/src/main/java/java/util/TreeMap.java +++ b/luni/src/main/java/java/util/TreeMap.java @@ -1094,11 +1094,11 @@ public class TreeMap<K, V> extends AbstractMap<K, V> * A map with optional limits on its range. */ final class BoundedMap extends AbstractMap<K, V> implements NavigableMap<K, V>, Serializable { - private final boolean ascending; - private final K from; - private final Bound fromBound; - private final K to; - private final Bound toBound; + private final transient boolean ascending; + private final transient K from; + private final transient Bound fromBound; + private final transient K to; + private final transient Bound toBound; BoundedMap(boolean ascending, K from, Bound fromBound, K to, Bound toBound) { /* @@ -1375,8 +1375,8 @@ public class TreeMap<K, V> extends AbstractMap<K, V> * View factory methods. */ - private BoundedEntrySet entrySet; - private BoundedKeySet keySet; + private transient BoundedEntrySet entrySet; + private transient BoundedKeySet keySet; @Override public Set<Entry<K, V>> entrySet() { BoundedEntrySet result = entrySet; diff --git a/luni/src/main/java/java/util/TreeSet.java b/luni/src/main/java/java/util/TreeSet.java index 63a51c9..502329e 100644 --- a/luni/src/main/java/java/util/TreeSet.java +++ b/luni/src/main/java/java/util/TreeSet.java @@ -490,7 +490,7 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, (Comparator<? super E>) stream.readObject()); int size = stream.readInt(); if (size > 0) { - for(int i=0; i<size; i++) { + for (int i=0; i<size; i++) { E elem = (E)stream.readObject(); map.put(elem, Boolean.TRUE); } diff --git a/luni/src/main/java/java/util/UnsafeArrayList.java b/luni/src/main/java/java/util/UnsafeArrayList.java new file mode 100644 index 0000000..106e73e --- /dev/null +++ b/luni/src/main/java/java/util/UnsafeArrayList.java @@ -0,0 +1,58 @@ +/* + * 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.util; + +import java.lang.reflect.Array; + +/** + * An array-backed list that exposes its array. + * + * @hide + */ +public class UnsafeArrayList<T> extends AbstractList<T> { + private final Class<T> elementType; + private T[] array; + private int size; + + public UnsafeArrayList(Class<T> elementType, int initialCapacity) { + this.array = (T[]) Array.newInstance(elementType, initialCapacity); + this.elementType = elementType; + } + + @Override public boolean add(T element) { + if (size == array.length) { + T[] newArray = (T[]) Array.newInstance(elementType, size * 2); + System.arraycopy(array, 0, newArray, 0, size); + array = newArray; + } + array[size++] = element; + ++modCount; + return true; + } + + public T[] array() { + return array; + } + + public T get(int i) { + return array[i]; + } + + public int size() { + return size; + } +} diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java index a26a602..e129e82 100644 --- a/luni/src/main/java/java/util/jar/JarFile.java +++ b/luni/src/main/java/java/util/jar/JarFile.java @@ -289,11 +289,7 @@ public class JarFile extends ZipFile { try { InputStream is = super.getInputStream(manifestEntry); if (verifier != null) { - try { - verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is)); - } finally { - is.close(); - } + verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is)); is = super.getInputStream(manifestEntry); } try { @@ -344,11 +340,7 @@ public class JarFile extends ZipFile { || endsWithIgnoreCase(entryName, ".RSA"))) { signed = true; InputStream is = super.getInputStream(entry); - try { - verifier.addMetaEntry(entryName, Streams.readFully(is)); - } finally { - is.close(); - } + verifier.addMetaEntry(entryName, Streams.readFully(is)); } } } diff --git a/luni/src/main/java/java/util/jar/JarVerifier.java b/luni/src/main/java/java/util/jar/JarVerifier.java index 81bbe77..ec0e088 100644 --- a/luni/src/main/java/java/util/jar/JarVerifier.java +++ b/luni/src/main/java/java/util/jar/JarVerifier.java @@ -33,7 +33,7 @@ import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import java.util.Vector; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; import org.apache.harmony.security.utils.JarUtils; /** diff --git a/luni/src/main/java/java/util/prefs/AbstractPreferences.java b/luni/src/main/java/java/util/prefs/AbstractPreferences.java index 06bef54..d86d789 100644 --- a/luni/src/main/java/java/util/prefs/AbstractPreferences.java +++ b/luni/src/main/java/java/util/prefs/AbstractPreferences.java @@ -24,12 +24,11 @@ import java.util.Collection; import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeSet; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; import libcore.util.EmptyArray; /** @@ -193,8 +192,8 @@ public abstract class AbstractPreferences extends Preferences { checkState(); AbstractPreferences result = null; String[] childrenNames = childrenNames(); - for (int i = 0; i < childrenNames.length; i++) { - if (childrenNames[i].equals(name)) { + for (String childrenName : childrenNames) { + if (childrenName.equals(name)) { result = childSpi(name); break; } @@ -364,9 +363,8 @@ public abstract class AbstractPreferences extends Preferences { @Override public void clear() throws BackingStoreException { synchronized (lock) { - String[] keyList = keys(); - for (int i = 0; i < keyList.length; i++) { - remove(keyList[i]); + for (String key : keys()) { + remove(key); } } } @@ -562,21 +560,18 @@ public abstract class AbstractPreferences extends Preferences { throws BackingStoreException { String[] names = path.split("/"); AbstractPreferences currentNode = this; - AbstractPreferences temp = null; - if (currentNode != null) { - for (int i = 0; i < names.length; i++) { - String name = names[i]; - synchronized (currentNode.lock) { - temp = currentNode.cachedNode.get(name); - if (temp == null) { - temp = getNodeFromBackend(createNew, currentNode, name); - } - } - currentNode = temp; - if (currentNode == null) { - break; + AbstractPreferences temp; + for (String name : names) { + synchronized (currentNode.lock) { + temp = currentNode.cachedNode.get(name); + if (temp == null) { + temp = getNodeFromBackend(createNew, currentNode, name); } } + currentNode = temp; + if (currentNode == null) { + break; + } } return currentNode; } @@ -660,37 +655,32 @@ public abstract class AbstractPreferences extends Preferences { @Override public void putBoolean(String key, boolean value) { - String sval = String.valueOf(value); - put(key, sval); + put(key, String.valueOf(value)); } @Override public void putByteArray(String key, byte[] value) { - put(key, Base64.encode(value, Charsets.US_ASCII)); + put(key, Base64.encode(value)); } @Override public void putDouble(String key, double value) { - String sval = Double.toString(value); - put(key, sval); + put(key, Double.toString(value)); } @Override public void putFloat(String key, float value) { - String sval = Float.toString(value); - put(key, sval); + put(key, Float.toString(value)); } @Override public void putInt(String key, int value) { - String sval = Integer.toString(value); - put(key, sval); + put(key, Integer.toString(value)); } @Override public void putLong(String key, long value) { - String sval = Long.toString(value); - put(key, sval); + put(key, Long.toString(value)); } @Override @@ -716,10 +706,10 @@ public abstract class AbstractPreferences extends Preferences { synchronized (lock) { checkState(); String[] childrenNames = childrenNamesSpi(); - for (int i = 0; i < childrenNames.length; i++) { - if (cachedNode.get(childrenNames[i]) == null) { - AbstractPreferences child = childSpi(childrenNames[i]); - cachedNode.put(childrenNames[i], child); + for (String childrenName : childrenNames) { + if (cachedNode.get(childrenName) == null) { + AbstractPreferences child = childSpi(childrenName); + cachedNode.put(childrenName, child); } } @@ -831,15 +821,14 @@ public abstract class AbstractPreferences extends Preferences { @Override public void run() { while (true) { - EventObject event = null; + EventObject event; try { event = getEventObject(); } catch (InterruptedException e) { e.printStackTrace(); continue; } - AbstractPreferences pref = (AbstractPreferences) event - .getSource(); + AbstractPreferences pref = (AbstractPreferences) event.getSource(); if (event instanceof NodeAddEvent) { dispatchNodeAdd((NodeChangeEvent) event, pref.nodeChangeListeners); @@ -867,11 +856,8 @@ public abstract class AbstractPreferences extends Preferences { private void dispatchPrefChange(PreferenceChangeEvent event, List<EventListener> preferenceChangeListeners) { synchronized (preferenceChangeListeners) { - Iterator<EventListener> i = preferenceChangeListeners.iterator(); - while (i.hasNext()) { - PreferenceChangeListener pcl = (PreferenceChangeListener) i - .next(); - pcl.preferenceChange(event); + for (EventListener preferenceChangeListener : preferenceChangeListeners) { + ((PreferenceChangeListener) preferenceChangeListener).preferenceChange(event); } } } @@ -879,10 +865,8 @@ public abstract class AbstractPreferences extends Preferences { private void dispatchNodeRemove(NodeChangeEvent event, List<EventListener> nodeChangeListeners) { synchronized (nodeChangeListeners) { - Iterator<EventListener> i = nodeChangeListeners.iterator(); - while (i.hasNext()) { - NodeChangeListener ncl = (NodeChangeListener) i.next(); - ncl.childRemoved(event); + for (EventListener nodeChangeListener : nodeChangeListeners) { + ((NodeChangeListener) nodeChangeListener).childRemoved(event); } } } @@ -890,9 +874,8 @@ public abstract class AbstractPreferences extends Preferences { private void dispatchNodeAdd(NodeChangeEvent event, List<EventListener> nodeChangeListeners) { synchronized (nodeChangeListeners) { - Iterator<EventListener> i = nodeChangeListeners.iterator(); - while (i.hasNext()) { - NodeChangeListener ncl = (NodeChangeListener) i.next(); + for (EventListener nodeChangeListener : nodeChangeListeners) { + NodeChangeListener ncl = (NodeChangeListener) nodeChangeListener; ncl.childAdded(event); } } diff --git a/luni/src/main/java/java/util/prefs/XMLParser.java b/luni/src/main/java/java/util/prefs/XMLParser.java index 6546b76..6e25c7d 100644 --- a/luni/src/main/java/java/util/prefs/XMLParser.java +++ b/luni/src/main/java/java/util/prefs/XMLParser.java @@ -405,7 +405,7 @@ class XMLParser { NodeList childNodes = documentElement.getChildNodes(); if(path[0].equals("entry") || path[0].equals("node")) { - for(int i = 0; i < childNodes.getLength(); i++) { + for (int i = 0; i < childNodes.getLength(); i++) { Object next = childNodes.item(i); if(next instanceof Element) { if(((Element) next).getNodeName().equals(path[0])) { @@ -414,12 +414,12 @@ class XMLParser { } } } else if(path[0].equals("map") && path[1].equals("entry")) { - for(int i = 0; i < childNodes.getLength(); i++) { + for (int i = 0; i < childNodes.getLength(); i++) { Object next = childNodes.item(i); if(next instanceof Element) { if(((Element) next).getNodeName().equals(path[0])) { NodeList nextChildNodes = ((Node)next).getChildNodes(); - for(int j = 0; j < nextChildNodes.getLength(); j++) { + for (int j = 0; j < nextChildNodes.getLength(); j++) { Object subnext = nextChildNodes.item(j); if(subnext instanceof Element) { if(((Element)subnext).getNodeName().equals(path[1])) { diff --git a/luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java b/luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java new file mode 100644 index 0000000..62eec24 --- /dev/null +++ b/luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java @@ -0,0 +1,26 @@ +/* + * 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.io.FileDescriptor; + +public final class AsynchronousCloseMonitor { + private AsynchronousCloseMonitor() { + } + + public static native void signalBlockedThreads(FileDescriptor fd); +} diff --git a/luni/src/main/java/org/apache/harmony/luni/util/Base64.java b/luni/src/main/java/libcore/io/Base64.java index 4c9b509..e4643b5 100644 --- a/luni/src/main/java/org/apache/harmony/luni/util/Base64.java +++ b/luni/src/main/java/libcore/io/Base64.java @@ -19,16 +19,16 @@ * @author Alexander Y. Kleymenov */ -package org.apache.harmony.luni.util; +package libcore.io; -import java.nio.charset.Charset; +import java.nio.charset.Charsets; import libcore.util.EmptyArray; /** - * This class implements Base64 encoding/decoding functionality - * as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt). + * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder. + * In violation of the RFC, this encoder doesn't wrap lines at 76 columns. */ -public class Base64 { +public final class Base64 { private Base64() { } @@ -132,22 +132,15 @@ public class Base64 { 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; - public static String encode(byte[] in, Charset charset) { - int length = in.length * 4 / 3; - length += length / 76 + 3; // for crlr + public static String encode(byte[] in) { + int length = (in.length + 2) * 4 / 3; byte[] out = new byte[length]; - int index = 0, i, crlr = 0, end = in.length - in.length%3; - for (i=0; i<end; i+=3) { + int index = 0, end = in.length - in.length % 3; + for (int i = 0; i < end; i += 3) { out[index++] = map[(in[i] & 0xff) >> 2]; out[index++] = map[((in[i] & 0x03) << 4) | ((in[i+1] & 0xff) >> 4)]; out[index++] = map[((in[i+1] & 0x0f) << 2) | ((in[i+2] & 0xff) >> 6)]; out[index++] = map[(in[i+2] & 0x3f)]; - if (((index - crlr)%76 == 0) && (index != 0)) { - out[index++] = '\n'; - crlr++; - //out[index++] = '\r'; - //crlr++; - } } switch (in.length % 3) { case 1: @@ -163,6 +156,6 @@ public class Base64 { out[index++] = '='; break; } - return new String(out, 0, index, charset); + return new String(out, 0, index, Charsets.US_ASCII); } } diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java index cda6a58..e77e0bb 100644 --- a/luni/src/main/java/libcore/io/BlockGuardOs.java +++ b/luni/src/main/java/libcore/io/BlockGuardOs.java @@ -18,6 +18,8 @@ package libcore.io; import dalvik.system.BlockGuard; import java.io.FileDescriptor; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import static libcore.io.OsConstants.*; @@ -30,26 +32,65 @@ public class BlockGuardOs extends ForwardingOs { super(os); } - @Override - public void fdatasync(FileDescriptor fd) throws ErrnoException { + private FileDescriptor tagSocket(FileDescriptor fd) { + try { + BlockGuard.tagSocketFd(fd); + return fd; + } catch (SocketException e) { + throw new ErrnoException("socket", EINVAL, e); + } + } + + @Override public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException { + BlockGuard.getThreadPolicy().onNetwork(); + return tagSocket(os.accept(fd, peerAddress)); + } + + @Override public void close(FileDescriptor fd) throws ErrnoException { + // TODO: is there a way to avoid calling getsockopt(2) on non-socket fds? + if (isLingerSocket(fd)) { + // If the fd is a socket with SO_LINGER set, we might block indefinitely. + // We allow non-linger sockets so that apps can close their network connections in + // methods like onDestroy which will run on the UI thread. + BlockGuard.getThreadPolicy().onNetwork(); + } + os.close(fd); + } + + private static boolean isLingerSocket(FileDescriptor fd) { + try { + StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER); + return linger.isOn() && linger.l_linger > 0; + } catch (ErrnoException ignored) { + // We're called via Socket.close (which doesn't ask for us to be called), so we + // must not throw here, because Socket.close must not throw if asked to close an + // already-closed socket. Also, the passed-in FileDescriptor isn't necessarily + // a socket at all. + return false; + } + } + + @Override public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException { + BlockGuard.getThreadPolicy().onNetwork(); + os.connect(fd, address, port); + } + + @Override public void fdatasync(FileDescriptor fd) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); os.fdatasync(fd); } - @Override - public void fsync(FileDescriptor fd) throws ErrnoException { + @Override public void fsync(FileDescriptor fd) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); os.fsync(fd); } - @Override - public void ftruncate(FileDescriptor fd, long length) throws ErrnoException { + @Override public void ftruncate(FileDescriptor fd, long length) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); os.ftruncate(fd, length); } - @Override - public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { + @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { BlockGuard.getThreadPolicy().onReadFromDisk(); if ((mode & O_ACCMODE) != O_RDONLY) { BlockGuard.getThreadPolicy().onWriteToDisk(); @@ -57,49 +98,68 @@ public class BlockGuardOs extends ForwardingOs { return os.open(path, flags, mode); } - @Override - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + @Override public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { + // Greater than 0 is a timeout in milliseconds and -1 means "block forever", + // but 0 means "poll and return immediately", which shouldn't be subject to BlockGuard. + if (timeoutMs != 0) { + BlockGuard.getThreadPolicy().onNetwork(); + } + return os.poll(fds, timeoutMs); + } + + @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.read(fd, buffer); } - @Override - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.read(fd, bytes, byteOffset, byteCount); } - @Override - public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { + @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.readv(fd, buffers, offsets, byteCounts); } - @Override - public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { - final FileDescriptor fd = os.socket(domain, type, protocol); - try { - BlockGuard.tagSocketFd(fd); - } catch (SocketException e) { - throw new ErrnoException("socket", EINVAL, e); + @Override public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException { + BlockGuard.getThreadPolicy().onNetwork(); + return os.recvfrom(fd, buffer, flags, srcAddress); + } + + @Override public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException { + BlockGuard.getThreadPolicy().onNetwork(); + return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); + } + + @Override public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException { + BlockGuard.getThreadPolicy().onNetwork(); + return os.sendto(fd, buffer, flags, inetAddress, port); + } + + @Override public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException { + // We permit datagrams without hostname lookups. + if (inetAddress != null) { + BlockGuard.getThreadPolicy().onNetwork(); } - return fd; + return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); + } + + @Override public FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { + return tagSocket(os.socket(domain, type, protocol)); } - @Override - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.write(fd, buffer); } - @Override - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.write(fd, bytes, byteOffset, byteCount); } - @Override - public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { + @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.writev(fd, buffers, offsets, byteCounts); } diff --git a/luni/src/main/java/libcore/io/DiskLruCache.java b/luni/src/main/java/libcore/io/DiskLruCache.java new file mode 100644 index 0000000..c887541 --- /dev/null +++ b/luni/src/main/java/libcore/io/DiskLruCache.java @@ -0,0 +1,804 @@ +/* + * 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.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * A cache that uses a bounded amount of space on a filesystem. Each cache + * entry has a string key and a fixed number of values. Values are byte + * sequences, accessible as streams or files. Each value must be between {@code + * 0} and {@code Integer.MAX_VALUE} bytes in length. + * + * <p>The cache stores its data in a directory on the filesystem. This + * directory must be exclusive to the cache; the cache may delete or overwrite + * files from its directory. It is an error for multiple processes to use the + * same cache directory at the same time. + * + * <p>This cache limits the number of bytes that it will store on the + * filesystem. When the number of stored bytes exceeds the limit, the cache will + * remove entries in the background until the limit is satisfied. The limit is + * not strict: the cache may temporarily exceed it while waiting for files to be + * deleted. The limit does not include filesystem overhead or the cache + * journal so space-sensitive applications should set a conservative limit. + * + * <p>Clients call {@link #edit} to create or update the values of an entry. An + * entry may have only one editor at one time; if a value is not available to be + * edited then {@link #edit} will return null. + * <ul> + * <li>When an entry is being <strong>created</strong> it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + * <li>When an entry is being <strong>created</strong>, it is not necessary + * to supply data for every value; values default to their previous + * value. + * </ul> + * Every {@link #edit} call must be matched by a call to {@link Editor#commit} + * or {@link Editor#abort}. Committing is atomic: a read observes the full set + * of values as they were before or after the commit, but never a mix of values. + * + * <p>Clients call {@link #get} to read a snapshot of an entry. The read will + * observe the value at the time that {@link #get} was called. Updates and + * removals after the call do not impact ongoing reads. + * + * <p>This class is tolerant of some I/O errors. If files are missing from the + * filesystem, the corresponding entries will be dropped from the cache. If + * an error occurs while writing a cache value, the edit will fail silently. + * Callers should handle other problems by catching {@code IOException} and + * responding appropriately. + */ +public final class DiskLruCache implements Closeable { + static final String JOURNAL_FILE = "journal"; + static final String JOURNAL_FILE_TMP = "journal.tmp"; + static final String MAGIC = "libcore.io.DiskLruCache"; + static final String VERSION_1 = "1"; + private static final String CLEAN = "CLEAN"; + private static final String DIRTY = "DIRTY"; + private static final String REMOVE = "REMOVE"; + private static final String READ = "READ"; + + /* + * This cache uses a journal file named "journal". A typical journal file + * looks like this: + * libcore.io.DiskLruCache + * 1 + * 100 + * 2 + * + * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 + * DIRTY 335c4c6028171cfddfbaae1a9c313c52 + * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 + * REMOVE 335c4c6028171cfddfbaae1a9c313c52 + * DIRTY 1ab96a171faeeee38496d8b330771a7a + * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 + * READ 335c4c6028171cfddfbaae1a9c313c52 + * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 + * + * The first five lines of the journal form its header. They are the + * constant string "libcore.io.DiskLruCache", the disk cache's version, + * the application's version, the value count, and a blank line. + * + * Each of the subsequent lines in the file is a record of the state of a + * cache entry. Each line contains space-separated values: a state, a key, + * and optional state-specific values. + * o DIRTY lines track that an entry is actively being created or updated. + * Every successful DIRTY action should be followed by a CLEAN or REMOVE + * action. DIRTY lines without a matching CLEAN or REMOVE indicate that + * temporary files may need to be deleted. + * o CLEAN lines track a cache entry that has been successfully published + * and may be read. A publish line is followed by the lengths of each of + * its values. + * o READ lines track accesses for LRU. + * o REMOVE lines track entries that have been deleted. + * + * The journal file is appended to as cache operations occur. The journal may + * occasionally be compacted by dropping redundant lines. A temporary file named + * "journal.tmp" will be used during compaction; that file should be deleted if + * it exists when the cache is opened. + */ + + private final File directory; + private final File journalFile; + private final File journalFileTmp; + private final int appVersion; + private final long maxSize; + private final int valueCount; + private long size = 0; + private Writer journalWriter; + private final LinkedHashMap<String, Entry> lruEntries + = new LinkedHashMap<String, Entry>(0, 0.75f, true); + private int redundantOpCount; + + /** This cache uses a single background thread to evict entries. */ + private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, + 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); + private final Callable<Void> cleanupCallable = new Callable<Void>() { + @Override public Void call() throws Exception { + synchronized (DiskLruCache.this) { + if (journalWriter == null) { + return null; // closed + } + trimToSize(); + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } + return null; + } + }; + + private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { + this.directory = directory; + this.appVersion = appVersion; + this.journalFile = new File(directory, JOURNAL_FILE); + this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); + this.valueCount = valueCount; + this.maxSize = maxSize; + } + + /** + * Opens the cache in {@code directory}, creating a cache if none exists + * there. + * + * @param directory a writable directory + * @param appVersion + * @param valueCount the number of values per cache entry. Must be positive. + * @param maxSize the maximum number of bytes this cache should use to store + * @throws IOException if reading or writing the cache directory fails + */ + public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) + throws IOException { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + if (valueCount <= 0) { + throw new IllegalArgumentException("valueCount <= 0"); + } + + // prefer to pick up where we left off + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + if (cache.journalFile.exists()) { + try { + cache.readJournal(); + cache.processJournal(); + cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true)); + return cache; + } catch (IOException journalIsCorrupt) { + System.logW("DiskLruCache " + directory + " is corrupt: " + + journalIsCorrupt.getMessage() + ", removing"); + cache.delete(); + } + } + + // create a new empty cache + directory.mkdirs(); + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + cache.rebuildJournal(); + return cache; + } + + private void readJournal() throws IOException { + InputStream in = new BufferedInputStream(new FileInputStream(journalFile)); + try { + String magic = Streams.readAsciiLine(in); + String version = Streams.readAsciiLine(in); + String appVersionString = Streams.readAsciiLine(in); + String valueCountString = Streams.readAsciiLine(in); + String blank = Streams.readAsciiLine(in); + if (!MAGIC.equals(magic) + || !VERSION_1.equals(version) + || !Integer.toString(appVersion).equals(appVersionString) + || !Integer.toString(valueCount).equals(valueCountString) + || !"".equals(blank)) { + throw new IOException("unexpected journal header: [" + + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); + } + + while (true) { + try { + readJournalLine(Streams.readAsciiLine(in)); + } catch (EOFException endOfJournal) { + break; + } + } + } finally { + IoUtils.closeQuietly(in); + } + } + + private void readJournalLine(String line) throws IOException { + String[] parts = line.split(" "); + if (parts.length < 2) { + throw new IOException("unexpected journal line: " + line); + } + + String key = parts[1]; + if (parts[0].equals(REMOVE) && parts.length == 2) { + lruEntries.remove(key); + return; + } + + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } + + if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { + entry.readable = true; + entry.currentEditor = null; + entry.setLengths(Arrays.copyOfRange(parts, 2, parts.length)); + } else if (parts[0].equals(DIRTY) && parts.length == 2) { + entry.currentEditor = new Editor(entry); + } else if (parts[0].equals(READ) && parts.length == 2) { + // this work was already done by calling lruEntries.get() + } else { + throw new IOException("unexpected journal line: " + line); + } + } + + /** + * Computes the initial size and collects garbage as a part of opening the + * cache. Dirty entries are assumed to be inconsistent and will be deleted. + */ + private void processJournal() throws IOException { + deleteIfExists(journalFileTmp); + for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { + Entry entry = i.next(); + if (entry.currentEditor == null) { + for (int t = 0; t < valueCount; t++) { + size += entry.lengths[t]; + } + } else { + entry.currentEditor = null; + for (int t = 0; t < valueCount; t++) { + deleteIfExists(entry.getCleanFile(t)); + deleteIfExists(entry.getDirtyFile(t)); + } + i.remove(); + } + } + } + + /** + * Creates a new journal that omits redundant information. This replaces the + * current journal if it exists. + */ + private synchronized void rebuildJournal() throws IOException { + if (journalWriter != null) { + journalWriter.close(); + } + + Writer writer = new BufferedWriter(new FileWriter(journalFileTmp)); + writer.write(MAGIC); + writer.write("\n"); + writer.write(VERSION_1); + writer.write("\n"); + writer.write(Integer.toString(appVersion)); + writer.write("\n"); + writer.write(Integer.toString(valueCount)); + writer.write("\n"); + writer.write("\n"); + + for (Entry entry : lruEntries.values()) { + if (entry.currentEditor != null) { + writer.write(DIRTY + ' ' + entry.key + '\n'); + } else { + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } + } + + writer.close(); + journalFileTmp.renameTo(journalFile); + journalWriter = new BufferedWriter(new FileWriter(journalFile, true)); + } + + private static void deleteIfExists(File file) throws IOException { + try { + Libcore.os.remove(file.getPath()); + } catch (ErrnoException e) { + if (e.errno != OsConstants.ENOENT) { + throw e; + } + } + } + + /** + * Returns a snapshot of the entry named {@code key}, or null if it doesn't + * exist is not currently readable. If a value is returned, it is moved to + * the head of the LRU queue. + */ + public synchronized Snapshot get(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null) { + return null; + } + + if (!entry.readable) { + return null; + } + + /* + * Open all streams eagerly to guarantee that we see a single published + * snapshot. If we opened streams lazily then the streams could come + * from different edits. + */ + InputStream[] ins = new InputStream[valueCount]; + try { + for (int i = 0; i < valueCount; i++) { + ins[i] = new FileInputStream(entry.getCleanFile(i)); + } + } catch (FileNotFoundException e) { + // a file must have been deleted manually! + return null; + } + + redundantOpCount++; + journalWriter.append(READ + ' ' + key + '\n'); + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return new Snapshot(ins); + } + + /** + * Returns an editor for the entry named {@code key}, or null if it cannot + * currently be edited. + */ + public synchronized Editor edit(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } else if (entry.currentEditor != null) { + return null; + } + + Editor editor = new Editor(entry); + entry.currentEditor = editor; + + // flush the journal before creating files to prevent file leaks + journalWriter.write(DIRTY + ' ' + key + '\n'); + journalWriter.flush(); + return editor; + } + + /** + * Returns the directory where this cache stores its data. + */ + public File getDirectory() { + return directory; + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public long maxSize() { + return maxSize; + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the max size if a background + * deletion is pending. + */ + public synchronized long size() { + return size; + } + + private synchronized void completeEdit(Editor editor, boolean success) throws IOException { + Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // if this edit is creating the entry for the first time, every index must have a value + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!entry.getDirtyFile(i).exists()) { + editor.abort(); + throw new IllegalStateException("edit didn't create file " + i); + } + } + } + + for (int i = 0; i < valueCount; i++) { + File dirty = entry.getDirtyFile(i); + if (success) { + if (dirty.exists()) { + File clean = entry.getCleanFile(i); + dirty.renameTo(clean); + long oldLength = entry.lengths[i]; + long newLength = clean.length(); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + } + } else { + deleteIfExists(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } else { + lruEntries.remove(entry.key); + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); + } + + if (size > maxSize || journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + } + + /** + * We only rebuild the journal when it will halve the size of the journal + * and eliminate at least 2000 ops. + */ + private boolean journalRebuildRequired() { + final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; + return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD + && redundantOpCount >= lruEntries.size(); + } + + /** + * Drops the entry for {@code key} if it exists and can be removed. Entries + * actively being edited cannot be removed. + * + * @return true if an entry was removed. + */ + public synchronized boolean remove(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null || entry.currentEditor != null) { + return false; + } + + for (int i = 0; i < valueCount; i++) { + File file = entry.getCleanFile(i); + if (!file.delete()) { + throw new IOException("failed to delete " + file); + } + size -= entry.lengths[i]; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.append(REMOVE + ' ' + key + '\n'); + lruEntries.remove(key); + + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return true; + } + + /** + * Returns true if this cache has been closed. + */ + public boolean isClosed() { + return journalWriter == null; + } + + private void checkNotClosed() { + if (journalWriter == null) { + throw new IllegalStateException("cache is closed"); + } + } + + /** + * Force buffered operations to the filesystem. + */ + public synchronized void flush() throws IOException { + checkNotClosed(); + trimToSize(); + journalWriter.flush(); + } + + /** + * Closes this cache. Stored values will remain on the filesystem. + */ + public synchronized void close() throws IOException { + if (journalWriter == null) { + return; // already closed + } + for (Entry entry : new ArrayList<Entry>(lruEntries.values())) { + if (entry.currentEditor != null) { + entry.currentEditor.abort(); + } + } + trimToSize(); + journalWriter.close(); + journalWriter = null; + } + + private void trimToSize() throws IOException { + while (size > maxSize) { + Map.Entry<String, Entry> toEvict = lruEntries.eldest(); + remove(toEvict.getKey()); + } + } + + /** + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. + */ + public void delete() throws IOException { + close(); + IoUtils.deleteContents(directory); + } + + private void validateKey(String key) { + if (key.contains(" ") || key.contains("\n") || key.contains("\r")) { + throw new IllegalArgumentException( + "keys must not contain spaces or newlines: \"" + key + "\""); + } + } + + private static String inputStreamToString(InputStream in) throws IOException { + return Streams.readFully(new InputStreamReader(in, Charsets.UTF_8)); + } + + /** + * A snapshot of the values for an entry. + */ + public static final class Snapshot implements Closeable { + private final InputStream[] ins; + + private Snapshot(InputStream[] ins) { + this.ins = ins; + } + + /** + * Returns the unbuffered stream with the value for {@code index}. + */ + public InputStream getInputStream(int index) { + return ins[index]; + } + + /** + * Returns the string value for {@code index}. + */ + public String getString(int index) throws IOException { + return inputStreamToString(getInputStream(index)); + } + + @Override public void close() { + for (InputStream in : ins) { + IoUtils.closeQuietly(in); + } + } + } + + /** + * Edits the values for an entry. + */ + public final class Editor { + private final Entry entry; + private boolean hasErrors; + + private Editor(Entry entry) { + this.entry = entry; + } + + /** + * Returns an unbuffered input stream to read the last committed value, + * or null if no value has been committed. + */ + public InputStream newInputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + return null; + } + return new FileInputStream(entry.getCleanFile(index)); + } + } + + /** + * Returns the last committed value as a string, or null if no value + * has been committed. + */ + public String getString(int index) throws IOException { + InputStream in = newInputStream(index); + return in != null ? inputStreamToString(in) : null; + } + + /** + * Returns a new unbuffered output stream to write the value at + * {@code index}. If the underlying output stream encounters errors + * when writing to the filesystem, this edit will be aborted when + * {@link #commit} is called. The returned output stream does not throw + * IOExceptions. + */ + public OutputStream newOutputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); + } + } + + /** + * Sets the value at {@code index} to {@code value}. + */ + public void set(int index, String value) throws IOException { + Writer writer = null; + try { + writer = new OutputStreamWriter(newOutputStream(index), Charsets.UTF_8); + writer.write(value); + } finally { + IoUtils.closeQuietly(writer); + } + } + + /** + * Commits this edit so it is visible to readers. This releases the + * edit lock so another edit may be started on the same key. + */ + public void commit() throws IOException { + if (hasErrors) { + completeEdit(this, false); + remove(entry.key); // the previous entry is stale + } else { + completeEdit(this, true); + } + } + + /** + * Aborts this edit. This releases the edit lock so another edit may be + * started on the same key. + */ + public void abort() throws IOException { + completeEdit(this, false); + } + + private class FaultHidingOutputStream extends FilterOutputStream { + private FaultHidingOutputStream(OutputStream out) { + super(out); + } + + @Override public void write(int oneByte) { + try { + out.write(oneByte); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void write(byte[] buffer, int offset, int length) { + try { + out.write(buffer, offset, length); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void close() { + try { + out.close(); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void flush() { + try { + out.flush(); + } catch (IOException e) { + hasErrors = true; + } + } + } + } + + private final class Entry { + private final String key; + + /** Lengths of this entry's files. */ + private final long[] lengths; + + /** True if this entry has ever been published */ + private boolean readable; + + /** The ongoing edit or null if this entry is not being edited. */ + private Editor currentEditor; + + private Entry(String key) { + this.key = key; + this.lengths = new long[valueCount]; + } + + public String getLengths() throws IOException { + StringBuilder result = new StringBuilder(); + for (long size : lengths) { + result.append(' ').append(size); + } + return result.toString(); + } + + /** + * Set lengths using decimal numbers like "10123". + */ + private void setLengths(String[] strings) throws IOException { + if (strings.length != valueCount) { + throw invalidLengths(strings); + } + + try { + for (int i = 0; i < strings.length; i++) { + lengths[i] = Long.parseLong(strings[i]); + } + } catch (NumberFormatException e) { + throw invalidLengths(strings); + } + } + + private IOException invalidLengths(String[] strings) throws IOException { + throw new IOException("unexpected journal line: " + Arrays.toString(strings)); + } + + public File getCleanFile(int i) { + return new File(directory, key + "." + i); + } + + public File getDirtyFile(int i) { + return new File(directory, key + "." + i + ".tmp"); + } + } +}
\ No newline at end of file diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java index 9e2b2c9..f205994 100644 --- a/luni/src/main/java/libcore/io/ForwardingOs.java +++ b/luni/src/main/java/libcore/io/ForwardingOs.java @@ -18,6 +18,7 @@ package libcore.io; import java.io.FileDescriptor; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import libcore.util.MutableInt; @@ -33,9 +34,14 @@ public class ForwardingOs implements Os { this.os = os; } + public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException { return os.accept(fd, peerAddress); } public boolean access(String path, int mode) throws ErrnoException { return os.access(path, mode); } + public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException { os.bind(fd, address, port); } public void chmod(String path, int mode) throws ErrnoException { os.chmod(path, mode); } public void close(FileDescriptor fd) throws ErrnoException { os.close(fd); } + public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException { os.connect(fd, address, port); } + public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return os.dup(oldFd); } + public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return os.dup2(oldFd, newFd); } public String[] environ() { return os.environ(); } public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return os.fcntlVoid(fd, cmd); } public int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException { return os.fcntlLong(fd, cmd, arg); } @@ -47,14 +53,22 @@ public class ForwardingOs implements Os { 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 int getegid() { return os.getegid(); } + public int geteuid() { return os.geteuid(); } + public int getgid() { return os.getgid(); } public String getenv(String name) { return os.getenv(name); } public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); } + public int getpid() { return os.getpid(); } + public int getppid() { return os.getppid(); } + public StructPasswd getpwnam(String name) throws ErrnoException { return os.getpwnam(name); } + public StructPasswd getpwuid(int uid) throws ErrnoException { return os.getpwuid(uid); } 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 int getuid() { return os.getuid(); } 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); } @@ -73,12 +87,20 @@ public class ForwardingOs implements Os { public void munmap(long address, long byteCount) throws ErrnoException { os.munmap(address, byteCount); } public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return os.open(path, flags, mode); } public FileDescriptor[] pipe() throws ErrnoException { return os.pipe(); } + public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); } public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.read(fd, buffer); } public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.read(fd, bytes, byteOffset, byteCount); } public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.readv(fd, buffers, offsets, byteCounts); } + public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException { return os.recvfrom(fd, buffer, flags, srcAddress); } + public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); } 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 int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException { return os.sendto(fd, buffer, flags, inetAddress, port); } + public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException { return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); } + public void setegid(int egid) throws ErrnoException { os.setegid(egid); } + public void seteuid(int euid) throws ErrnoException { os.seteuid(euid); } + public void setgid(int gid) throws ErrnoException { os.setgid(gid); } 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); } @@ -86,6 +108,7 @@ public class ForwardingOs implements Os { 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 setuid(int uid) throws ErrnoException { os.setuid(uid); } 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); } @@ -94,6 +117,7 @@ public class ForwardingOs implements Os { public void symlink(String oldPath, String newPath) throws ErrnoException { os.symlink(oldPath, newPath); } public long sysconf(int name) { return os.sysconf(name); } public StructUtsname uname() throws ErrnoException { return os.uname(); } + public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); } public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); } public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); } public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.writev(fd, buffers, offsets, byteCounts); } diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java new file mode 100644 index 0000000..c26b565 --- /dev/null +++ b/luni/src/main/java/libcore/io/IoBridge.java @@ -0,0 +1,593 @@ +/* + * 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.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.BindException; +import java.net.ConnectException; +import java.net.DatagramPacket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.PortUnreachableException; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketOptions; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.util.MutableInt; +import static libcore.io.OsConstants.*; + +/** + * Implements java.io/java.net/java.nio semantics in terms of the underlying POSIX system calls. + */ +public final class IoBridge { + + private IoBridge() { + } + + public static int available(FileDescriptor fd) throws IOException { + try { + MutableInt available = new MutableInt(0); + int rc = Libcore.os.ioctlInt(fd, FIONREAD, available); + if (available.value < 0) { + // If the fd refers to a regular file, the result is the difference between + // the file size and the file position. This may be negative if the position + // is past the end of the file. If the fd refers to a special file masquerading + // as a regular file, the result may be negative because the special file + // may appear to have zero size and yet a previous read call may have + // read some amount of data and caused the file position to be advanced. + available.value = 0; + } + return available.value; + } catch (ErrnoException errnoException) { + if (errnoException.errno == ENOTTY) { + // The fd is unwilling to opine about its read buffer. + return 0; + } + throw errnoException.rethrowAsIOException(); + } + } + + + public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException { + if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) { + // Linux won't let you bind a link-local address without a scope id. Find one. + NetworkInterface nif = NetworkInterface.getByInetAddress(address); + if (nif == null) { + throw new SocketException("Can't bind to a link-local address without a scope id: " + address); + } + try { + address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex()); + } catch (UnknownHostException ex) { + throw new AssertionError(ex); // Can't happen. + } + } + try { + Libcore.os.bind(fd, address, port); + } catch (ErrnoException errnoException) { + throw new BindException(errnoException.getMessage(), errnoException); + } + } + + + /** + * 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 IoBridge.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 (ErrnoException errnoException) { + throw new ConnectException(connectDetail(inetAddress, port, timeoutMs, errnoException), errnoException); + } 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); + } + } + + 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) { + Libcore.os.connect(fd, inetAddress, port); + return true; + } + + // With a timeout, we set the socket to non-blocking, connect(2), and then loop + // using poll(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 { + try { + Libcore.os.connect(fd, inetAddress, port); + return true; // We connected immediately. + } catch (ErrnoException errnoException) { + if (errnoException.errno != EINPROGRESS) { + throw errnoException; + } + // EINPROGRESS means we should keep trying... + } + int remainingTimeoutMs; + do { + remainingTimeoutMs = (int) (finishTimeMs - System.currentTimeMillis()); + if (remainingTimeoutMs <= 0) { + throw new SocketTimeoutException(connectDetail(inetAddress, port, timeoutMs, null)); + } + } while (!IoBridge.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs)); + return true; // Or we'd have thrown. + } finally { + IoUtils.setBlocking(fd, true); + } + } + + private static String connectDetail(InetAddress inetAddress, int port, int timeoutMs, ErrnoException cause) { + String detail = "failed to connect to " + inetAddress + " (port " + port + ")"; + if (timeoutMs > 0) { + detail += " after " + timeoutMs + "ms"; + } + if (cause != null) { + detail += ": " + cause.getMessage(); + } + return detail; + } + + public static void closeSocket(FileDescriptor fd) throws IOException { + if (!fd.valid()) { + // Socket.close doesn't throw if you try to close an already-closed socket. + return; + } + int intFd = fd.getInt$(); + fd.setInt$(-1); + FileDescriptor oldFd = new FileDescriptor(); + oldFd.setInt$(intFd); + AsynchronousCloseMonitor.signalBlockedThreads(oldFd); + try { + Libcore.os.close(oldFd); + } catch (ErrnoException errnoException) { + // TODO: are there any cases in which we should throw? + } + } + + public static boolean isConnected(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs, int remainingTimeoutMs) throws IOException { + ErrnoException cause = null; + try { + StructPollfd[] pollFds = new StructPollfd[] { new StructPollfd() }; + pollFds[0].fd = fd; + pollFds[0].events = (short) POLLOUT; + int rc = Libcore.os.poll(pollFds, remainingTimeoutMs); + if (rc == 0) { + return false; // Timeout. + } + int connectError = Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_ERROR); + if (connectError == 0) { + return true; // Success! + } + throw new ErrnoException("isConnected", connectError); // The connect(2) failed. + } catch (ErrnoException errnoException) { + if (errnoException.errno == EINTR) { + return false; // Punt and ask the caller to try again. + } else { + cause = errnoException; + } + } + // TODO: is it really helpful/necessary to throw so many different exceptions? + String detail = connectDetail(inetAddress, port, timeoutMs, cause); + if (cause.errno == ECONNRESET || cause.errno == ECONNREFUSED || + cause.errno == EADDRNOTAVAIL || cause.errno == EADDRINUSE || + cause.errno == ENETUNREACH) { + throw new ConnectException(detail, cause); + } else if (cause.errno == EACCES) { + throw new SecurityException(detail, cause); + } else if (cause.errno == ETIMEDOUT) { + throw new SocketTimeoutException(detail, cause); + } + throw new SocketException(detail, cause); + } + + // 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. + */ + public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException { + try { + return getSocketOptionErrno(fd, option); + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsSocketException(); + } + } + + private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException { + switch (option) { + 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: + // 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 IoBridge.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: + // 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: + return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE)); + case SocketOptions.SO_LINGER: + StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER); + if (!linger.isOn()) { + return false; + } + return linger.l_linger; + case SocketOptions.SO_OOBINLINE: + return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE)); + case SocketOptions.SO_RCVBUF: + return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF); + case SocketOptions.SO_REUSEADDR: + return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR)); + case SocketOptions.SO_SNDBUF: + return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF); + case SocketOptions.SO_TIMEOUT: + return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis(); + case SocketOptions.TCP_NODELAY: + return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY)); + default: + throw new SocketException("Unknown socket option: " + option); + } + } + + 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 IoBridge.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 IoBridge.JAVA_MCAST_JOIN_GROUP: + case IoBridge.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); + } + } + + /** + * java.io only throws FileNotFoundException when opening files, regardless of what actually + * went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening + * directories: POSIX says read-only is okay, but java.io doesn't even allow that. We also + * have an Android-specific hack to alter the default permissions. + */ + public static FileDescriptor open(String path, int flags) throws FileNotFoundException { + FileDescriptor fd = null; + try { + // On Android, we don't want default permissions to allow global access. + int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600; + fd = Libcore.os.open(path, flags, mode); + if (fd.valid()) { + // Posix open(2) fails with EISDIR only if you ask for write permission. + // Java disallows reading directories too. + boolean isDirectory = false; + if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) { + throw new ErrnoException("open", EISDIR); + } + } + return fd; + } catch (ErrnoException errnoException) { + try { + if (fd != null) { + IoUtils.close(fd); + } + } catch (IOException ignored) { + } + FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage()); + ex.initCause(errnoException); + throw ex; + } + } + + /** + * java.io thinks that a read at EOF is an error and should return -1, contrary to traditional + * Unix practice where you'd read until you got 0 bytes (and any future read would return -1). + */ + public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException { + Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount); + if (byteCount == 0) { + return 0; + } + try { + int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount); + if (readCount == 0) { + return -1; + } + return readCount; + } catch (ErrnoException errnoException) { + if (errnoException.errno == EAGAIN) { + // We return 0 rather than throw if we try to read from an empty non-blocking pipe. + return 0; + } + throw errnoException.rethrowAsIOException(); + } + } + + /** + * java.io always writes every byte it's asked to, or fails with an error. (That is, unlike + * Unix it never just writes as many bytes as happens to be convenient.) + */ + public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException { + Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount); + if (byteCount == 0) { + return; + } + try { + while (byteCount > 0) { + int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount); + byteCount -= bytesWritten; + byteOffset += bytesWritten; + } + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsIOException(); + } + } + + public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws IOException { + boolean isDatagram = (inetAddress != null); + if (!isDatagram && byteCount <= 0) { + return 0; + } + int result; + try { + result = Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); + } catch (ErrnoException errnoException) { + result = maybeThrowAfterSendto(isDatagram, errnoException); + } + return result; + } + + public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws IOException { + boolean isDatagram = (inetAddress != null); + if (!isDatagram && buffer.remaining() == 0) { + return 0; + } + int result; + try { + result = Libcore.os.sendto(fd, buffer, flags, inetAddress, port); + } catch (ErrnoException errnoException) { + result = maybeThrowAfterSendto(isDatagram, errnoException); + } + return result; + } + + private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException) throws SocketException { + if (isDatagram) { + if (errnoException.errno == ECONNRESET || errnoException.errno == ECONNREFUSED) { + return 0; + } + } else { + if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) { + // We were asked to write to a non-blocking socket, but were told + // it would block, so report "no bytes written". + return 0; + } + } + throw errnoException.rethrowAsSocketException(); + } + + public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException { + int result; + try { + InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null; + result = Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); + result = postRecvfrom(isRead, packet, isConnected, srcAddress, result); + } catch (ErrnoException errnoException) { + result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException); + } + return result; + } + + public static int recvfrom(boolean isRead, FileDescriptor fd, ByteBuffer buffer, int flags, DatagramPacket packet, boolean isConnected) throws IOException { + int result; + try { + InetSocketAddress srcAddress = (packet != null && !isConnected) ? new InetSocketAddress() : null; + result = Libcore.os.recvfrom(fd, buffer, flags, srcAddress); + result = postRecvfrom(isRead, packet, isConnected, srcAddress, result); + } catch (ErrnoException errnoException) { + result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException); + } + return result; + } + + private static int postRecvfrom(boolean isRead, DatagramPacket packet, boolean isConnected, InetSocketAddress srcAddress, int byteCount) { + if (isRead && byteCount == 0) { + return -1; + } + if (packet != null) { + packet.setLength(byteCount); + if (!isConnected) { + packet.setAddress(srcAddress.getAddress()); + packet.setPort(srcAddress.getPort()); + } + } + return byteCount; + } + + private static int maybeThrowAfterRecvfrom(boolean isRead, boolean isConnected, ErrnoException errnoException) throws SocketException, SocketTimeoutException { + if (isRead) { + if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) { + return 0; + } else { + throw errnoException.rethrowAsSocketException(); + } + } else { + if (isConnected && errnoException.errno == ECONNREFUSED) { + throw new PortUnreachableException("", errnoException); + } else if (errnoException.errno == EAGAIN || errnoException.errno == EWOULDBLOCK) { + throw new SocketTimeoutException(errnoException); + } else { + throw errnoException.rethrowAsSocketException(); + } + } + } + + public static FileDescriptor socket(boolean stream) throws SocketException { + FileDescriptor fd; + try { + fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0); + + // The RFC (http://www.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults + // to 1. The Linux kernel (at least up to 2.6.38) accidentally defaults to 64 (which + // would be correct for the *unicast* hop limit). + // See http://www.spinics.net/lists/netdev/msg129022.html, though no patch appears to + // have been applied as a result of that discussion. If that bug is ever fixed, we can + // remove this code. Until then, we manually set the hop limit on IPv6 datagram sockets. + // (IPv4 is already correct.) + if (!stream) { + Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 1); + } + + return fd; + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsSocketException(); + } + } + + public static InetAddress getSocketLocalAddress(FileDescriptor fd) { + SocketAddress sa = Libcore.os.getsockname(fd); + InetSocketAddress isa = (InetSocketAddress) sa; + return isa.getAddress(); + } + + public static int getSocketLocalPort(FileDescriptor fd) { + SocketAddress sa = Libcore.os.getsockname(fd); + InetSocketAddress isa = (InetSocketAddress) sa; + return isa.getPort(); + } + +} diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java index eeb8d99..6680882 100644 --- a/luni/src/main/java/libcore/io/IoUtils.java +++ b/luni/src/main/java/libcore/io/IoUtils.java @@ -17,137 +17,19 @@ package libcore.io; import java.io.Closeable; +import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; -import java.net.InetAddress; -import java.net.Inet4Address; -import java.net.InetSocketAddress; import java.net.Socket; -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() { } /** - * Implements java.io/java.net "available" semantics. - */ - public static int available(FileDescriptor fd) throws IOException { - try { - MutableInt available = new MutableInt(0); - int rc = Libcore.os.ioctlInt(fd, FIONREAD, available); - if (available.value < 0) { - // If the fd refers to a regular file, the result is the difference between - // the file size and the file position. This may be negative if the position - // is past the end of the file. If the fd refers to a special file masquerading - // as a regular file, the result may be negative because the special file - // may appear to have zero size and yet a previous read call may have - // read some amount of data and caused the file position to be advanced. - available.value = 0; - } - return available.value; - } catch (ErrnoException errnoException) { - if (errnoException.errno == ENOTTY) { - // The fd is unwilling to opine about its read buffer. - return 0; - } - throw errnoException.rethrowAsIOException(); - } - } - - /** - * java.io only throws FileNotFoundException when opening files, regardless of what actually - * went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening - * directories: POSIX says read-only is okay, but java.io doesn't even allow that. We also - * have an Android-specific hack to alter the default permissions. - */ - public static FileDescriptor open(String path, int flags) throws FileNotFoundException { - FileDescriptor fd = null; - try { - // On Android, we don't want default permissions to allow global access. - int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600; - fd = Libcore.os.open(path, flags, mode); - if (fd.valid()) { - // Posix open(2) fails with EISDIR only if you ask for write permission. - // Java disallows reading directories too. - boolean isDirectory = false; - if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) { - throw new ErrnoException("open", EISDIR); - } - } - return fd; - } catch (ErrnoException errnoException) { - try { - if (fd != null) { - close(fd); - } - } catch (IOException ignored) { - } - FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage()); - ex.initCause(errnoException); - throw ex; - } - } - - /** - * java.io thinks that a read at EOF is an error and should return -1, contrary to traditional - * Unix practice where you'd read until you got 0 bytes (and any future read would return -1). - */ - public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException { - Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount); - if (byteCount == 0) { - return 0; - } - try { - int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount); - if (readCount == 0) { - return -1; - } - return readCount; - } catch (ErrnoException errnoException) { - if (errnoException.errno == EAGAIN) { - // We return 0 rather than throw if we try to read from an empty non-blocking pipe. - return 0; - } - throw errnoException.rethrowAsIOException(); - } - } - - /** - * java.io always writes every byte it's asked to, or fails with an error. (That is, unlike - * Unix it never just writes as many bytes as happens to be convenient.) - */ - public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException { - Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount); - if (byteCount == 0) { - return; - } - try { - while (byteCount > 0) { - int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount); - byteCount -= bytesWritten; - byteOffset += bytesWritten; - } - } catch (ErrnoException errnoException) { - throw errnoException.rethrowAsIOException(); - } - } - - /** * Calls close(2) on 'fd'. Also resets the internal int to -1. Does nothing if 'fd' is null * or invalid. */ @@ -196,67 +78,6 @@ public final class IoUtils { } /** - * 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 { @@ -273,196 +94,6 @@ public final class IoUtils { } } - public static FileDescriptor socket(boolean stream) throws SocketException { - FileDescriptor fd; - try { - fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0); - - // The RFC (http://www.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults - // to 1. The Linux kernel (at least up to 2.6.38) accidentally defaults to 64 (which - // would be correct for the *unicast* hop limit). - // See http://www.spinics.net/lists/netdev/msg129022.html, though no patch appears to - // have been applied as a result of that discussion. If that bug is ever fixed, we can - // remove this code. Until then, we manually set the hop limit on IPv6 datagram sockets. - // (IPv4 is already correct.) - if (!stream) { - Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 1); - } - - return fd; - } catch (ErrnoException errnoException) { - throw errnoException.rethrowAsSocketException(); - } - } - - // 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. - */ - public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException { - try { - return getSocketOptionErrno(fd, option); - } catch (ErrnoException errnoException) { - throw errnoException.rethrowAsSocketException(); - } - } - - private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException { - switch (option) { - 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: - // 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: - // 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: - return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE)); - case SocketOptions.SO_LINGER: - StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER); - if (!linger.isOn()) { - return false; - } - return linger.l_linger; - case SocketOptions.SO_OOBINLINE: - return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE)); - case SocketOptions.SO_RCVBUF: - return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF); - case SocketOptions.SO_REUSEADDR: - return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR)); - case SocketOptions.SO_SNDBUF: - return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF); - case SocketOptions.SO_TIMEOUT: - return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis(); - case SocketOptions.TCP_NODELAY: - return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY)); - default: - throw new SocketException("Unknown socket option: " + option); - } - } - - 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; - return isa.getAddress(); - } - - public static int getSocketLocalPort(FileDescriptor fd) { - SocketAddress sa = Libcore.os.getsockname(fd); - InetSocketAddress isa = (InetSocketAddress) sa; - return isa.getPort(); - } - /** * Returns the contents of 'path' as a byte array. */ @@ -494,4 +125,23 @@ public final class IoUtils { IoUtils.closeQuietly(f); } } + + /** + * Recursively delete everything in {@code dir}. + */ + // TODO: this should specify paths as Strings rather than as Files + public static void deleteContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalArgumentException("not a directory: " + dir); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } + } } diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java index 7fceedc..bd50998 100644 --- a/luni/src/main/java/libcore/io/Os.java +++ b/luni/src/main/java/libcore/io/Os.java @@ -18,15 +18,21 @@ package libcore.io; import java.io.FileDescriptor; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import libcore.util.MutableInt; import libcore.util.MutableLong; public interface Os { + public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException; public boolean access(String path, int mode) throws ErrnoException; + public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException; public void chmod(String path, int mode) throws ErrnoException; public void close(FileDescriptor fd) throws ErrnoException; + public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException; + public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException; + public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException; public String[] environ(); public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException; public int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException; @@ -38,15 +44,23 @@ public interface Os { 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 int getegid(); + public int geteuid(); + public int getgid(); public String getenv(String name); /* TODO: break into getnameinfoHost and getnameinfoService? */ public String getnameinfo(InetAddress address, int flags) throws GaiException; + public int getpid(); + public int getppid(); + public StructPasswd getpwnam(String name) throws ErrnoException; + public StructPasswd getpwuid(int uid) throws ErrnoException; 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 int getuid(); public String if_indextoname(int index); public InetAddress inet_aton(String address); public InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException; @@ -64,13 +78,22 @@ public interface Os { public void munmap(long address, long byteCount) throws ErrnoException; public FileDescriptor open(String path, int flags, int mode) throws ErrnoException; public FileDescriptor[] pipe() throws ErrnoException; + /* TODO: if we used the non-standard ppoll(2) behind the scenes, we could take a long timeout. */ + public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; public StructStat lstat(String path) throws ErrnoException; public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException; public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException; public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException; + public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException; public void remove(String path) throws ErrnoException; public void rename(String oldPath, String newPath) throws ErrnoException; + public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException; + public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException; public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException; + public void setegid(int egid) throws ErrnoException; + public void seteuid(int euid) throws ErrnoException; + public void setgid(int gid) 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; @@ -78,6 +101,7 @@ public interface Os { 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 setuid(int uid) 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; @@ -87,6 +111,7 @@ public interface Os { public void symlink(String oldPath, String newPath) throws ErrnoException; public long sysconf(int name); public StructUtsname uname() throws ErrnoException; + public int waitpid(int pid, MutableInt status, int options) throws ErrnoException; public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException; public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException; public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/libcore/io/OsConstants.java index 34a931c..68a165c 100644 --- a/luni/src/main/java/libcore/io/OsConstants.java +++ b/luni/src/main/java/libcore/io/OsConstants.java @@ -230,6 +230,16 @@ public final class OsConstants { public static final int O_SYNC = placeholder(); public static final int O_TRUNC = placeholder(); public static final int O_WRONLY = placeholder(); + public static final int POLLERR = placeholder(); + public static final int POLLHUP = placeholder(); + public static final int POLLIN = placeholder(); + public static final int POLLNVAL = placeholder(); + public static final int POLLOUT = placeholder(); + public static final int POLLPRI = placeholder(); + public static final int POLLRDBAND = placeholder(); + public static final int POLLRDNORM = placeholder(); + public static final int POLLWRBAND = placeholder(); + public static final int POLLWRNORM = placeholder(); public static final int PROT_EXEC = placeholder(); public static final int PROT_NONE = placeholder(); public static final int PROT_READ = placeholder(); diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java index 4e23dc0..f976209 100644 --- a/luni/src/main/java/libcore/io/Posix.java +++ b/luni/src/main/java/libcore/io/Posix.java @@ -18,6 +18,7 @@ package libcore.io; import java.io.FileDescriptor; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.NioUtils; @@ -27,9 +28,14 @@ import libcore.util.MutableLong; public final class Posix implements Os { Posix() { } + public native FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException; public native boolean access(String path, int mode) throws ErrnoException; + public native void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException; public native void chmod(String path, int mode) throws ErrnoException; public native void close(FileDescriptor fd) throws ErrnoException; + public native void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException; + public native FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException; + public native FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException; public native String[] environ(); public native int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException; public native int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException; @@ -41,16 +47,24 @@ public final class Posix implements Os { 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 int getegid(); + public native int geteuid(); + public native int getgid(); public native String getenv(String name); public native String getnameinfo(InetAddress address, int flags) throws GaiException; + public native int getpid(); + public native int getppid(); + public native StructPasswd getpwnam(String name) throws ErrnoException; + public native StructPasswd getpwuid(int uid) throws ErrnoException; 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 int getuid(); public native String if_indextoname(int index); + public native InetAddress inet_aton(String address); 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); @@ -66,6 +80,7 @@ public final class Posix implements Os { public native void munmap(long address, long byteCount) throws ErrnoException; public native FileDescriptor open(String path, int flags, int mode) throws ErrnoException; public native FileDescriptor[] pipe() throws ErrnoException; + public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; public native StructStat lstat(String path) throws ErrnoException; public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { if (buffer.isDirect()) { @@ -80,9 +95,36 @@ public final class Posix implements Os { } 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 int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException { + if (buffer.isDirect()) { + return recvfromBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, srcAddress); + } else { + return recvfromBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), flags, srcAddress); + } + } + public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException { + // This indirection isn't strictly necessary, but ensures that our public interface is type safe. + return recvfromBytes(fd, bytes, byteOffset, byteCount, flags, srcAddress); + } + private native int recvfromBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) 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 int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException { + if (buffer.isDirect()) { + return sendtoBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, inetAddress, port); + } else { + return sendtoBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), flags, inetAddress, port); + } + } + public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException { + // This indirection isn't strictly necessary, but ensures that our public interface is type safe. + return sendtoBytes(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); + } + private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException; + public native void setegid(int egid) throws ErrnoException; + public native void seteuid(int euid) throws ErrnoException; + public native void setgid(int gid) 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; @@ -90,6 +132,7 @@ public final class Posix implements Os { 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 setuid(int uid) 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; @@ -98,6 +141,7 @@ public final class Posix implements Os { public native void symlink(String oldPath, String newPath) throws ErrnoException; public native long sysconf(int name); public native StructUtsname uname() throws ErrnoException; + public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException; public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { if (buffer.isDirect()) { return writeBytes(fd, buffer, buffer.position(), buffer.remaining()); diff --git a/luni/src/main/java/libcore/io/Streams.java b/luni/src/main/java/libcore/io/Streams.java index 2ece93c..ab8795c 100644 --- a/luni/src/main/java/libcore/io/Streams.java +++ b/luni/src/main/java/libcore/io/Streams.java @@ -21,6 +21,8 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; @@ -86,18 +88,36 @@ public final class Streams { } /** - * Returns a new byte[] containing the entire contents of the given InputStream. - * Useful when you don't know in advance how much data there is to be read. + * Returns a byte[] containing the remainder of 'in', closing it when done. */ public static byte[] readFully(InputStream in) throws IOException { - byte[] buffer = new byte[1024]; - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - while (true) { - int byteCount = in.read(buffer); - if (byteCount == -1) { - return bytes.toByteArray(); + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); + } + return bytes.toByteArray(); + } finally { + in.close(); + } + } + + /** + * Returns the remainder of 'reader' as a string, closing it when done. + */ + public static String readFully(Reader reader) throws IOException { + try { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); } - bytes.write(buffer, 0, byteCount); + return writer.toString(); + } finally { + reader.close(); } } @@ -158,4 +178,32 @@ public final class Streams { } return total; } + + /** + * Returns the ASCII characters up to but not including the next "\r\n", or + * "\n". + * + * @throws java.io.EOFException if the stream is exhausted before the next newline + * character. + */ + public static String readAsciiLine(InputStream in) throws IOException { + // TODO: support UTF-8 here instead + + StringBuilder result = new StringBuilder(80); + while (true) { + int c = in.read(); + if (c == -1) { + throw new EOFException(); + } else if (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(); + } } diff --git a/luni/src/main/java/libcore/io/StructPasswd.java b/luni/src/main/java/libcore/io/StructPasswd.java new file mode 100644 index 0000000..6f5e058 --- /dev/null +++ b/luni/src/main/java/libcore/io/StructPasswd.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * Information returned by getpwnam(3) and getpwuid(3). Corresponds to C's + * {@code struct passwd} from + * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pwd.h.html"><pwd.h></a> + */ +public final class StructPasswd { + public String pw_name; + public int pw_uid; /* uid_t */ + public int pw_gid; /* gid_t */ + public String pw_dir; + public String pw_shell; + + public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) { + this.pw_name = pw_name; + this.pw_uid = pw_uid; + this.pw_gid = pw_gid; + this.pw_dir = pw_dir; + this.pw_shell = pw_shell; + } +} diff --git a/luni/src/main/java/libcore/io/StructPollfd.java b/luni/src/main/java/libcore/io/StructPollfd.java new file mode 100644 index 0000000..c659d6e --- /dev/null +++ b/luni/src/main/java/libcore/io/StructPollfd.java @@ -0,0 +1,48 @@ +/* + * 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.io.FileDescriptor; + +/** + * Corresponds to C's {@code struct pollfd} from + * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html"><poll.h></a> + */ +public final class StructPollfd { + /** The file descriptor to poll. */ + public FileDescriptor fd; + + /** + * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set, + * POLLOUT to the write fd set. + */ + public short events; + + /** The events that actually happened. */ + public short revents; + + /** + * A non-standard extension that lets callers conveniently map back to the object + * their fd belongs to. This is used by Selector, for example, to associate each + * FileDescriptor with the corresponding SelectionKey. + */ + public Object userData; + + @Override public String toString() { + return "StructPollfd[fd=" + fd + ",events=" + events + ",revents=" + revents + "]"; + } +} diff --git a/luni/src/main/java/libcore/net/RawSocket.java b/luni/src/main/java/libcore/net/RawSocket.java index 255b0b9..bb29fb0 100644 --- a/luni/src/main/java/libcore/net/RawSocket.java +++ b/luni/src/main/java/libcore/net/RawSocket.java @@ -22,7 +22,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.net.SocketException; import java.util.Arrays; -import org.apache.harmony.luni.platform.Platform; +import libcore.io.IoBridge; /** * This class allows raw L2 packets to be sent and received via the @@ -111,7 +111,7 @@ public class RawSocket implements Closeable { */ public void close() throws IOException { guard.close(); - Platform.NETWORK.close(fd); + IoBridge.closeSocket(fd); } @Override protected void finalize() throws Throwable { diff --git a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java index 755bc96..70f76b7 100644 --- a/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java +++ b/luni/src/main/java/libcore/net/http/AbstractHttpInputStream.java @@ -83,7 +83,7 @@ abstract class AbstractHttpInputStream extends InputStream { if (cacheRequest != null) { cacheBody.close(); } - httpEngine.releaseSocket(reuseSocket); + httpEngine.release(reuseSocket); } /** @@ -102,6 +102,6 @@ abstract class AbstractHttpInputStream extends InputStream { if (cacheRequest != null) { cacheRequest.abort(); } - httpEngine.releaseSocket(false); + httpEngine.release(false); } } diff --git a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java index 274f03c..380ed02 100644 --- a/luni/src/main/java/libcore/net/http/ChunkedInputStream.java +++ b/luni/src/main/java/libcore/net/http/ChunkedInputStream.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.CacheRequest; import java.util.Arrays; +import libcore.io.Streams; /** * An HTTP body with alternating chunk sizes and chunk bodies. @@ -73,9 +74,9 @@ final class ChunkedInputStream extends AbstractHttpInputStream { private void readChunkSize() throws IOException { // read the suffix of the previous chunk if (bytesRemainingInChunk != NO_CHUNK_YET) { - HttpEngine.readLine(in); + Streams.readAsciiLine(in); } - String chunkSizeString = HttpEngine.readLine(in); + String chunkSizeString = Streams.readAsciiLine(in); int index = chunkSizeString.indexOf(";"); if (index != -1) { chunkSizeString = chunkSizeString.substring(0, index); diff --git a/luni/src/main/java/libcore/net/http/HttpEngine.java b/luni/src/main/java/libcore/net/http/HttpEngine.java index d023cbc..d95e21c 100644 --- a/luni/src/main/java/libcore/net/http/HttpEngine.java +++ b/luni/src/main/java/libcore/net/http/HttpEngine.java @@ -26,7 +26,6 @@ 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; @@ -34,6 +33,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charsets; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -61,16 +61,15 @@ import libcore.util.EmptyArray; * 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()}. + * recycled. By default, this socket connection is held when the last byte of + * the response is consumed. To release the connection when it is no longer + * required, use {@link #automaticallyReleaseConnectionToPool()}. */ public class HttpEngine { private static final CacheResponse 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 { @@ -151,10 +150,10 @@ public class HttpEngine { private final URI uri; - private final RawHeaders rawRequestHeaders; + private final RequestHeaders requestHeaders; /** Null until a response is received from the network or the cache */ - private RawHeaders rawResponseHeaders; + private ResponseHeaders responseHeaders; /* * The cache response currently being validated on a conditional get. Null @@ -162,16 +161,21 @@ public class HttpEngine { * 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; + private ResponseHeaders cachedResponseHeaders; + private InputStream cachedResponseBody; /** * True if the socket connection should be released to the connection pool * when the response has been fully read. */ - private boolean dontReleaseSocketToPool; + private boolean automaticallyReleaseConnectionToPool; + + /** True if the socket connection is no longer needed by this engine. */ + private boolean connectionReleased; /** + * @param requestHeaders the client's supplied request headers. This class + * creates a private copy that it can mutate. * @param connection the connection used for an intermediate response * immediately prior to this request/response pair, such as a same-host * redirect. This engine assumes ownership of the connection and must @@ -179,19 +183,8 @@ public class HttpEngine { */ 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; @@ -200,10 +193,8 @@ public class HttpEngine { } catch (URISyntaxException e) { throw new IOException(e); } - } - public final void dontReleaseSocketToPool() { - this.dontReleaseSocketToPool = true; + this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders)); } /** @@ -217,30 +208,31 @@ public class HttpEngine { } prepareRawRequestHeaders(); - RequestHeaders cacheRequestHeaders = new RequestHeaders(uri, rawRequestHeaders); - initResponseSource(cacheRequestHeaders); + initResponseSource(); + if (responseCache instanceof HttpResponseCache) { + ((HttpResponseCache) responseCache).trackResponse(responseSource); + } /* * The raw response source may require the network, but the request * headers may forbid network use. In that case, dispose of the network * response and use a BAD_GATEWAY response instead. */ - if (cacheRequestHeaders.onlyIfCached && responseSource.requiresConnection()) { + if (requestHeaders.onlyIfCached && responseSource.requiresConnection()) { if (responseSource == ResponseSource.CONDITIONAL_CACHE) { - this.responseHeadersToValidate = null; - IoUtils.closeQuietly(responseBodyToValidate); - this.responseBodyToValidate = null; + IoUtils.closeQuietly(cachedResponseBody); } this.responseSource = ResponseSource.CACHE; this.cacheResponse = BAD_GATEWAY_RESPONSE; - setResponse(RawHeaders.fromMultimap(cacheResponse.getHeaders()), - cacheResponse.getBody()); + RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders()); + setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody()); } if (responseSource.requiresConnection()) { sendSocketRequest(); - } else { - // TODO: release 'connection' if it is non-null + } else if (connection != null) { + HttpConnectionPool.INSTANCE.recycle(connection); + connection = null; } } @@ -248,38 +240,38 @@ public class HttpEngine { * 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 { + private void initResponseSource() throws IOException { responseSource = ResponseSource.NETWORK; if (!policy.getUseCaches() || responseCache == null) { return; } - CacheResponse candidate = responseCache.get(uri, method, rawRequestHeaders.toMultimap()); - if (candidate == null || !acceptCacheResponseType(candidate)) { + CacheResponse candidate = responseCache.get(uri, method, + requestHeaders.headers.toMultimap()); + if (candidate == null) { return; } - Map<String, List<String>> responseHeaders = candidate.getHeaders(); - if (responseHeaders == null) { - return; - } - InputStream cacheBodyIn = candidate.getBody(); // must be closed - if (cacheBodyIn == null) { + + Map<String, List<String>> responseHeadersMap = candidate.getHeaders(); + cachedResponseBody = candidate.getBody(); + if (!acceptCacheResponseType(candidate) + || responseHeadersMap == null + || cachedResponseBody == null) { + IoUtils.closeQuietly(cachedResponseBody); return; } - RawHeaders headers = RawHeaders.fromMultimap(responseHeaders); - ResponseHeaders cacheResponseHeaders = new ResponseHeaders(uri, headers); + RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap); + cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders); long now = System.currentTimeMillis(); - this.responseSource = cacheResponseHeaders.chooseResponseSource(now, cacheRequestHeaders); + this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders); if (responseSource == ResponseSource.CACHE) { this.cacheResponse = candidate; - setResponse(headers, cacheBodyIn); + setResponse(cachedResponseHeaders, cachedResponseBody); } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { this.cacheResponse = candidate; - this.responseHeadersToValidate = cacheResponseHeaders; - this.responseBodyToValidate = cacheBodyIn; } else if (responseSource == ResponseSource.NETWORK) { - IoUtils.closeQuietly(cacheBodyIn); + IoUtils.closeQuietly(cachedResponseBody); } else { throw new AssertionError(); } @@ -298,7 +290,7 @@ public class HttpEngine { requestOut = socketOut; socketIn = connection.getInputStream(); - if (policy.getDoOutput() && method != CONNECT) { + if (hasRequestBody()) { initRequestBodyOut(); } } @@ -324,17 +316,9 @@ public class HttpEngine { } 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)) { + if (chunkLength > 0 || requestHeaders.isChunked()) { sendChunked = true; - contentLength = -1; if (chunkLength == -1) { chunkLength = DEFAULT_CHUNK_LENGTH; } @@ -357,9 +341,9 @@ public class HttpEngine { } else if (sendChunked) { writeRequestHeaders(-1); requestBodyOut = new ChunkedOutputStream(requestOut, chunkLength); - } else if (contentLength != -1) { - writeRequestHeaders(contentLength); - requestBodyOut = new RetryableOutputStream(contentLength); + } else if (requestHeaders.contentLength != -1) { + writeRequestHeaders(requestHeaders.contentLength); + requestBodyOut = new RetryableOutputStream(requestHeaders.contentLength); } else { requestBodyOut = new RetryableOutputStream(); } @@ -369,17 +353,21 @@ public class HttpEngine { * @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 { + private void setResponse(ResponseHeaders headers, InputStream body) throws IOException { if (this.responseBodyIn != null) { throw new IllegalStateException(); } - this.rawResponseHeaders = headers; - this.httpMinorVersion = rawResponseHeaders.getHttpMinorVersion(); + this.responseHeaders = headers; + this.httpMinorVersion = responseHeaders.headers.getHttpMinorVersion(); if (body != null) { initContentStream(body); } } + private boolean hasRequestBody() { + return method == POST || method == PUT; + } + /** * Returns the request body or null if this request doesn't have a body. */ @@ -391,29 +379,36 @@ public class HttpEngine { } public final boolean hasResponse() { - return rawResponseHeaders != null; + return responseHeaders != null; + } + + public final RequestHeaders getRequestHeaders() { + return requestHeaders; } - public final RawHeaders getRequestHeaders() { - return rawRequestHeaders; + public final ResponseHeaders getResponseHeaders() { + if (responseHeaders == null) { + throw new IllegalStateException(); + } + return responseHeaders; } - public final RawHeaders getResponseHeaders() { - if (rawResponseHeaders == null) { + public final int getResponseCode() { + if (responseHeaders == null) { throw new IllegalStateException(); } - return rawResponseHeaders; + return responseHeaders.headers.getResponseCode(); } public final InputStream getResponseBody() { - if (rawResponseHeaders == null) { + if (responseHeaders == null) { throw new IllegalStateException(); } return responseBodyIn; } public final CacheResponse getCacheResponse() { - if (rawResponseHeaders == null) { + if (responseHeaders == null) { throw new IllegalStateException(); } return cacheResponse; @@ -439,9 +434,7 @@ public class HttpEngine { } // Should we cache this response for this request? - RequestHeaders requestCacheHeaders = new RequestHeaders(uri, rawRequestHeaders); - ResponseHeaders responseCacheHeaders = new ResponseHeaders(uri, rawResponseHeaders); - if (!responseCacheHeaders.isCacheable(requestCacheHeaders)) { + if (!responseHeaders.isCacheable(requestHeaders)) { return; } @@ -454,67 +447,71 @@ public class HttpEngine { } /** - * Releases this connection so that it may be either reused or closed. + * Cause the socket connection to be released to the connection pool when + * it is no longer needed. If it is already unneeded, it will be pooled + * immediately. */ - public final void releaseSocket(boolean reuseSocket) { - // we cannot recycle sockets that have incomplete output. - if (requestBodyOut != null && !requestBodyOut.closed) { - reuseSocket = false; + public final void automaticallyReleaseConnectionToPool() { + automaticallyReleaseConnectionToPool = true; + if (connection != null && connectionReleased) { + HttpConnectionPool.INSTANCE.recycle(connection); + connection = null; } + } - // if the headers specify that the connection shouldn't be reused, don't reuse it - if (hasConnectionCloseHeaders()) { - reuseSocket = false; + /** + * Releases this engine so that its resources may be either reused or + * closed. + */ + public final void release(boolean reusable) { + // If the response body comes from the cache, close it. + if (responseBodyIn == cachedResponseBody) { + IoUtils.closeQuietly(responseBodyIn); } - /* - * 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 (!connectionReleased && connection != null) { + connectionReleased = true; - if (connection != null) { - if (reuseSocket) { - HttpConnectionPool.INSTANCE.recycle(connection); - } else { - connection.closeSocketAndStreams(); + // We cannot reuse sockets that have incomplete output. + if (requestBodyOut != null && !requestBodyOut.closed) { + reusable = false; } - 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; - } + // If the headers specify that the connection shouldn't be reused, don't reuse it. + if (hasConnectionCloseHeader()) { + reusable = false; + } - /** - * 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); + if (responseBodyIn instanceof UnknownLengthHttpInputStream) { + reusable = false; + } + + if (reusable && responseBodyIn != null) { + // We must discard the response body before the connection can be reused. + try { + Streams.skipAll(responseBodyIn); + } catch (IOException e) { + reusable = false; + } + } + + if (!reusable) { + connection.closeSocketAndStreams(); + connection = null; + } else if (automaticallyReleaseConnectionToPool) { + HttpConnectionPool.INSTANCE.recycle(connection); + connection = null; } - responseBodyIn.close(); } } private void initContentStream(InputStream transferStream) throws IOException { - if (transparentGzip - && "gzip".equalsIgnoreCase(rawResponseHeaders.get("Content-Encoding"))) { + if (transparentGzip && responseHeaders.isContentEncodingGzip()) { /* * If the response was transparently gzipped, remove the gzip header field * so clients don't double decompress. http://b/3009828 */ - rawResponseHeaders.removeAll("Content-Encoding"); + responseHeaders.stripContentEncoding(); responseBodyIn = new GZIPInputStream(transferStream); } else { responseBodyIn = transferStream; @@ -526,17 +523,13 @@ public class HttpEngine { return new FixedLengthInputStream(socketIn, cacheRequest, this, 0); } - if ("chunked".equalsIgnoreCase(rawResponseHeaders.get("Transfer-Encoding"))) { + if (responseHeaders.isChunked()) { 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) { - } + if (responseHeaders.contentLength != -1) { + return new FixedLengthInputStream(socketIn, cacheRequest, this, + responseHeaders.contentLength); } /* @@ -547,35 +540,14 @@ public class HttpEngine { 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()); + headers.setStatusLine(Streams.readAsciiLine(socketIn)); readHeaders(headers); - setResponse(headers, null); } while (headers.getResponseCode() == HTTP_CONTINUE); + setResponse(new ResponseHeaders(uri, headers), null); } /** @@ -583,7 +555,7 @@ public class HttpEngine { * See RFC 2616 section 4.3. */ public final boolean hasResponseBody() { - int responseCode = rawResponseHeaders.getResponseCode(); + int responseCode = responseHeaders.headers.getResponseCode(); if (method != HEAD && method != CONNECT && (responseCode < HTTP_CONTINUE || responseCode >= 200) @@ -597,11 +569,7 @@ public class HttpEngine { * 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"))) { + if (responseHeaders.contentLength != -1 || responseHeaders.isChunked()) { return true; } @@ -613,20 +581,14 @@ public class HttpEngine { * with chunked encoding. */ final void readTrailers() throws IOException { - readHeaders(rawResponseHeaders); + readHeaders(responseHeaders.headers); } 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)); - } + while (!(line = Streams.readAsciiLine(socketIn)).isEmpty()) { + headers.addLine(line); } CookieHandler cookieHandler = CookieHandler.getDefault(); @@ -676,23 +638,19 @@ public class HttpEngine { * the connection is using a proxy. */ protected RawHeaders getNetworkRequestHeaders() throws IOException { - rawRequestHeaders.setStatusLine(getRequestLine()); + requestHeaders.headers.setStatusLine(getRequestLine()); int fixedContentLength = policy.getFixedContentLength(); if (fixedContentLength != -1) { - rawRequestHeaders.addIfAbsent("Content-Length", Integer.toString(fixedContentLength)); + requestHeaders.setContentLength(fixedContentLength); } else if (sendChunked) { - rawRequestHeaders.addIfAbsent("Transfer-Encoding", "chunked"); + requestHeaders.setChunked(); } 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"); + int contentLength = ((RetryableOutputStream) requestBodyOut).contentLength(); + requestHeaders.setContentLength(contentLength); } - return rawRequestHeaders; + return requestHeaders.headers; } /** @@ -702,35 +660,37 @@ public class HttpEngine { * doesn't know what content types the application is interested in. */ private void prepareRawRequestHeaders() throws IOException { - rawRequestHeaders.setStatusLine(getRequestLine()); + requestHeaders.headers.setStatusLine(getRequestLine()); - if (rawRequestHeaders.get("User-Agent") == null) { - rawRequestHeaders.add("User-Agent", getDefaultUserAgent()); + if (requestHeaders.userAgent == null) { + requestHeaders.setUserAgent(getDefaultUserAgent()); } - if (rawRequestHeaders.get("Host") == null) { - rawRequestHeaders.add("Host", getOriginAddress(policy.getURL())); + if (requestHeaders.host == null) { + requestHeaders.setHost(getOriginAddress(policy.getURL())); } - if (httpMinorVersion > 0) { - rawRequestHeaders.addIfAbsent("Connection", "Keep-Alive"); + if (httpMinorVersion > 0 && requestHeaders.connection == null) { + requestHeaders.setConnection("Keep-Alive"); } - if (rawRequestHeaders.get("Accept-Encoding") == null) { + if (requestHeaders.acceptEncoding == null) { transparentGzip = true; - rawRequestHeaders.set("Accept-Encoding", "gzip"); + requestHeaders.setAcceptEncoding("gzip"); + } + + if (hasRequestBody() && requestHeaders.contentType == null) { + requestHeaders.setContentType("application/x-www-form-urlencoded"); + } + + long ifModifiedSince = policy.getIfModifiedSince(); + if (ifModifiedSince != 0) { + requestHeaders.setIfModifiedSince(new Date(ifModifiedSince)); } CookieHandler cookieHandler = CookieHandler.getDefault(); if (cookieHandler != null) { - 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()); - } - } + requestHeaders.addCookies(cookieHandler.get(uri, requestHeaders.headers.toMultimap())); } } @@ -769,10 +729,9 @@ public class HttpEngine { 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"))); + private boolean hasConnectionCloseHeader() { + return (responseHeaders != null && responseHeaders.hasConnectionClose()) + || requestHeaders.hasConnectionClose(); } protected final String getOriginAddress(URL url) { @@ -823,23 +782,19 @@ public class HttpEngine { requestOut = socketOut; readResponseHeaders(); - rawResponseHeaders.add(ResponseHeaders.SENT_MILLIS, Long.toString(sentRequestMillis)); - rawResponseHeaders.add(ResponseHeaders.RECEIVED_MILLIS, - Long.toString(System.currentTimeMillis())); + responseHeaders.setLocalTimestamps(sentRequestMillis, 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; + if (cachedResponseHeaders.validate(responseHeaders)) { + if (responseCache instanceof HttpResponseCache) { + ((HttpResponseCache) responseCache).trackConditionalCacheHit(); + } + // Discard the network response body. Combine the headers. + release(true); + setResponse(cachedResponseHeaders.combine(responseHeaders), cachedResponseBody); return; } else { - IoUtils.closeQuietly(responseBodyToValidate); - responseBodyToValidate = null; - responseHeadersToValidate = null; + IoUtils.closeQuietly(cachedResponseBody); } } diff --git a/luni/src/main/java/libcore/net/http/HttpResponseCache.java b/luni/src/main/java/libcore/net/http/HttpResponseCache.java new file mode 100644 index 0000000..50ad5e2 --- /dev/null +++ b/luni/src/main/java/libcore/net/http/HttpResponseCache.java @@ -0,0 +1,543 @@ +/* + * 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.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.CacheRequest; +import java.net.CacheResponse; +import java.net.HttpURLConnection; +import java.net.ResponseCache; +import java.net.SecureCacheResponse; +import java.net.URI; +import java.net.URLConnection; +import java.nio.charset.Charsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; +import libcore.io.Base64; +import libcore.io.DiskLruCache; +import libcore.io.IoUtils; +import libcore.io.Streams; + +/** + * Cache responses in a directory on the file system. Most clients should use + * {@code android.net.HttpResponseCache}, the stable, documented front end for + * this. + */ +public final class HttpResponseCache extends ResponseCache { + // TODO: add APIs to iterate the cache? + private static final int VERSION = 201105; + private static final int ENTRY_METADATA = 0; + private static final int ENTRY_BODY = 1; + private static final int ENTRY_COUNT = 2; + + private final DiskLruCache cache; + + /* read and write statistics, all guarded by 'this' */ + private int writeSuccessCount; + private int writeAbortCount; + private int networkCount; + private int hitCount; + private int requestCount; + + public HttpResponseCache(File directory, long maxSize) throws IOException { + cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize); + } + + private String uriToKey(URI uri) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] md5bytes = messageDigest.digest(uri.toString().getBytes(Charsets.UTF_8)); + return IntegralToString.bytesToHexString(md5bytes, false); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + @Override public CacheResponse get(URI uri, String requestMethod, + Map<String, List<String>> requestHeaders) { + String key = uriToKey(uri); + DiskLruCache.Snapshot snapshot; + Entry entry; + try { + snapshot = cache.get(key); + if (snapshot == null) { + return null; + } + entry = new Entry(new BufferedInputStream(snapshot.getInputStream(ENTRY_METADATA))); + } catch (IOException e) { + // Give up because the cache cannot be read. + return null; + } + + if (!entry.matches(uri, requestMethod, requestHeaders)) { + snapshot.close(); + return null; + } + + InputStream body = newBodyInputStream(snapshot); + return entry.isHttps() + ? entry.newSecureCacheResponse(body) + : entry.newCacheResponse(body); + } + + /** + * Returns an input stream that reads the body of a snapshot, closing the + * snapshot when the stream is closed. + */ + private InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) { + return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) { + @Override public void close() throws IOException { + snapshot.close(); + super.close(); + } + }; + } + + @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + if (!(urlConnection instanceof HttpURLConnection)) { + return null; + } + + HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; + String requestMethod = httpConnection.getRequestMethod(); + String key = uriToKey(uri); + + if (requestMethod.equals(HttpEngine.POST) + || requestMethod.equals(HttpEngine.PUT) + || requestMethod.equals(HttpEngine.DELETE)) { + try { + cache.remove(key); + } catch (IOException ignored) { + // The cache cannot be written. + } + return null; + } else if (!requestMethod.equals(HttpEngine.GET)) { + /* + * Don't cache non-GET responses. We're technically allowed to cache + * HEAD requests and some POST requests, but the complexity of doing + * so is high and the benefit is low. + */ + return null; + } + + HttpEngine httpEngine = getHttpEngine(httpConnection); + if (httpEngine == null) { + // Don't cache unless the HTTP implementation is ours. + return null; + } + + ResponseHeaders response = httpEngine.getResponseHeaders(); + if (response.hasVaryAll()) { + return null; + } + + RawHeaders varyHeaders = httpEngine.getRequestHeaders().headers.getAll(response.varyFields); + Entry entry = new Entry(uri, varyHeaders, httpConnection); + DiskLruCache.Editor editor = null; + try { + editor = cache.edit(key); + if (editor == null) { + return null; + } + entry.writeTo(editor); + return new CacheRequestImpl(editor); + } catch (IOException e) { + // Give up because the cache cannot be written. + try { + if (editor != null) { + editor.abort(); + } + } catch (IOException ignored) { + } + return null; + } + } + + private HttpEngine getHttpEngine(HttpURLConnection httpConnection) { + if (httpConnection instanceof HttpURLConnectionImpl) { + return ((HttpURLConnectionImpl) httpConnection).getHttpEngine(); + } else if (httpConnection instanceof HttpsURLConnectionImpl) { + return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine(); + } else { + return null; + } + } + + public DiskLruCache getCache() { + return cache; + } + + synchronized int getWriteAbortCount() { + return writeAbortCount; + } + + synchronized int getWriteSuccessCount() { + return writeSuccessCount; + } + + synchronized void trackResponse(ResponseSource source) { + requestCount++; + + switch (source) { + case CACHE: + hitCount++; + break; + case CONDITIONAL_CACHE: + case NETWORK: + networkCount++; + break; + } + } + + synchronized void trackConditionalCacheHit() { + hitCount++; + } + + public synchronized int getNetworkCount() { + return networkCount; + } + + public synchronized int getHitCount() { + return hitCount; + } + + public synchronized int getRequestCount() { + return requestCount; + } + + private final class CacheRequestImpl extends CacheRequest { + private final DiskLruCache.Editor editor; + private OutputStream cacheOut; + private boolean done; + private OutputStream body; + + public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException { + this.editor = editor; + this.cacheOut = editor.newOutputStream(ENTRY_BODY); + this.body = new FilterOutputStream(cacheOut) { + @Override public void close() throws IOException { + synchronized (HttpResponseCache.this) { + if (done) { + return; + } + done = true; + writeSuccessCount++; + } + super.close(); + editor.commit(); + } + }; + } + + @Override public void abort() { + synchronized (HttpResponseCache.this) { + if (done) { + return; + } + done = true; + writeAbortCount++; + } + IoUtils.closeQuietly(cacheOut); + try { + editor.abort(); + } catch (IOException ignored) { + } + } + + @Override public OutputStream getBody() throws IOException { + return body; + } + } + + private static final class Entry { + private final String uri; + private final RawHeaders varyHeaders; + private final String requestMethod; + private final RawHeaders responseHeaders; + private final String cipherSuite; + private final Certificate[] peerCertificates; + private final Certificate[] localCertificates; + + /* + * Reads an entry from an input stream. A typical entry looks like this: + * http://google.com/foo + * GET + * 2 + * Accept-Language: fr-CA + * Accept-Charset: UTF-8 + * HTTP/1.1 200 OK + * 3 + * Content-Type: image/png + * Content-Length: 100 + * Cache-Control: max-age=600 + * + * A typical HTTPS file looks like this: + * https://google.com/foo + * GET + * 2 + * Accept-Language: fr-CA + * Accept-Charset: UTF-8 + * HTTP/1.1 200 OK + * 3 + * Content-Type: image/png + * Content-Length: 100 + * Cache-Control: max-age=600 + * + * AES_256_WITH_MD5 + * 2 + * base64-encoded peerCertificate[0] + * base64-encoded peerCertificate[1] + * -1 + * + * The file is newline separated. The first two lines are the URL and + * the request method. Next is the number of HTTP Vary request header + * lines, followed by those lines. + * + * Next is the response status line, followed by the number of HTTP + * response header lines, followed by those lines. + * + * HTTPS responses also contain SSL session information. This begins + * with a blank line, and then a line containing the cipher suite. Next + * is the length of the peer certificate chain. These certificates are + * base64-encoded and appear each on their own line. The next line + * contains the length of the local certificate chain. These + * certificates are also base64-encoded and appear each on their own + * line. A length of -1 is used to encode a null array. + */ + public Entry(InputStream in) throws IOException { + try { + uri = Streams.readAsciiLine(in); + requestMethod = Streams.readAsciiLine(in); + varyHeaders = new RawHeaders(); + int varyRequestHeaderLineCount = readInt(in); + for (int i = 0; i < varyRequestHeaderLineCount; i++) { + varyHeaders.addLine(Streams.readAsciiLine(in)); + } + + responseHeaders = new RawHeaders(); + responseHeaders.setStatusLine(Streams.readAsciiLine(in)); + int responseHeaderLineCount = readInt(in); + for (int i = 0; i < responseHeaderLineCount; i++) { + responseHeaders.addLine(Streams.readAsciiLine(in)); + } + + if (isHttps()) { + String blank = Streams.readAsciiLine(in); + if (!blank.isEmpty()) { + throw new IOException("expected \"\" but was \"" + blank + "\""); + } + cipherSuite = Streams.readAsciiLine(in); + peerCertificates = readCertArray(in); + localCertificates = readCertArray(in); + } else { + cipherSuite = null; + peerCertificates = null; + localCertificates = null; + } + } finally { + in.close(); + } + } + + public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) { + this.uri = uri.toString(); + this.varyHeaders = varyHeaders; + this.requestMethod = httpConnection.getRequestMethod(); + this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields()); + + if (isHttps()) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection; + cipherSuite = httpsConnection.getCipherSuite(); + Certificate[] peerCertificatesNonFinal = null; + try { + peerCertificatesNonFinal = httpsConnection.getServerCertificates(); + } catch (SSLPeerUnverifiedException ignored) { + } + peerCertificates = peerCertificatesNonFinal; + localCertificates = httpsConnection.getLocalCertificates(); + } else { + cipherSuite = null; + peerCertificates = null; + localCertificates = null; + } + } + + public void writeTo(DiskLruCache.Editor editor) throws IOException { + OutputStream out = editor.newOutputStream(0); + Writer writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.UTF_8)); + + writer.write(uri + '\n'); + writer.write(requestMethod + '\n'); + writer.write(Integer.toString(varyHeaders.length()) + '\n'); + for (int i = 0; i < varyHeaders.length(); i++) { + writer.write(varyHeaders.getFieldName(i) + ": " + + varyHeaders.getValue(i) + '\n'); + } + + writer.write(responseHeaders.getStatusLine() + '\n'); + writer.write(Integer.toString(responseHeaders.length()) + '\n'); + for (int i = 0; i < responseHeaders.length(); i++) { + writer.write(responseHeaders.getFieldName(i) + ": " + + responseHeaders.getValue(i) + '\n'); + } + + if (isHttps()) { + writer.write('\n'); + writer.write(cipherSuite + '\n'); + writeCertArray(writer, peerCertificates); + writeCertArray(writer, localCertificates); + } + writer.close(); + } + + private boolean isHttps() { + return uri.startsWith("https://"); + } + + private int readInt(InputStream in) throws IOException { + String intString = Streams.readAsciiLine(in); + try { + return Integer.parseInt(intString); + } catch (NumberFormatException e) { + throw new IOException("expected an int but was \"" + intString + "\""); + } + } + + private Certificate[] readCertArray(InputStream in) throws IOException { + int length = readInt(in); + if (length == -1) { + return null; + } + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Certificate[] result = new Certificate[length]; + for (int i = 0; i < result.length; i++) { + String line = Streams.readAsciiLine(in); + byte[] bytes = Base64.decode(line.getBytes(Charsets.US_ASCII)); + result[i] = certificateFactory.generateCertificate( + new ByteArrayInputStream(bytes)); + } + return result; + } catch (CertificateException e) { + throw new IOException(e); + } + } + + private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException { + if (certificates == null) { + writer.write("-1\n"); + return; + } + try { + writer.write(Integer.toString(certificates.length) + '\n'); + for (Certificate certificate : certificates) { + byte[] bytes = certificate.getEncoded(); + String line = Base64.encode(bytes); + writer.write(line + '\n'); + } + } catch (CertificateEncodingException e) { + throw new IOException(e); + } + } + + public boolean matches(URI uri, String requestMethod, + Map<String, List<String>> requestHeaders) { + return this.uri.equals(uri.toString()) + && this.requestMethod.equals(requestMethod) + && new ResponseHeaders(uri, responseHeaders) + .varyMatches(varyHeaders.toMultimap(), requestHeaders); + } + + public CacheResponse newCacheResponse(final InputStream in) { + return new CacheResponse() { + @Override public Map<String, List<String>> getHeaders() { + return responseHeaders.toMultimap(); + } + + @Override public InputStream getBody() { + return in; + } + }; + } + + public SecureCacheResponse newSecureCacheResponse(final InputStream in) { + return new SecureCacheResponse() { + @Override public Map<String, List<String>> getHeaders() { + return responseHeaders.toMultimap(); + } + + @Override public InputStream getBody() { + return in; + } + + @Override public String getCipherSuite() { + return cipherSuite; + } + + @Override public List<Certificate> getServerCertificateChain() + throws SSLPeerUnverifiedException { + if (peerCertificates == null || peerCertificates.length == 0) { + throw new SSLPeerUnverifiedException(null); + } + return Arrays.asList(peerCertificates.clone()); + } + + @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + if (peerCertificates == null || peerCertificates.length == 0) { + throw new SSLPeerUnverifiedException(null); + } + return ((X509Certificate) peerCertificates[0]).getSubjectX500Principal(); + } + + @Override public List<Certificate> getLocalCertificateChain() { + if (localCertificates == null || localCertificates.length == 0) { + return null; + } + return Arrays.asList(localCertificates.clone()); + } + + @Override public Principal getLocalPrincipal() { + if (localCertificates == null || localCertificates.length == 0) { + return null; + } + return ((X509Certificate) localCertificates[0]).getSubjectX500Principal(); + } + }; + } + } +} diff --git a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java index 95b37fd..be4c132 100644 --- a/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java +++ b/luni/src/main/java/libcore/net/http/HttpURLConnectionImpl.java @@ -33,10 +33,9 @@ import java.net.SocketPermission; import java.net.URL; import java.nio.charset.Charsets; import java.security.Permission; -import java.util.Date; import java.util.List; import java.util.Map; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; /** * This implementation uses HttpEngine to send requests and receive responses. @@ -52,16 +51,13 @@ import org.apache.harmony.luni.util.Base64; * 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 { +class HttpURLConnectionImpl extends HttpURLConnection { private final int defaultPort; private Proxy proxy; - // TODO: should these be set by URLConnection.setDefaultRequestProperty ? - private static RawHeaders defaultRequestHeaders = new RawHeaders(); - - protected RawHeaders rawRequestHeaders = new RawHeaders(defaultRequestHeaders); + private final RawHeaders rawRequestHeaders = new RawHeaders(); private int redirectionCount; @@ -88,13 +84,10 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } } - /** - * Close the socket connection to the remote origin server or proxy. - */ @Override public final void disconnect() { - // TODO: what happens if they call disconnect() before connect? + // Calling disconnect() before a connection exists should have no effect. if (httpEngine != null) { - httpEngine.releaseSocket(false); + httpEngine.release(false); } } @@ -106,7 +99,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { try { HttpEngine response = getResponse(); if (response.hasResponseBody() - && response.getResponseHeaders().getResponseCode() >= HTTP_BAD_REQUEST) { + && response.getResponseCode() >= HTTP_BAD_REQUEST) { return response.getResponseBody(); } return null; @@ -121,7 +114,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { */ @Override public final String getHeaderField(int position) { try { - return getResponse().getResponseHeaders().getValue(position); + return getResponse().getResponseHeaders().headers.getValue(position); } catch (IOException e) { return null; } @@ -134,10 +127,10 @@ public class HttpURLConnectionImpl extends HttpURLConnection { */ @Override public final String getHeaderField(String fieldName) { try { - RawHeaders responseHeaders = getResponse().getResponseHeaders(); + RawHeaders rawHeaders = getResponse().getResponseHeaders().headers; return fieldName == null - ? responseHeaders.getStatusLine() - : responseHeaders.get(fieldName); + ? rawHeaders.getStatusLine() + : rawHeaders.get(fieldName); } catch (IOException e) { return null; } @@ -145,7 +138,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { @Override public final String getHeaderFieldKey(int position) { try { - return getResponse().getResponseHeaders().getFieldName(position); + return getResponse().getResponseHeaders().headers.getFieldName(position); } catch (IOException e) { return null; } @@ -153,7 +146,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { @Override public final Map<String, List<String>> getHeaderFields() { try { - return getResponse().getResponseHeaders().toMultimap(); + return getResponse().getResponseHeaders().headers.toMultimap(); } catch (IOException e) { return null; } @@ -193,7 +186,15 @@ public class HttpURLConnectionImpl extends HttpURLConnection { @Override public final OutputStream getOutputStream() throws IOException { connect(); - return httpEngine.getRequestBody(); + + OutputStream result = httpEngine.getRequestBody(); + if (result == null) { + throw new ProtocolException("method does not support a request body: " + method); + } else if (httpEngine.hasResponse()) { + throw new ProtocolException("cannot write request body after response has been read"); + } + + return result; } @Override public final Permission getPermission() throws IOException { @@ -230,6 +231,15 @@ public class HttpURLConnectionImpl extends HttpURLConnection { connected = true; try { + if (doOutput) { + if (method == HttpEngine.GET) { + // they are requesting a stream to write to. This implies a POST method + method = HttpEngine.POST; + } else if (method != HttpEngine.POST && method != HttpEngine.PUT) { + // If the request method is neither POST nor PUT, then you're not writing + throw new ProtocolException(method + " does not support writing"); + } + } httpEngine = newHttpEngine(method, rawRequestHeaders, null, null); } catch (IOException e) { httpEngineFailure = e; @@ -265,34 +275,41 @@ public class HttpURLConnectionImpl extends HttpURLConnection { Retry retry = processResponseHeaders(); if (retry == Retry.NONE) { + httpEngine.automaticallyReleaseConnectionToPool(); break; } /* * The first request was insufficient. Prepare for another... */ + String retryMethod = method; OutputStream requestBody = httpEngine.getRequestBody(); + + /* + * Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM + * redirect should keep the same method, Chrome, Firefox and the + * RI all issue GETs when following any redirect. + */ + int responseCode = getResponseCode(); + if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM + || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) { + retryMethod = HttpEngine.GET; + requestBody = null; + } + if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) { throw new HttpRetryException("Cannot retry streamed HTTP body", - httpEngine.getResponseHeaders().getResponseCode()); + httpEngine.getResponseCode()); } - if (retry == Retry.SAME_CONNECTION && httpEngine.hasConnectionCloseHeaders()) { - retry = Retry.NEW_CONNECTION; + if (retry == Retry.DIFFERENT_CONNECTION) { + httpEngine.automaticallyReleaseConnectionToPool(); } - HttpConnection connection = null; - if (retry == Retry.NEW_CONNECTION) { - httpEngine.discardResponseBody(); - httpEngine.releaseSocket(true); - } else { - httpEngine.dontReleaseSocketToPool(); - httpEngine.discardResponseBody(); - connection = httpEngine.getConnection(); - } + httpEngine.release(true); - httpEngine = newHttpEngine(method, rawRequestHeaders, connection, - (RetryableOutputStream) requestBody); + httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, + httpEngine.getConnection(), (RetryableOutputStream) requestBody); } return httpEngine; } catch (IOException e) { @@ -301,10 +318,14 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } } + HttpEngine getHttpEngine() { + return httpEngine; + } + enum Retry { NONE, SAME_CONNECTION, - NEW_CONNECTION + DIFFERENT_CONNECTION } /** @@ -313,49 +334,32 @@ public class HttpURLConnectionImpl extends HttpURLConnection { * 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 ? + switch (getResponseCode()) { + case HTTP_PROXY_AUTH: 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"); + // fall-through + case HTTP_UNAUTHORIZED: + boolean credentialsFound = processAuthHeader(getResponseCode(), + httpEngine.getResponseHeaders(), rawRequestHeaders); + return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE; case HTTP_MULT_CHOICE: case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: - 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"); + 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())) { @@ -365,9 +369,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { && previousUrl.getEffectivePort() == url.getEffectivePort()) { return Retry.SAME_CONNECTION; } else { - // TODO: strip cookies? - rawRequestHeaders.removeAll("Host"); - return Retry.NEW_CONNECTION; + return Retry.DIFFERENT_CONNECTION; } default: @@ -375,37 +377,36 @@ public class HttpURLConnectionImpl extends HttpURLConnection { } } - 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. + * + * @return true if credentials have been added to successorRequestHeaders + * and another request should be attempted. */ - private Retry processAuthHeader(String fieldName, String value) throws IOException { + final boolean processAuthHeader(int responseCode, ResponseHeaders response, + RawHeaders successorRequestHeaders) throws IOException { + if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) { + throw new IllegalArgumentException(); + } + // keep asking for username/password until authorized - String challenge = httpEngine.getResponseHeaders().get(fieldName); + String challenge = responseCode == HTTP_PROXY_AUTH + ? response.proxyAuthenticate + : response.wwwAuthenticate; if (challenge == null) { throw new IOException("Received authentication challenge is null"); } String credentials = getAuthorizationCredentials(challenge); if (credentials == null) { - return Retry.NONE; // could not find credentials, end request cycle + return false; // could not find credentials, end request cycle } + // add authorization credentials, bypassing the already-connected check - rawRequestHeaders.set(value, credentials); - return Retry.SAME_CONNECTION; + String fieldName = responseCode == HTTP_PROXY_AUTH + ? "Proxy-Authorization" + : "Authorization"; + successorRequestHeaders.set(fieldName, credentials); + return true; } /** @@ -434,7 +435,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection { // base64 encode the username and password String usernameAndPassword = pa.getUserName() + ":" + new String(pa.getPassword()); byte[] bytes = usernameAndPassword.getBytes(Charsets.ISO_8859_1); - String encoded = Base64.encode(bytes, Charsets.ISO_8859_1); + String encoded = Base64.encode(bytes); return scheme + " " + encoded; } @@ -466,23 +467,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection { 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(); + return getResponse().getResponseHeaders().headers.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))); + return getResponse().getResponseCode(); } @Override public final void setRequestProperty(String field, String newValue) { diff --git a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java index 46f74f4..e385c9e 100644 --- a/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java +++ b/luni/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java @@ -57,6 +57,10 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { } } + HttpEngine getHttpEngine() { + return delegate.getHttpEngine(); + } + @Override public String getCipherSuite() { SecureCacheResponse cacheResponse = delegate.getCacheResponse(); @@ -382,7 +386,8 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { } public SecureCacheResponse getCacheResponse() { - return (SecureCacheResponse) httpEngine.getCacheResponse(); + HttpsEngine engine = (HttpsEngine) httpEngine; + return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null; } public SSLSocket getSSLSocket() { @@ -432,7 +437,7 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { && e.getCause() instanceof CertificateException) { throw e; } - releaseSocket(false); + release(false); connectionReused = makeSslConnection(false); } @@ -473,11 +478,36 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { return false; } + /** + * To make an HTTPS connection over an HTTP proxy, send an unencrypted + * CONNECT request to create the proxy connection. This may need to be + * retried if the proxy requires authorization. + */ private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection, - RawHeaders requestHeaders) throws IOException { - HttpEngine connect = new ProxyConnectEngine(policy, requestHeaders, connection); - connect.sendRequest(); - connect.readResponse(); + RequestHeaders requestHeaders) throws IOException { + RawHeaders rawRequestHeaders = requestHeaders.headers; + while (true) { + HttpEngine connect = new ProxyConnectEngine(policy, rawRequestHeaders, connection); + connect.sendRequest(); + connect.readResponse(); + + int responseCode = connect.getResponseCode(); + switch (connect.getResponseCode()) { + case HTTP_OK: + return; + case HTTP_PROXY_AUTH: + rawRequestHeaders = new RawHeaders(rawRequestHeaders); + boolean credentialsFound = policy.processAuthHeader(HTTP_PROXY_AUTH, + connect.getResponseHeaders(), rawRequestHeaders); + if (credentialsFound) { + continue; + } else { + throw new IOException("Failed to authenticate with proxy"); + } + default: + throw new IOException("Unexpected response code for CONNECT: " + responseCode); + } + } } @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { @@ -497,8 +527,7 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { 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; + super(policy, HttpEngine.CONNECT, requestHeaders, connection, null); } /** @@ -507,7 +536,7 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { * sensitive data like HTTP cookies to the proxy unencrypted. */ @Override protected RawHeaders getNetworkRequestHeaders() throws IOException { - RawHeaders privateHeaders = getRequestHeaders(); + RequestHeaders privateHeaders = getRequestHeaders(); URL url = policy.getURL(); RawHeaders result = new RawHeaders(); @@ -515,20 +544,20 @@ final class HttpsURLConnectionImpl extends HttpsURLConnection { + " HTTP/1.1"); // Always set Host and User-Agent. - String host = privateHeaders.get("Host"); + String host = privateHeaders.host; if (host == null) { host = getOriginAddress(url); } result.set("Host", host); - String userAgent = privateHeaders.get("User-Agent"); + String userAgent = privateHeaders.userAgent; 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"); + String proxyAuthorization = privateHeaders.proxyAuthorization; if (proxyAuthorization != null) { result.set("Proxy-Authorization", proxyAuthorization); } diff --git a/luni/src/main/java/libcore/net/http/RawHeaders.java b/luni/src/main/java/libcore/net/http/RawHeaders.java index a6ec3c3..75a1392 100644 --- a/luni/src/main/java/libcore/net/http/RawHeaders.java +++ b/luni/src/main/java/libcore/net/http/RawHeaders.java @@ -23,6 +23,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.TreeMap; /** @@ -41,9 +42,9 @@ import java.util.TreeMap; * <p>This class trims whitespace from values. It never returns values with * leading or trailing whitespace. */ -final class RawHeaders implements Cloneable { - +final class RawHeaders { private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() { + @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ") @Override public int compare(String a, String b) { if (a == b) { return 0; @@ -78,6 +79,7 @@ final class RawHeaders implements Cloneable { * (like "GET / HTTP/1.1"). */ public void setStatusLine(String statusLine) { + statusLine = statusLine.trim(); this.statusLine = statusLine; if (statusLine == null || !statusLine.startsWith("HTTP/")) { @@ -128,6 +130,19 @@ final class RawHeaders implements Cloneable { } /** + * Add an HTTP header line containing a field name, a literal colon, and a + * value. + */ + public void addLine(String line) { + int index = line.indexOf(":"); + if (index == -1) { + add("", line); + } else { + add(line.substring(0, index), line.substring(index + 1)); + } + } + + /** * Add a field with the specified value. */ public void add(String fieldName, String value) { @@ -162,12 +177,6 @@ final class RawHeaders implements Cloneable { } } - 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. @@ -218,6 +227,20 @@ final class RawHeaders implements Cloneable { return null; } + /** + * @param fieldNames a case-insensitive set of HTTP header field names. + */ + public RawHeaders getAll(Set<String> fieldNames) { + RawHeaders result = new RawHeaders(); + for (int i = 0; i < namesAndValues.size(); i += 2) { + String fieldName = namesAndValues.get(i); + if (fieldNames.contains(fieldName)) { + result.add(fieldName, namesAndValues.get(i + 1)); + } + } + return result; + } + public String toHeaderString() { StringBuilder result = new StringBuilder(256); result.append(statusLine).append("\r\n"); diff --git a/luni/src/main/java/libcore/net/http/RequestHeaders.java b/luni/src/main/java/libcore/net/http/RequestHeaders.java index ce9354b..b143f81 100644 --- a/luni/src/main/java/libcore/net/http/RequestHeaders.java +++ b/luni/src/main/java/libcore/net/http/RequestHeaders.java @@ -17,6 +17,9 @@ package libcore.net.http; import java.net.URI; +import java.util.Date; +import java.util.List; +import java.util.Map; /** * Parsed HTTP request headers. @@ -39,14 +42,6 @@ final class RequestHeaders { * 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 @@ -55,6 +50,17 @@ final class RequestHeaders { */ boolean hasAuthorization; + int contentLength = -1; + String transferEncoding; + String userAgent; + String host; + String connection; + String acceptEncoding; + String contentType; + String ifModifiedSince; + String ifNoneMatch; + String proxyAuthorization; + public RequestHeaders(URI uri, RawHeaders headers) { this.uri = uri; this.headers = headers; @@ -63,7 +69,6 @@ final class RequestHeaders { @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")) { @@ -86,11 +91,129 @@ final class RequestHeaders { noCache = true; } } else if ("If-None-Match".equalsIgnoreCase(fieldName)) { - hasConditions = true; + ifNoneMatch = value; } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) { - hasConditions = true; + ifModifiedSince = value; } else if ("Authorization".equalsIgnoreCase(fieldName)) { hasAuthorization = true; + } else if ("Content-Length".equalsIgnoreCase(fieldName)) { + try { + contentLength = Integer.parseInt(value); + } catch (NumberFormatException ignored) { + } + } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { + transferEncoding = value; + } else if ("User-Agent".equalsIgnoreCase(fieldName)) { + userAgent = value; + } else if ("Host".equalsIgnoreCase(fieldName)) { + host = value; + } else if ("Connection".equalsIgnoreCase(fieldName)) { + connection = value; + } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) { + acceptEncoding = value; + } else if ("Content-Type".equalsIgnoreCase(fieldName)) { + contentType = value; + } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) { + proxyAuthorization = value; + } + } + } + + public boolean isChunked() { + return "chunked".equalsIgnoreCase(transferEncoding); + } + + public boolean hasConnectionClose() { + return "close".equalsIgnoreCase(connection); + } + + public void setChunked() { + if (this.transferEncoding != null) { + headers.removeAll("Transfer-Encoding"); + } + headers.add("Transfer-Encoding", "chunked"); + this.transferEncoding = "chunked"; + } + + public void setContentLength(int contentLength) { + if (this.contentLength != -1) { + headers.removeAll("Content-Length"); + } + headers.add("Content-Length", Integer.toString(contentLength)); + this.contentLength = contentLength; + } + + public void setUserAgent(String userAgent) { + if (this.userAgent != null) { + headers.removeAll("User-Agent"); + } + headers.add("User-Agent", userAgent); + this.userAgent = userAgent; + } + + public void setHost(String host) { + if (this.host != null) { + headers.removeAll("Host"); + } + headers.add("Host", host); + this.host = host; + } + + public void setConnection(String connection) { + if (this.connection != null) { + headers.removeAll("Connection"); + } + headers.add("Connection", connection); + this.connection = connection; + } + + public void setAcceptEncoding(String acceptEncoding) { + if (this.acceptEncoding != null) { + headers.removeAll("Accept-Encoding"); + } + headers.add("Accept-Encoding", acceptEncoding); + this.acceptEncoding = acceptEncoding; + } + + public void setContentType(String contentType) { + if (this.contentType != null) { + headers.removeAll("Content-Type"); + } + headers.add("Content-Type", contentType); + this.contentType = contentType; + } + + public void setIfModifiedSince(Date date) { + if (ifModifiedSince != null) { + headers.removeAll("If-Modified-Since"); + } + String formattedDate = HttpDate.format(date); + headers.add("If-Modified-Since", formattedDate); + ifModifiedSince = formattedDate; + } + + public void setIfNoneMatch(String ifNoneMatch) { + if (this.ifNoneMatch != null) { + headers.removeAll("If-None-Match"); + } + headers.add("If-None-Match", ifNoneMatch); + this.ifNoneMatch = ifNoneMatch; + } + + /** + * Returns true if the request contains conditions that save the server from + * sending a response that the client has locally. When the caller adds + * conditions, this cache won't participate in the request. + */ + public boolean hasConditions() { + return ifModifiedSince != null || ifNoneMatch != null; + } + + public void addCookies(Map<String, List<String>> allCookieHeaders) { + for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) { + String key = entry.getKey(); + if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) { + headers.addAll(key, entry.getValue()); } } } diff --git a/luni/src/main/java/libcore/net/http/ResponseHeaders.java b/luni/src/main/java/libcore/net/http/ResponseHeaders.java index 2ee5a58..0764a53 100644 --- a/luni/src/main/java/libcore/net/http/ResponseHeaders.java +++ b/luni/src/main/java/libcore/net/http/ResponseHeaders.java @@ -18,8 +18,14 @@ package libcore.net.http; import java.net.HttpURLConnection; import java.net.URI; +import java.util.Collections; import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.TimeUnit; +import libcore.util.Objects; /** * Parsed HTTP response headers. @@ -27,10 +33,10 @@ import java.util.concurrent.TimeUnit; 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"; + private 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"; + private static final String RECEIVED_MILLIS = "X-Android-Received-Millis"; final URI uri; final RawHeaders headers; @@ -91,11 +97,20 @@ final class ResponseHeaders { * not permitted if this header is set. */ boolean isPublic; - String privateField; boolean mustRevalidate; String etag; int ageSeconds = -1; + /** Case-insensitive set of field names. */ + Set<String> varyFields = Collections.emptySet(); + + String contentEncoding; + String transferEncoding; + int contentLength = -1; + String connection; + String proxyAuthenticate; + String wwwAuthenticate; + public ResponseHeaders(URI uri, RawHeaders headers) { this.uri = uri; this.headers = headers; @@ -112,8 +127,6 @@ final class ResponseHeaders { 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; } @@ -139,6 +152,29 @@ final class ResponseHeaders { } } else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HeaderParser.parseSeconds(value); + } else if ("Vary".equalsIgnoreCase(fieldName)) { + // Replace the immutable empty set with something we can mutate. + if (varyFields.isEmpty()) { + varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); + } + for (String varyField : value.split(",")) { + varyFields.add(varyField.trim()); + } + } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) { + contentEncoding = value; + } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { + transferEncoding = value; + } else if ("Content-Length".equalsIgnoreCase(fieldName)) { + try { + contentLength = Integer.parseInt(value); + } catch (NumberFormatException ignored) { + } + } else if ("Connection".equalsIgnoreCase(fieldName)) { + connection = value; + } else if ("Proxy-Authenticate".equalsIgnoreCase(fieldName)) { + proxyAuthenticate = value; + } else if ("WWW-Authenticate".equalsIgnoreCase(fieldName)) { + wwwAuthenticate = value; } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) { sentRequestMillis = Long.parseLong(value); } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) { @@ -147,6 +183,30 @@ final class ResponseHeaders { } } + public boolean isContentEncodingGzip() { + return "gzip".equalsIgnoreCase(contentEncoding); + } + + public void stripContentEncoding() { + contentEncoding = null; + headers.removeAll("Content-Encoding"); + } + + public boolean isChunked() { + return "chunked".equalsIgnoreCase(transferEncoding); + } + + public boolean hasConnectionClose() { + return "close".equalsIgnoreCase(connection); + } + + public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) { + this.sentRequestMillis = sentRequestMillis; + headers.add(SENT_MILLIS, Long.toString(sentRequestMillis)); + this.receivedResponseMillis = receivedResponseMillis; + headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis)); + } + /** * Returns the current age of the response, in milliseconds. The calculation * is specified by RFC 2616, 13.2.3 Age Calculations. @@ -234,6 +294,28 @@ final class ResponseHeaders { } /** + * Returns true if a Vary header contains an asterisk. Such responses cannot + * be cached. + */ + public boolean hasVaryAll() { + return varyFields.contains("*"); + } + + /** + * Returns true if none of the Vary headers on this response have changed + * between {@code cachedRequest} and {@code newRequest}. + */ + public boolean varyMatches(Map<String, List<String>> cachedRequest, + Map<String, List<String>> newRequest) { + for (String field : varyFields) { + if (!Objects.equal(cachedRequest.get(field), newRequest.get(field))) { + return false; + } + } + return true; + } + + /** * Returns the source to satisfy {@code request} given this cached response. */ public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) { @@ -246,7 +328,7 @@ final class ResponseHeaders { return ResponseSource.NETWORK; } - if (request.noCache || request.hasConditions) { + if (request.noCache || request.hasConditions()) { return ResponseSource.NETWORK; } @@ -264,36 +346,31 @@ final class ResponseHeaders { } long maxStaleMillis = 0; - if (request.maxStaleSeconds != -1) { + if (!mustRevalidate && 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\""); + 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\""); + 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; + request.setIfModifiedSince(lastModified); } else if (servedDate != null) { - request.headers.add("If-Modified-Since", HttpDate.format(servedDate)); - request.hasConditions = true; + request.setIfModifiedSince(servedDate); } if (etag != null) { - request.headers.add("If-None-Match", etag); - request.hasConditions = true; + request.setIfNoneMatch(etag); } - return request.hasConditions + return request.hasConditions() ? ResponseSource.CONDITIONAL_CACHE : ResponseSource.NETWORK; } @@ -320,4 +397,47 @@ final class ResponseHeaders { return false; } + + /** + * Combines this cached header with a network header as defined by RFC 2616, + * 13.5.3. + */ + public ResponseHeaders combine(ResponseHeaders network) { + RawHeaders result = new RawHeaders(); + + for (int i = 0; i < headers.length(); i++) { + String fieldName = headers.getFieldName(i); + String value = headers.getValue(i); + if (fieldName.equals("Warning") && value.startsWith("1")) { + continue; // drop 100-level freshness warnings + } + if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) { + result.add(fieldName, value); + } + } + + for (int i = 0; i < network.headers.length(); i++) { + String fieldName = network.headers.getFieldName(i); + if (isEndToEnd(fieldName)) { + result.add(fieldName, network.headers.getValue(i)); + } + } + + return new ResponseHeaders(uri, result); + } + + /** + * Returns true if {@code fieldName} is an end-to-end HTTP header, as + * defined by RFC 2616, 13.5.1. + */ + private static boolean isEndToEnd(String fieldName) { + return !fieldName.equalsIgnoreCase("Connection") + && !fieldName.equalsIgnoreCase("Keep-Alive") + && !fieldName.equalsIgnoreCase("Proxy-Authenticate") + && !fieldName.equalsIgnoreCase("Proxy-Authorization") + && !fieldName.equalsIgnoreCase("TE") + && !fieldName.equalsIgnoreCase("Trailers") + && !fieldName.equalsIgnoreCase("Transfer-Encoding") + && !fieldName.equalsIgnoreCase("Upgrade"); + } } diff --git a/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java b/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java index 4667416..3fd13f5 100644 --- a/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java +++ b/luni/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java @@ -68,7 +68,7 @@ public final class AnnotationFactory implements InvocationHandler, Serializable Method[] m = annotationType.getDeclaredMethods(); desc = new AnnotationMember[m.length]; int idx = 0; - for(Method element : m) { + for (Method element : m) { String name = element.getName(); Class<?> type = element.getReturnType(); try { @@ -260,7 +260,7 @@ public final class AnnotationFactory implements InvocationHandler, Serializable result.append('@'); result.append(klazz.getName()); result.append('('); - for(int i = 0; i < elements.length; ++i) { + for (int i = 0; i < elements.length; ++i) { if (i != 0) { result.append(", "); } 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 deleted file mode 100644 index 485d722..0000000 --- a/luni/src/main/java/org/apache/harmony/luni/platform/INetworkSystem.java +++ /dev/null @@ -1,100 +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 org.apache.harmony.luni.platform; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.InetAddress; -import java.net.SocketException; -import java.net.SocketImpl; - -/* - * The interface for network methods. - */ -public interface INetworkSystem { - public void accept(FileDescriptor serverFd, SocketImpl newSocket, FileDescriptor clientFd) - throws IOException; - - public void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException; - - public int read(FileDescriptor fd, byte[] data, int offset, int count) throws IOException; - - public int readDirect(FileDescriptor fd, int address, int count) throws IOException; - - public int write(FileDescriptor fd, byte[] data, int offset, int count) throws IOException; - - public int writeDirect(FileDescriptor fd, int address, int offset, int count) 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, - int port, InetAddress inetAddress) throws IOException; - public int sendDirect(FileDescriptor fd, int address, int offset, int length, - int port, InetAddress inetAddress) throws IOException; - - public int recv(FileDescriptor fd, DatagramPacket packet, byte[] data, int offset, - int length, boolean peek, boolean connected) throws IOException; - public int recvDirect(FileDescriptor fd, DatagramPacket packet, int address, int offset, - int length, boolean peek, boolean connected) throws IOException; - - public void disconnectDatagram(FileDescriptor fd) throws SocketException; - - public void sendUrgentData(FileDescriptor fd, byte value); - - /** - * Select the given file descriptors for read and write operations. - * - * <p>The first {@code numReadable} file descriptors of {@code readFDs} will - * be selected for read-ready operations. The first {@code numWritable} file - * descriptors in {@code writeFDs} will be selected for write-ready - * operations. A file descriptor can appear in either or both and must not - * be null. If the file descriptor is closed during the select the behavior - * depends upon the underlying OS. - * - * @param readFDs - * all sockets interested in read and accept - * @param writeFDs - * all sockets interested in write and connect - * @param numReadable - * the size of the subset of readFDs to read or accept. - * @param numWritable - * the size of the subset of writeFDs to write or connect - * @param timeout - * timeout in milliseconds - * @param flags - * for output. Length must be at least {@code numReadable - * + numWritable}. Upon returning, each element describes the - * state of the descriptor in the corresponding read or write - * array. See {@code SelectorImpl.READABLE} and {@code - * SelectorImpl.WRITEABLE} - * @return true - * unless selection timed out or was interrupted - * @throws SocketException - */ - public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs, - int numReadable, int numWritable, long timeout, int[] flags) - throws SocketException; - - /** - * It is an error to close the same file descriptor from multiple threads - * concurrently. - */ - public void close(FileDescriptor fd) throws IOException; -} 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 deleted file mode 100644 index e98b41c..0000000 --- a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java +++ /dev/null @@ -1,97 +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 org.apache.harmony.luni.platform; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.InetAddress; -import java.net.SocketException; -import java.net.SocketImpl; - -/** - * This wraps native code that implements the INetworkSystem interface. - * Address length was changed from long to int for performance reasons. - */ -final class OSNetworkSystem implements INetworkSystem { - private static final OSNetworkSystem singleton = new OSNetworkSystem(); - - public static OSNetworkSystem getOSNetworkSystem() { - return singleton; - } - - private OSNetworkSystem() { - } - - public native void accept(FileDescriptor serverFd, SocketImpl newSocket, - FileDescriptor clientFd) throws IOException; - - public native void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException; - - 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; - - public native int read(FileDescriptor fd, byte[] data, int offset, int count) - throws IOException; - - public native int readDirect(FileDescriptor fd, int address, int count) throws IOException; - - public native int recv(FileDescriptor fd, DatagramPacket packet, - byte[] data, int offset, int length, - boolean peek, boolean connected) throws IOException; - - public native int recvDirect(FileDescriptor fd, DatagramPacket packet, - int address, int offset, int length, - boolean peek, boolean connected) throws IOException; - - public boolean select(FileDescriptor[] readFDs, FileDescriptor[] writeFDs, - int numReadable, int numWritable, long timeout, int[] flags) - throws SocketException { - if (numReadable < 0 || numWritable < 0) { - throw new IllegalArgumentException(); - } - - int total = numReadable + numWritable; - if (total == 0) { - return true; - } - - return selectImpl(readFDs, writeFDs, numReadable, numWritable, flags, timeout); - } - - static native boolean selectImpl(FileDescriptor[] readfd, - FileDescriptor[] writefd, int cread, int cwirte, int[] flags, - long timeout); - - public native int send(FileDescriptor fd, byte[] data, int offset, int length, - int port, InetAddress inetAddress) throws IOException; - public native int sendDirect(FileDescriptor fd, int address, int offset, int length, - int port, InetAddress inetAddress) throws IOException; - - public native void sendUrgentData(FileDescriptor fd, byte value); - - public native void close(FileDescriptor fd) throws IOException; - - public native int write(FileDescriptor fd, byte[] data, int offset, int count) - throws IOException; - - public native int writeDirect(FileDescriptor fd, int address, int offset, int count) - throws IOException; -} diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/Platform.java b/luni/src/main/java/org/apache/harmony/luni/platform/Platform.java deleted file mode 100644 index db79a59..0000000 --- a/luni/src/main/java/org/apache/harmony/luni/platform/Platform.java +++ /dev/null @@ -1,43 +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 org.apache.harmony.luni.platform; - -import dalvik.system.BlockGuard; -import dalvik.system.VMStack; - -/** - * The Platform class gives access to the low-level underlying capabilities of - * the operating system. - * - * The platform is structured into operations on the process heap memory, - * network subsystem, and file system through different OS components. - * - * OS components are 'dangerous' in that they pass through the calls and - * arguments to the OS with very little checking, and as such may cause fatal - * exceptions in the runtime. Access to the OS components is restricted to - * trusted code running on the system classpath. - * - * @see INetworkSystem - */ -public class Platform { - // BlockGuard-policy-free threads should have no extra overhead, but for - // now they do because ThreadLocal lookups will be done on most operations, which - // should be relatively less than the speed of the operation. - // TODO: measure & fix if needed. - public static final INetworkSystem NETWORK = - new BlockGuard.WrappedNetworkSystem(OSNetworkSystem.getOSNetworkSystem()); -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java index ac76a89..7207002 100644 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java +++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java @@ -36,8 +36,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; +import libcore.io.Base64; import libcore.io.Streams; -import org.apache.harmony.luni.util.Base64; import org.apache.harmony.security.asn1.ASN1Constants; import org.apache.harmony.security.asn1.BerInputStream; import org.apache.harmony.security.pkcs7.ContentInfo; diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java index a144727..35bcb39 100644 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java +++ b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java @@ -90,10 +90,6 @@ public final class X509CertImpl extends X509Certificate { // encoding of the certificate private volatile byte[] encoding; - // - // ---------------------- Constructors ------------------------------- - // - /** * Constructs the instance on the base of ASN.1 encoded * form of X.509 certificate provided via stream parameter. @@ -133,10 +129,6 @@ public final class X509CertImpl extends X509Certificate { this((Certificate) Certificate.ASN1.decode(encoding)); } - // - // ----------------- Public methods implementations ------------------ - // - public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException { checkValidity(System.currentTimeMillis()); @@ -351,11 +343,7 @@ public final class X509CertImpl extends X509Certificate { } } - // - // ----- java.security.cert.Certificate methods implementations ------ - // - - public byte[] getEncoded() throws CertificateEncodingException { + @Override public byte[] getEncoded() throws CertificateEncodingException { return getEncodedInternal().clone(); } private byte[] getEncodedInternal() throws CertificateEncodingException { @@ -366,7 +354,7 @@ public final class X509CertImpl extends X509Certificate { return result; } - public PublicKey getPublicKey() { + @Override public PublicKey getPublicKey() { PublicKey result = publicKey; if (result == null) { publicKey = result = tbsCert.getSubjectPublicKeyInfo().getPublicKey(); @@ -374,15 +362,14 @@ public final class X509CertImpl extends X509Certificate { return result; } - public String toString() { + @Override public String toString() { return certificate.toString(); } - public void verify(PublicKey key) - throws CertificateException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, - SignatureException { - if (getSigAlgName().endsWith("withRSA")) { + @Override public void verify(PublicKey key) + throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + if (getSigAlgName().endsWith("withRSA") || getSigAlgName().endsWith("WithRSAEncryption")) { fastVerify(key); return; } @@ -398,11 +385,11 @@ public final class X509CertImpl extends X509Certificate { } } - public void verify(PublicKey key, String sigProvider) - throws CertificateException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, - SignatureException { - if (getSigAlgName().endsWith("withRSA") && sigProvider == null) { + @Override public void verify(PublicKey key, String sigProvider) + throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + if ((getSigAlgName().endsWith("withRSA") || getSigAlgName().endsWith("WithRSAEncryption")) + && sigProvider == null) { fastVerify(key); return; } @@ -457,11 +444,7 @@ public final class X509CertImpl extends X509Certificate { } } - // - // ----- java.security.cert.X509Extension methods implementations ---- - // - - public Set<String> getNonCriticalExtensionOIDs() { + @Override public Set<String> getNonCriticalExtensionOIDs() { if (extensions == null) { return null; } @@ -469,7 +452,7 @@ public final class X509CertImpl extends X509Certificate { return extensions.getNonCriticalExtensions(); } - public Set<String> getCriticalExtensionOIDs() { + @Override public Set<String> getCriticalExtensionOIDs() { if (extensions == null) { return null; } @@ -477,7 +460,7 @@ public final class X509CertImpl extends X509Certificate { return extensions.getCriticalExtensions(); } - public byte[] getExtensionValue(String oid) { + @Override public byte[] getExtensionValue(String oid) { if (extensions == null) { return null; } @@ -486,7 +469,7 @@ public final class X509CertImpl extends X509Certificate { return (ext == null) ? null : ext.getRawExtnValue(); } - public boolean hasUnsupportedCriticalExtension() { + @Override public boolean hasUnsupportedCriticalExtension() { if (extensions == null) { return false; } diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java index 42d7f0e..28aeef5 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java @@ -313,9 +313,6 @@ public class OpenSSLSessionImpl implements SSLSession { /** * Returns a string identifier of the crypto tools used in the actual SSL * session. For example AES_256_WITH_MD5. - * - * @return an identifier for all the cryptographic algorithms used in the - * actual SSL session. */ public String getCipherSuite() { if (cipherSuite == null) { @@ -331,10 +328,6 @@ public class OpenSSLSessionImpl implements SSLSession { /** * Returns the standard version name of the SSL protocol used in all * connections pertaining to this SSL session. - * - * @return the standard version name of the SSL protocol used in all - * connections pertaining to this SSL session. - * */ public String getProtocol() { if (protocol == null) { @@ -346,10 +339,6 @@ public class OpenSSLSessionImpl implements SSLSession { /** * Returns the compression method name used in all connections * pertaining to this SSL session. - * - * @return the compresison method used in all connections - * pertaining to this SSL session. - * */ public String getCompressionMethod() { if (compressionMethod == null) { diff --git a/luni/src/main/native/JniConstants.cpp b/luni/src/main/native/JniConstants.cpp index 2681b47..ebdad0c 100644 --- a/luni/src/main/native/JniConstants.cpp +++ b/luni/src/main/native/JniConstants.cpp @@ -25,7 +25,6 @@ jclass JniConstants::byteArrayClass; jclass JniConstants::byteClass; jclass JniConstants::charsetICUClass; jclass JniConstants::constructorClass; -jclass JniConstants::datagramPacketClass; jclass JniConstants::deflaterClass; jclass JniConstants::doubleClass; jclass JniConstants::errnoExceptionClass; @@ -33,6 +32,7 @@ jclass JniConstants::fieldClass; jclass JniConstants::fieldPositionIteratorClass; jclass JniConstants::fileDescriptorClass; jclass JniConstants::gaiExceptionClass; +jclass JniConstants::inet6AddressClass; jclass JniConstants::inetAddressClass; jclass JniConstants::inetSocketAddressClass; jclass JniConstants::inflaterClass; @@ -53,6 +53,8 @@ jclass JniConstants::structAddrinfoClass; jclass JniConstants::structFlockClass; jclass JniConstants::structGroupReqClass; jclass JniConstants::structLingerClass; +jclass JniConstants::structPasswdClass; +jclass JniConstants::structPollfdClass; jclass JniConstants::structStatClass; jclass JniConstants::structStatFsClass; jclass JniConstants::structTimevalClass; @@ -75,7 +77,6 @@ void JniConstants::init(JNIEnv* env) { byteArrayClass = findClass(env, "[B"); charsetICUClass = findClass(env, "java/nio/charset/CharsetICU"); constructorClass = findClass(env, "java/lang/reflect/Constructor"); - datagramPacketClass = findClass(env, "java/net/DatagramPacket"); deflaterClass = findClass(env, "java/util/zip/Deflater"); doubleClass = findClass(env, "java/lang/Double"); errnoExceptionClass = findClass(env, "libcore/io/ErrnoException"); @@ -83,6 +84,7 @@ void JniConstants::init(JNIEnv* env) { fieldPositionIteratorClass = findClass(env, "libcore/icu/NativeDecimalFormat$FieldPositionIterator"); fileDescriptorClass = findClass(env, "java/io/FileDescriptor"); gaiExceptionClass = findClass(env, "libcore/io/GaiException"); + inet6AddressClass = findClass(env, "java/net/Inet6Address"); inetAddressClass = findClass(env, "java/net/InetAddress"); inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress"); inflaterClass = findClass(env, "java/util/zip/Inflater"); @@ -103,6 +105,8 @@ void JniConstants::init(JNIEnv* env) { structFlockClass = findClass(env, "libcore/io/StructFlock"); structGroupReqClass = findClass(env, "libcore/io/StructGroupReq"); structLingerClass = findClass(env, "libcore/io/StructLinger"); + structPasswdClass = findClass(env, "libcore/io/StructPasswd"); + structPollfdClass = findClass(env, "libcore/io/StructPollfd"); structStatClass = findClass(env, "libcore/io/StructStat"); structStatFsClass = findClass(env, "libcore/io/StructStatFs"); structTimevalClass = findClass(env, "libcore/io/StructTimeval"); diff --git a/luni/src/main/native/JniConstants.h b/luni/src/main/native/JniConstants.h index af5056b..db52775 100644 --- a/luni/src/main/native/JniConstants.h +++ b/luni/src/main/native/JniConstants.h @@ -47,7 +47,6 @@ struct JniConstants { static jclass byteClass; static jclass charsetICUClass; static jclass constructorClass; - static jclass datagramPacketClass; static jclass deflaterClass; static jclass doubleClass; static jclass errnoExceptionClass; @@ -55,6 +54,7 @@ struct JniConstants { static jclass fieldPositionIteratorClass; static jclass fileDescriptorClass; static jclass gaiExceptionClass; + static jclass inet6AddressClass; static jclass inetAddressClass; static jclass inetSocketAddressClass; static jclass inflaterClass; @@ -75,6 +75,8 @@ struct JniConstants { static jclass structFlockClass; static jclass structGroupReqClass; static jclass structLingerClass; + static jclass structPasswdClass; + static jclass structPollfdClass; static jclass structStatClass; static jclass structStatFsClass; static jclass structTimevalClass; diff --git a/luni/src/main/native/NetworkUtilities.cpp b/luni/src/main/native/NetworkUtilities.cpp index 26df02d..1b2c11e 100644 --- a/luni/src/main/native/NetworkUtilities.cpp +++ b/luni/src/main/native/NetworkUtilities.cpp @@ -19,6 +19,7 @@ #include "NetworkUtilities.h" #include "JNIHelp.h" #include "JniConstants.h" +#include "ScopedLocalRef.h" #include <arpa/inet.h> #include <fcntl.h> @@ -26,42 +27,8 @@ #include <string.h> #include <sys/socket.h> -static bool byteArrayToSocketAddress(JNIEnv* env, jbyteArray byteArray, int port, sockaddr_storage* ss) { - if (byteArray == NULL) { - jniThrowNullPointerException(env, NULL); - return false; - } - - // Convert the IP address bytes to the proper IP address type. - size_t addressLength = env->GetArrayLength(byteArray); - memset(ss, 0, sizeof(*ss)); - if (addressLength == 4) { - // IPv4 address. - sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss); - sin->sin_family = AF_INET; - sin->sin_port = htons(port); - jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr); - env->GetByteArrayRegion(byteArray, 0, 4, dst); - } else if (addressLength == 16) { - // IPv6 address. - sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss); - sin6->sin6_family = AF_INET6; - sin6->sin6_port = htons(port); - jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr); - env->GetByteArrayRegion(byteArray, 0, 16, dst); - } else { - // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one - // really does imply an internal error. - // TODO: fix the code (native and Java) so we don't paint ourselves into this corner. - jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", - "byteArrayToSocketAddress bad array length (%i)", addressLength); - return false; - } - return true; -} - -static jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) { - // Convert IPv4-mapped addresses to IPv4 addresses. +jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage* ss, jint* port) { + // Convert IPv4-mapped IPv6 addresses to IPv4 addresses. // The RI states "Java will never return an IPv4-mapped address". sockaddr_storage tmp; memset(&tmp, 0, sizeof(tmp)); @@ -80,57 +47,117 @@ static jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* const void* rawAddress; size_t addressLength; + int sin_port; + int scope_id = 0; if (ss->ss_family == AF_INET) { const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(ss); rawAddress = &sin->sin_addr.s_addr; addressLength = 4; + sin_port = ntohs(sin->sin_port); } else if (ss->ss_family == AF_INET6) { const sockaddr_in6* sin6 = reinterpret_cast<const sockaddr_in6*>(ss); rawAddress = &sin6->sin6_addr.s6_addr; addressLength = 16; + sin_port = ntohs(sin6->sin6_port); + scope_id = sin6->sin6_scope_id; } else { // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one // really does imply an internal error. - // TODO: fix the code (native and Java) so we don't paint ourselves into this corner. jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", - "socketAddressToByteArray bad ss_family (%i)", ss->ss_family); + "sockaddrToInetAddress bad ss_family: %i", ss->ss_family); return NULL; } + if (port != NULL) { + *port = sin_port; + } jbyteArray byteArray = env->NewByteArray(addressLength); if (byteArray == NULL) { return NULL; } env->SetByteArrayRegion(byteArray, 0, addressLength, reinterpret_cast<const jbyte*>(rawAddress)); - return byteArray; -} -static jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) { - if (byteArray == NULL) { - return NULL; - } - jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inetAddressClass, - "getByAddress", "([B)Ljava/net/InetAddress;"); + static jmethodID getByAddressMethod = env->GetStaticMethodID(JniConstants::inetAddressClass, + "getByAddress", "(Ljava/lang/String;[BI)Ljava/net/InetAddress;"); if (getByAddressMethod == NULL) { return NULL; } - return env->CallStaticObjectMethod(JniConstants::inetAddressClass, getByAddressMethod, byteArray); + return env->CallStaticObjectMethod(JniConstants::inetAddressClass, getByAddressMethod, + NULL, byteArray, scope_id); } -jobject socketAddressToInetAddress(JNIEnv* env, const sockaddr_storage* ss) { - jbyteArray byteArray = socketAddressToByteArray(env, ss); - return byteArrayToInetAddress(env, byteArray); -} +static bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss, bool map) { + memset(ss, 0, sizeof(*ss)); -bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) { - // Get the byte array that stores the IP address bytes in the InetAddress. if (inetAddress == NULL) { jniThrowNullPointerException(env, NULL); return false; } - static jfieldID fid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B"); - jbyteArray addressBytes = reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, fid)); - return byteArrayToSocketAddress(env, addressBytes, port, ss); + + // Get the address family. + static jfieldID familyFid = env->GetFieldID(JniConstants::inetAddressClass, "family", "I"); + ss->ss_family = env->GetIntField(inetAddress, familyFid); + if (ss->ss_family == AF_UNSPEC) { + return true; // Job done! + } + + // Check this is an address family we support. + if (ss->ss_family != AF_INET && ss->ss_family != AF_INET6) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "inetAddressToSockaddr bad family: %i", ss->ss_family); + return false; + } + + // Get the byte array that stores the IP address bytes in the InetAddress. + static jfieldID bytesFid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B"); + ScopedLocalRef<jbyteArray> addressBytes(env, reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, bytesFid))); + if (addressBytes.get() == NULL) { + jniThrowNullPointerException(env, NULL); + return false; + } + + // We use AF_INET6 sockets, so we want an IPv6 address (which may be a IPv4-mapped address). + sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss); + sin6->sin6_port = htons(port); + if (ss->ss_family == AF_INET6) { + // IPv6 address. Copy the bytes... + jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr); + env->GetByteArrayRegion(addressBytes.get(), 0, 16, dst); + // ...and set the scope id... + static jfieldID scopeFid = env->GetFieldID(JniConstants::inet6AddressClass, "scope_id", "I"); + sin6->sin6_scope_id = env->GetIntField(inetAddress, scopeFid); + return true; + } + + // Deal with Inet4Address instances. + if (map) { + // We should represent this Inet4Address as an IPv4-mapped IPv6 sockaddr_in6. + // Change the family... + sin6->sin6_family = AF_INET6; + // Copy the bytes... + jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr[12]); + env->GetByteArrayRegion(addressBytes.get(), 0, 4, dst); + // INADDR_ANY and in6addr_any are both all-zeros... + if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + // ...but all other IPv4-mapped addresses are ::ffff:a.b.c.d, so insert the ffff... + memset(&(sin6->sin6_addr.s6_addr[10]), 0xff, 2); + } + } else { + // We should represent this Inet4Address as an IPv4 sockaddr_in. + sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss); + sin->sin_port = htons(port); + jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr); + env->GetByteArrayRegion(addressBytes.get(), 0, 4, dst); + } + return true; +} + +bool inetAddressToSockaddr_getnameinfo(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) { + return inetAddressToSockaddr(env, inetAddress, port, ss, false); +} + +bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) { + return inetAddressToSockaddr(env, inetAddress, port, ss, true); } bool setBlocking(int fd, bool blocking) { diff --git a/luni/src/main/native/NetworkUtilities.h b/luni/src/main/native/NetworkUtilities.h index 5e5b19e..8b1b6f1 100644 --- a/luni/src/main/native/NetworkUtilities.h +++ b/luni/src/main/native/NetworkUtilities.h @@ -17,11 +17,20 @@ #include "jni.h" #include <sys/socket.h> -// Convert from sockaddr_storage to InetAddress. -jobject socketAddressToInetAddress(JNIEnv* env, const sockaddr_storage* ss); +// Convert from sockaddr_storage to InetAddress and an optional int port. +jobject sockaddrToInetAddress(JNIEnv* env, const sockaddr_storage* ss, int* port); -// Convert from InetAddress to sockaddr_storage. -bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss); +// Convert from InetAddress to sockaddr_storage. An Inet4Address will be converted to +// an IPv4-mapped AF_INET6 sockaddr_in6. This is what you want if you're about to perform an +// operation on a socket, since all our sockets are AF_INET6. +bool inetAddressToSockaddr(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss); + +// Convert from InetAddress to sockaddr_storage. An Inet6Address will be converted to +// a sockaddr_in6 while an Inet4Address will be converted to a sockaddr_in. This is +// probably only useful for getnameinfo(2), where we'll be presenting the result to +// the user and the user may actually care whether the original address was pure IPv4 +// or an IPv4-mapped IPv6 address. +bool inetAddressToSockaddr_getnameinfo(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss); diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp index e518944..a637a73 100644 --- a/luni/src/main/native/Register.cpp +++ b/luni/src/main/native/Register.cpp @@ -29,6 +29,7 @@ extern int register_java_lang_Math(JNIEnv* env); extern int register_java_lang_ProcessManager(JNIEnv* env); extern int register_java_lang_RealToString(JNIEnv* env); extern int register_java_lang_StrictMath(JNIEnv* env); +extern int register_java_lang_StringToReal(JNIEnv* env); extern int register_java_lang_System(JNIEnv* env); extern int register_java_math_NativeBN(JNIEnv* env); extern int register_java_nio_ByteOrder(JNIEnv* env); @@ -49,13 +50,12 @@ extern int register_libcore_icu_NativeIDN(JNIEnv* env); extern int register_libcore_icu_NativeNormalizer(JNIEnv* env); extern int register_libcore_icu_NativePluralRules(JNIEnv* env); extern int register_libcore_icu_TimeZones(JNIEnv* env); +extern int register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env); extern int register_libcore_io_Memory(JNIEnv* env); extern int register_libcore_io_OsConstants(JNIEnv* env); extern int register_libcore_io_Posix(JNIEnv* env); extern int register_libcore_net_RawSocket(JNIEnv* env); extern int register_org_apache_harmony_dalvik_NativeTestTarget(JNIEnv* env); -extern int register_org_apache_harmony_luni_platform_OSNetworkSystem(JNIEnv* env); -extern int register_org_apache_harmony_luni_util_fltparse(JNIEnv* env); extern int register_org_apache_harmony_xml_ExpatParser(JNIEnv* env); extern int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env); @@ -74,6 +74,7 @@ int registerCoreLibrariesJni(JNIEnv* env) { register_java_lang_ProcessManager(env) != -1 && register_java_lang_RealToString(env) != -1 && register_java_lang_StrictMath(env) != -1 && + register_java_lang_StringToReal(env) != -1 && register_java_lang_System(env) != -1 && register_java_math_NativeBN(env) != -1 && register_java_nio_ByteOrder(env) != -1 && @@ -94,13 +95,12 @@ int registerCoreLibrariesJni(JNIEnv* env) { register_libcore_icu_NativeNormalizer(env) != -1 && register_libcore_icu_NativePluralRules(env) != -1 && register_libcore_icu_TimeZones(env) != -1 && + register_libcore_io_AsynchronousCloseMonitor(env) != -1 && register_libcore_io_Memory(env) != -1 && register_libcore_io_OsConstants(env) != -1 && register_libcore_io_Posix(env) != -1 && register_libcore_net_RawSocket(env) != -1 && register_org_apache_harmony_dalvik_NativeTestTarget(env) != -1 && - register_org_apache_harmony_luni_platform_OSNetworkSystem(env) != -1 && - register_org_apache_harmony_luni_util_fltparse(env) != -1 && register_org_apache_harmony_xml_ExpatParser(env) != -1 && register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(env) != -1 && true; diff --git a/luni/src/main/native/java_lang_ProcessManager.cpp b/luni/src/main/native/java_lang_ProcessManager.cpp index 2a1638f..3648a8f 100644 --- a/luni/src/main/native/java_lang_ProcessManager.cpp +++ b/luni/src/main/native/java_lang_ProcessManager.cpp @@ -18,10 +18,8 @@ #include <sys/resource.h> #include <sys/types.h> -#include <sys/wait.h> #include <unistd.h> #include <fcntl.h> -#include <signal.h> #include <stdlib.h> #include <string.h> #include <errno.h> @@ -29,90 +27,9 @@ #include "jni.h" #include "JNIHelp.h" #include "JniConstants.h" +#include "ScopedLocalRef.h" #include "utils/Log.h" -/* - * These are constants shared with the higher level code in - * ProcessManager.java. - */ -#define WAIT_STATUS_UNKNOWN (-1) // unknown child status -#define WAIT_STATUS_NO_CHILDREN (-2) // no children to wait for -#define WAIT_STATUS_STRANGE_ERRNO (-3) // observed an undocumented errno - -/** - * Loops indefinitely and calls ProcessManager.onExit() when children exit. - */ -static void ProcessManager_watchChildren(JNIEnv* env, jclass processManagerClass, jobject processManager) { - static jmethodID onExitMethod = env->GetMethodID(processManagerClass, "onExit", "(II)V"); - if (onExitMethod == NULL) { - return; - } - - while (true) { - // Wait for children in our process group. - int status; - pid_t pid = waitpid(0, &status, 0); - - if (pid >= 0) { - // Extract real status. - if (WIFEXITED(status)) { - status = WEXITSTATUS(status); - } else if (WIFSIGNALED(status)) { - status = WTERMSIG(status); - } else if (WIFSTOPPED(status)) { - status = WSTOPSIG(status); - } else { - status = WAIT_STATUS_UNKNOWN; - } - } else { - /* - * The pid should be -1 already, but force it here just in case - * we somehow end up with some other negative value. - */ - pid = -1; - - switch (errno) { - case ECHILD: { - /* - * Expected errno: There are no children to wait() - * for. The callback will sleep until it is - * informed of another child coming to life. - */ - status = WAIT_STATUS_NO_CHILDREN; - break; - } - case EINTR: { - /* - * An unblocked signal came in while waiting; just - * retry the wait(). - */ - continue; - } - default: { - /* - * Unexpected errno, so squawk! Note: Per the - * Linux docs, there are no errnos defined for - * wait() other than the two that are handled - * immediately above. - */ - LOGE("Error %d calling wait(): %s", errno, strerror(errno)); - status = WAIT_STATUS_STRANGE_ERRNO; - break; - } - } - } - - env->CallVoidMethod(processManager, onExitMethod, pid, status); - if (env->ExceptionOccurred()) { - /* - * The callback threw, so break out of the loop and return, - * letting the exception percolate up. - */ - break; - } - } -} - /** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */ static void closeNonStandardFds(int skipFd1, int skipFd2) { // TODO: rather than close all these non-open files, we could look in /proc/self/fd. @@ -274,11 +191,11 @@ static char** convertStrings(JNIEnv* env, jobjectArray javaArray) { jsize length = env->GetArrayLength(javaArray); char** array = new char*[length + 1]; array[length] = 0; - for (jsize index = 0; index < length; index++) { - jstring javaEntry = (jstring) env->GetObjectArrayElement(javaArray, index); + for (jsize i = 0; i < length; ++i) { + ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i))); // We need to pass these strings to const-unfriendly code. - char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry, NULL)); - array[index] = entry; + char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry.get(), NULL)); + array[i] = entry; } return array; @@ -291,9 +208,9 @@ static void freeStrings(JNIEnv* env, jobjectArray javaArray, char** array) { } jsize length = env->GetArrayLength(javaArray); - for (jsize index = 0; index < length; index++) { - jstring javaEntry = reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, index)); - env->ReleaseStringUTFChars(javaEntry, array[index]); + for (jsize i = 0; i < length; ++i) { + ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i))); + env->ReleaseStringUTFChars(javaEntry.get(), array[i]); } delete[] array; @@ -346,7 +263,6 @@ static pid_t ProcessManager_exec(JNIEnv* env, jclass, jobjectArray javaCommands, } static JNINativeMethod methods[] = { - 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_lang_RealToString.cpp b/luni/src/main/native/java_lang_RealToString.cpp index f6d50b2..835151e 100644 --- a/luni/src/main/native/java_lang_RealToString.cpp +++ b/luni/src/main/native/java_lang_RealToString.cpp @@ -23,6 +23,7 @@ #include "JNIHelp.h" #include "JniConstants.h" +#include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "cbigint.h" @@ -168,8 +169,8 @@ void RealToString_bigIntDigitGenerator(JNIEnv* env, jobject obj, jlong f, jint e } static jfieldID digitsFid = env->GetFieldID(JniConstants::realToStringClass, "digits", "[I"); - jintArray javaDigits = reinterpret_cast<jintArray>(env->GetObjectField(obj, digitsFid)); - ScopedIntArrayRW digits(env, javaDigits); + ScopedLocalRef<jintArray> javaDigits(env, reinterpret_cast<jintArray>(env->GetObjectField(obj, digitsFid))); + ScopedIntArrayRW digits(env, javaDigits.get()); if (digits.get() == NULL) { return; } diff --git a/luni/src/main/native/org_apache_harmony_luni_util_FloatingPointParser.cpp b/luni/src/main/native/java_lang_StringToReal.cpp index 70a6d4b..06aa076 100644 --- a/luni/src/main/native/org_apache_harmony_luni_util_FloatingPointParser.cpp +++ b/luni/src/main/native/java_lang_StringToReal.cpp @@ -989,7 +989,7 @@ OutOfMemory: return z; } -static jfloat FloatingPointParser_parseFltImpl(JNIEnv* env, jclass, jstring s, jint e) { +static jfloat StringToReal_parseFltImpl(JNIEnv* env, jclass, jstring s, jint e) { ScopedUtfChars str(env, s); if (str.c_str() == NULL) { return 0.0; @@ -997,7 +997,7 @@ static jfloat FloatingPointParser_parseFltImpl(JNIEnv* env, jclass, jstring s, j return createFloat(env, str.c_str(), e); } -static jdouble FloatingPointParser_parseDblImpl(JNIEnv* env, jclass, jstring s, jint e) { +static jdouble StringToReal_parseDblImpl(JNIEnv* env, jclass, jstring s, jint e) { ScopedUtfChars str(env, s); if (str.c_str() == NULL) { return 0.0; @@ -1006,10 +1006,9 @@ static jdouble FloatingPointParser_parseDblImpl(JNIEnv* env, jclass, jstring s, } static JNINativeMethod gMethods[] = { - NATIVE_METHOD(FloatingPointParser, parseFltImpl, "(Ljava/lang/String;I)F"), - NATIVE_METHOD(FloatingPointParser, parseDblImpl, "(Ljava/lang/String;I)D"), + NATIVE_METHOD(StringToReal, parseFltImpl, "(Ljava/lang/String;I)F"), + NATIVE_METHOD(StringToReal, parseDblImpl, "(Ljava/lang/String;I)D"), }; -int register_org_apache_harmony_luni_util_fltparse(JNIEnv* env) { - return jniRegisterNativeMethods(env, "org/apache/harmony/luni/util/FloatingPointParser", - gMethods, NELEM(gMethods)); +int register_java_lang_StringToReal(JNIEnv* env) { + return jniRegisterNativeMethods(env, "java/lang/StringToReal", gMethods, NELEM(gMethods)); } diff --git a/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp new file mode 100644 index 0000000..d3fdabf --- /dev/null +++ b/luni/src/main/native/libcore_io_AsynchronousCloseMonitor.cpp @@ -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. + */ + +#define LOG_TAG "AsynchronousCloseMonitor" + +#include "AsynchronousSocketCloseMonitor.h" +#include "JNIHelp.h" +#include "JniConstants.h" +#include "jni.h" + +static void AsynchronousCloseMonitor_signalBlockedThreads(JNIEnv* env, jclass, jobject javaFd) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + AsynchronousSocketCloseMonitor::signalBlockedThreads(fd); +} + +static JNINativeMethod gMethods[] = { + NATIVE_METHOD(AsynchronousCloseMonitor, signalBlockedThreads, "(Ljava/io/FileDescriptor;)V"), +}; + +int register_libcore_io_AsynchronousCloseMonitor(JNIEnv* env) { + AsynchronousSocketCloseMonitor::init(); + return jniRegisterNativeMethods(env, "libcore/io/AsynchronousCloseMonitor", gMethods, NELEM(gMethods)); +} diff --git a/luni/src/main/native/libcore_io_OsConstants.cpp b/luni/src/main/native/libcore_io_OsConstants.cpp index dd8ebf2..942cfce 100644 --- a/luni/src/main/native/libcore_io_OsConstants.cpp +++ b/luni/src/main/native/libcore_io_OsConstants.cpp @@ -18,7 +18,6 @@ #include "JNIHelp.h" #include "JniConstants.h" -#include "ScopedLocalRef.h" #include <errno.h> #include <fcntl.h> @@ -26,6 +25,7 @@ #include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.h> +#include <poll.h> #include <signal.h> #include <stdlib.h> #include <sys/ioctl.h> @@ -236,6 +236,16 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "O_SYNC", O_SYNC); initConstant(env, c, "O_TRUNC", O_TRUNC); initConstant(env, c, "O_WRONLY", O_WRONLY); + initConstant(env, c, "POLLERR", POLLERR); + initConstant(env, c, "POLLHUP", POLLHUP); + initConstant(env, c, "POLLIN", POLLIN); + initConstant(env, c, "POLLNVAL", POLLNVAL); + initConstant(env, c, "POLLOUT", POLLOUT); + initConstant(env, c, "POLLPRI", POLLPRI); + initConstant(env, c, "POLLRDBAND", POLLRDBAND); + initConstant(env, c, "POLLRDNORM", POLLRDNORM); + initConstant(env, c, "POLLWRBAND", POLLWRBAND); + initConstant(env, c, "POLLWRNORM", POLLWRNORM); initConstant(env, c, "PROT_EXEC", PROT_EXEC); initConstant(env, c, "PROT_NONE", PROT_NONE); initConstant(env, c, "PROT_READ", PROT_READ); diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp index aa69eac..5b02862 100644 --- a/luni/src/main/native/libcore_io_Posix.cpp +++ b/luni/src/main/native/libcore_io_Posix.cpp @@ -16,11 +16,13 @@ #define LOG_TAG "Posix" +#include "AsynchronousSocketCloseMonitor.h" #include "JNIHelp.h" #include "JniConstants.h" #include "JniException.h" #include "NetworkUtilities.h" #include "ScopedBytes.h" +#include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" #include "StaticAssert.h" @@ -34,6 +36,8 @@ #include <netdb.h> #include <netinet/in.h> #include <netinet/in.h> +#include <poll.h> +#include <pwd.h> #include <signal.h> #include <stdlib.h> #include <sys/ioctl.h> @@ -47,8 +51,13 @@ #include <sys/uio.h> #include <sys/utsname.h> #include <sys/vfs.h> // Bionic doesn't have <sys/statvfs.h> +#include <sys/wait.h> #include <unistd.h> +#define TO_JAVA_STRING(NAME, EXP) \ + jstring NAME = env->NewStringUTF(EXP); \ + if (NAME == NULL) return NULL; + struct addrinfo_deleter { void operator()(addrinfo* p) const { if (p != NULL) { // bionic's freeaddrinfo(3) crashes when passed NULL. @@ -57,6 +66,43 @@ struct addrinfo_deleter { } }; +/** + * Used to retry syscalls that can return EINTR. This differs from TEMP_FAILURE_RETRY in that + * it also considers the case where the reason for failure is that another thread called + * Socket.close. + * + * Assumes 'JNIEnv* env' and 'jobject javaFd' (which is a java.io.FileDescriptor) are in scope. + * + * Returns the result of 'exp', though a Java exception will be pending if the result is -1. + * + * Correct usage looks like this: + * + * void Posix_syscall(JNIEnv* env, jobject javaFd, ...) { + * ... + * int fd; + * NET_FAILURE_RETRY("syscall", syscall(fd, ...)); // Throws on error. + * } + */ +#define NET_FAILURE_RETRY(syscall_name, exp) ({ \ + typeof (exp) _rc = -1; \ + do { \ + { \ + fd = jniGetFDFromFileDescriptor(env, javaFd); \ + AsynchronousSocketCloseMonitor monitor(fd); \ + _rc = (exp); \ + } \ + if (_rc == -1) { \ + if (jniGetFDFromFileDescriptor(env, javaFd) == -1) { \ + jniThrowException(env, "java/net/SocketException", "Socket closed"); \ + break; \ + } else if (errno != EINTR) { \ + throwErrnoException(env, syscall_name); \ + break; \ + } \ + } \ + } while (_rc == -1); \ + _rc; }) + static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2, const char* functionName, int error) { jthrowable cause = NULL; @@ -132,8 +178,9 @@ public: return false; } // TODO: Linux actually has a 1024 buffer limit. glibc works around this, and we should too. + // TODO: you can query the limit at runtime with sysconf(_SC_IOV_MAX). for (size_t i = 0; i < mBufferCount; ++i) { - jobject buffer = mEnv->GetObjectArrayElement(javaBuffers, i); + jobject buffer = mEnv->GetObjectArrayElement(javaBuffers, i); // We keep this local ref. mScopedBuffers.push_back(new ScopedT(mEnv, buffer)); jbyte* ptr = const_cast<jbyte*>(mScopedBuffers.back()->get()); if (ptr == NULL) { @@ -169,8 +216,10 @@ private: std::vector<ScopedT*> mScopedBuffers; }; -static jobject makeInetSocketAddress(JNIEnv* env, const sockaddr_storage* ss, int port) { - jobject inetAddress = socketAddressToInetAddress(env, ss); +static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage* ss) { + // TODO: support AF_UNIX and AF_UNSPEC (and other families?) + jint port; + jobject inetAddress = sockaddrToInetAddress(env, ss, &port); if (inetAddress == NULL) { return NULL; } @@ -179,16 +228,14 @@ static jobject makeInetSocketAddress(JNIEnv* env, const sockaddr_storage* ss, in return env->NewObject(JniConstants::inetSocketAddressClass, ctor, inetAddress, port); } -static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage* ss) { - if (ss->ss_family == AF_INET) { - int port = ntohs(reinterpret_cast<const sockaddr_in*>(ss)->sin_port); - return makeInetSocketAddress(env, ss, port); - } else if (ss->ss_family == AF_INET6) { - int port = ntohs(reinterpret_cast<const sockaddr_in6*>(ss)->sin6_port); - return makeInetSocketAddress(env, ss, port); - } - // TODO: support AF_UNIX and AF_UNSPEC, and have some other behavior for other families - return NULL; +static jobject makeStructPasswd(JNIEnv* env, const struct passwd& pw) { + TO_JAVA_STRING(pw_name, pw.pw_name); + TO_JAVA_STRING(pw_dir, pw.pw_dir); + TO_JAVA_STRING(pw_shell, pw.pw_shell); + static jmethodID ctor = env->GetMethodID(JniConstants::structPasswdClass, "<init>", + "(Ljava/lang/String;IILjava/lang/String;Ljava/lang/String;)V"); + return env->NewObject(JniConstants::structPasswdClass, ctor, + pw_name, static_cast<jint>(pw.pw_uid), static_cast<jint>(pw.pw_gid), pw_dir, pw_shell); } static jobject makeStructStat(JNIEnv* env, const struct stat& sb) { @@ -230,17 +277,11 @@ static jobject makeStructTimeval(JNIEnv* env, const struct timeval& tv) { } static jobject makeStructUtsname(JNIEnv* env, const struct utsname& buf) { -#define TO_JAVA_STRING(NAME) \ - jstring NAME = env->NewStringUTF(buf. NAME); \ - if (NAME == NULL) return NULL; - - TO_JAVA_STRING(sysname); - TO_JAVA_STRING(nodename); - TO_JAVA_STRING(release); - TO_JAVA_STRING(version); - TO_JAVA_STRING(machine); -#undef TO_JAVA_STRING - + TO_JAVA_STRING(sysname, buf.sysname); + TO_JAVA_STRING(nodename, buf.nodename); + TO_JAVA_STRING(release, buf.release); + TO_JAVA_STRING(version, buf.version); + TO_JAVA_STRING(machine, buf.machine); static jmethodID ctor = env->GetMethodID(JniConstants::structUtsnameClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); return env->NewObject(JniConstants::structUtsnameClass, ctor, @@ -258,6 +299,23 @@ static bool fillIfreq(JNIEnv* env, jstring javaInterfaceName, struct ifreq& req) return true; } +static bool fillInetSocketAddress(JNIEnv* env, jint rc, jobject javaInetSocketAddress, const sockaddr_storage* ss) { + if (rc == -1 || javaInetSocketAddress == NULL) { + return true; + } + // Fill out the passed-in InetSocketAddress with the sender's IP address and port number. + jint port; + jobject sender = sockaddrToInetAddress(env, ss, &port); + if (sender == NULL) { + return false; + } + static jfieldID addressFid = env->GetFieldID(JniConstants::inetSocketAddressClass, "addr", "Ljava/net/InetAddress;"); + static jfieldID portFid = env->GetFieldID(JniConstants::inetSocketAddressClass, "port", "I"); + env->SetObjectField(javaInetSocketAddress, addressFid, sender); + env->SetIntField(javaInetSocketAddress, portFid, port); + return true; +} + static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) { ScopedUtfChars path(env, javaPath); if (path.c_str() == NULL) { @@ -273,6 +331,62 @@ static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) { return makeStructStat(env, sb); } +class Passwd { +public: + Passwd(JNIEnv* env) : mEnv(env), mResult(NULL) { + mBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (mBufferSize == -1UL) { + // We're probably on bionic, where 1KiB should be enough for anyone. + // TODO: fix bionic to return 1024 like glibc. + mBufferSize = 1024; + } + mBuffer.reset(new char[mBufferSize]); + } + + jobject getpwnam(const char* name) { + return process("getpwnam_r", getpwnam_r(name, &mPwd, mBuffer.get(), mBufferSize, &mResult)); + } + + jobject getpwuid(uid_t uid) { + return process("getpwuid_r", getpwuid_r(uid, &mPwd, mBuffer.get(), mBufferSize, &mResult)); + } + + struct passwd* get() { + return mResult; + } + +private: + jobject process(const char* syscall, int error) { + if (mResult == NULL) { + errno = error; + throwErrnoException(mEnv, syscall); + return NULL; + } + return makeStructPasswd(mEnv, *mResult); + } + + JNIEnv* mEnv; + UniquePtr<char[]> mBuffer; + size_t mBufferSize; + struct passwd mPwd; + struct passwd* mResult; +}; + +static jobject Posix_accept(JNIEnv* env, jobject, jobject javaFd, jobject javaInetSocketAddress) { + sockaddr_storage ss; + socklen_t sl = sizeof(ss); + memset(&ss, 0, sizeof(ss)); + int fd; + sockaddr* peer = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL; + socklen_t* peerLength = (javaInetSocketAddress != NULL) ? &sl : 0; + jint clientFd = NET_FAILURE_RETRY("accept", accept(fd, peer, peerLength)); + if (clientFd == -1 || !fillInetSocketAddress(env, clientFd, javaInetSocketAddress, &ss)) { + close(clientFd); + return NULL; + } + return (clientFd != -1) ? jniCreateFileDescriptor(env, clientFd) : NULL; +} + static jboolean Posix_access(JNIEnv* env, jobject, jstring javaPath, jint mode) { ScopedUtfChars path(env, javaPath); if (path.c_str() == NULL) { @@ -285,6 +399,16 @@ static jboolean Posix_access(JNIEnv* env, jobject, jstring javaPath, jint mode) return (rc == 0); } +static void Posix_bind(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) { + sockaddr_storage ss; + if (!inetAddressToSockaddr(env, javaAddress, port, &ss)) { + return; + } + int fd; + const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); + NET_FAILURE_RETRY("bind", bind(fd, sa, sizeof(sockaddr_storage))); +} + static void Posix_chmod(JNIEnv* env, jobject, jstring javaPath, jint mode) { ScopedUtfChars path(env, javaPath); if (path.c_str() == NULL) { @@ -305,6 +429,28 @@ static void Posix_close(JNIEnv* env, jobject, jobject javaFd) { throwIfMinusOne(env, "close", close(fd)); } +static void Posix_connect(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) { + sockaddr_storage ss; + if (!inetAddressToSockaddr(env, javaAddress, port, &ss)) { + return; + } + int fd; + const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); + NET_FAILURE_RETRY("connect", connect(fd, sa, sizeof(sockaddr_storage))); +} + +static jobject Posix_dup(JNIEnv* env, jobject, jobject javaOldFd) { + int oldFd = jniGetFDFromFileDescriptor(env, javaOldFd); + int newFd = throwIfMinusOne(env, "dup", TEMP_FAILURE_RETRY(dup(oldFd))); + return (newFd != -1) ? jniCreateFileDescriptor(env, newFd) : NULL; +} + +static jobject Posix_dup2(JNIEnv* env, jobject, jobject javaOldFd, jint newFd) { + int oldFd = jniGetFDFromFileDescriptor(env, javaOldFd); + int fd = throwIfMinusOne(env, "dup2", TEMP_FAILURE_RETRY(dup2(oldFd, newFd))); + return (fd != -1) ? jniCreateFileDescriptor(env, fd) : NULL; +} + static jobjectArray Posix_environ(JNIEnv* env, jobject) { extern char** environ; // Standard, but not in any header file. return toStringArray(env, environ); @@ -444,7 +590,7 @@ static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jo // 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)); + ScopedLocalRef<jobject> inetAddress(env, sockaddrToInetAddress(env, address, NULL)); if (inetAddress.get() == NULL) { return NULL; } @@ -454,6 +600,18 @@ static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jo return result; } +static jint Posix_getegid(JNIEnv*, jobject) { + return getegid(); +} + +static jint Posix_geteuid(JNIEnv*, jobject) { + return geteuid(); +} + +static jint Posix_getgid(JNIEnv*, jobject) { + return getgid(); +} + static jstring Posix_getenv(JNIEnv* env, jobject, jstring javaName) { ScopedUtfChars name(env, javaName); if (name.c_str() == NULL) { @@ -464,7 +622,7 @@ static jstring Posix_getenv(JNIEnv* env, jobject, jstring javaName) { static jstring Posix_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint flags) { sockaddr_storage ss; - if (!inetAddressToSocketAddress(env, javaAddress, 0, &ss)) { + if (!inetAddressToSockaddr_getnameinfo(env, javaAddress, 0, &ss)) { return NULL; } // TODO: bionic's getnameinfo(3) seems to want its length parameter to be exactly @@ -481,6 +639,26 @@ static jstring Posix_getnameinfo(JNIEnv* env, jobject, jobject javaAddress, jint return env->NewStringUTF(buf); } +static jint Posix_getpid(JNIEnv*, jobject) { + return getpid(); +} + +static jint Posix_getppid(JNIEnv*, jobject) { + return getppid(); +} + +static jobject Posix_getpwnam(JNIEnv* env, jobject, jstring javaName) { + ScopedUtfChars name(env, javaName); + if (name.c_str() == NULL) { + return NULL; + } + return Passwd(env).getpwnam(name.c_str()); +} + +static jobject Posix_getpwuid(JNIEnv* env, jobject, jint uid) { + return Passwd(env).getpwuid(uid); +} + static jobject Posix_getsockname(JNIEnv* env, jobject, jobject javaFd) { int fd = jniGetFDFromFileDescriptor(env, javaFd); sockaddr_storage ss; @@ -515,7 +693,7 @@ static jobject Posix_getsockoptInAddr(JNIEnv* env, jobject, jobject javaFd, jint throwErrnoException(env, "getsockopt"); return NULL; } - return socketAddressToInetAddress(env, &ss); + return sockaddrToInetAddress(env, &ss, NULL); } static jint Posix_getsockoptInt(JNIEnv* env, jobject, jobject javaFd, jint level, jint option) { @@ -552,6 +730,10 @@ static jobject Posix_getsockoptTimeval(JNIEnv* env, jobject, jobject javaFd, jin return makeStructTimeval(env, tv); } +static jint Posix_getuid(JNIEnv*, jobject) { + return getuid(); +} + static jstring Posix_if_indextoname(JNIEnv* env, jobject, jint index) { char buf[IF_NAMESIZE]; char* name = if_indextoname(index, buf); @@ -572,7 +754,7 @@ static jobject Posix_inet_aton(JNIEnv* env, jobject, jstring javaName) { return NULL; } sin->sin_family = AF_INET; // inet_aton only supports IPv4. - return socketAddressToInetAddress(env, &ss); + return sockaddrToInetAddress(env, &ss, NULL); } static jobject Posix_ioctlInetAddress(JNIEnv* env, jobject, jobject javaFd, jint cmd, jstring javaInterfaceName) { @@ -585,7 +767,7 @@ static jobject Posix_ioctlInetAddress(JNIEnv* env, jobject, jobject javaFd, jint if (rc == -1) { return NULL; } - return socketAddressToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr)); + return sockaddrToInetAddress(env, reinterpret_cast<sockaddr_storage*>(&req.ifr_addr), NULL); } static jint Posix_ioctlInt(JNIEnv* env, jobject, jobject javaFd, jint cmd, jobject javaArg) { @@ -701,6 +883,47 @@ static jobjectArray Posix_pipe(JNIEnv* env, jobject) { return result; } +static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint timeoutMs) { + static jfieldID fdFid = env->GetFieldID(JniConstants::structPollfdClass, "fd", "Ljava/io/FileDescriptor;"); + static jfieldID eventsFid = env->GetFieldID(JniConstants::structPollfdClass, "events", "S"); + static jfieldID reventsFid = env->GetFieldID(JniConstants::structPollfdClass, "revents", "S"); + + // Turn the Java libcore.io.StructPollfd[] into a C++ struct pollfd[]. + size_t arrayLength = env->GetArrayLength(javaStructs); + UniquePtr<struct pollfd[]> fds(new struct pollfd[arrayLength]); + memset(fds.get(), 0, sizeof(struct pollfd) * arrayLength); + size_t count = 0; // Some trailing array elements may be irrelevant. (See below.) + for (size_t i = 0; i < arrayLength; ++i) { + ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i)); + if (javaStruct.get() == NULL) { + break; // We allow trailing nulls in the array for caller convenience. + } + ScopedLocalRef<jobject> javaFd(env, env->GetObjectField(javaStruct.get(), fdFid)); + if (javaFd.get() == NULL) { + break; // We also allow callers to just clear the fd field (this is what Selector does). + } + fds[count].fd = jniGetFDFromFileDescriptor(env, javaFd.get()); + fds[count].events = env->GetShortField(javaStruct.get(), eventsFid); + ++count; + } + + int rc = TEMP_FAILURE_RETRY(poll(fds.get(), count, timeoutMs)); + if (rc == -1) { + throwErrnoException(env, "poll"); + return -1; + } + + // Update the revents fields in the Java libcore.io.StructPollfd[]. + for (size_t i = 0; i < count; ++i) { + ScopedLocalRef<jobject> javaStruct(env, env->GetObjectArrayElement(javaStructs, i)); + if (javaStruct.get() == NULL) { + return -1; + } + env->SetShortField(javaStruct.get(), reventsFid, fds[i].revents); + } + return rc; +} + static jint Posix_readBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount) { ScopedBytesRW bytes(env, javaBytes); if (bytes.get() == NULL) { @@ -719,6 +942,22 @@ static jint Posix_readv(JNIEnv* env, jobject, jobject javaFd, jobjectArray buffe return throwIfMinusOne(env, "readv", TEMP_FAILURE_RETRY(readv(fd, ioVec.get(), ioVec.size()))); } +static jint Posix_recvfromBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetSocketAddress) { + ScopedBytesRW bytes(env, javaBytes); + if (bytes.get() == NULL) { + return -1; + } + sockaddr_storage ss; + socklen_t sl = sizeof(ss); + memset(&ss, 0, sizeof(ss)); + int fd; + sockaddr* from = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL; + socklen_t* fromLength = (javaInetSocketAddress != NULL) ? &sl : 0; + jint recvCount = NET_FAILURE_RETRY("recvfrom", recvfrom(fd, bytes.get() + byteOffset, byteCount, flags, from, fromLength)); + fillInetSocketAddress(env, recvCount, javaInetSocketAddress, &ss); + return recvCount; +} + static void Posix_remove(JNIEnv* env, jobject, jstring javaPath) { ScopedUtfChars path(env, javaPath); if (path.c_str() == NULL) { @@ -757,6 +996,33 @@ static jlong Posix_sendfile(JNIEnv* env, jobject, jobject javaOutFd, jobject jav return result; } +static jint Posix_sendtoBytes(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaInetAddress, jint port) { + ScopedBytesRO bytes(env, javaBytes); + if (bytes.get() == NULL) { + return -1; + } + sockaddr_storage ss; + if (javaInetAddress != NULL && !inetAddressToSockaddr(env, javaInetAddress, port, &ss)) { + return -1; + } + int fd; + const sockaddr* to = (javaInetAddress != NULL) ? reinterpret_cast<const sockaddr*>(&ss) : NULL; + socklen_t toLength = (javaInetAddress != NULL) ? sizeof(ss) : 0; + return NET_FAILURE_RETRY("sendto", sendto(fd, bytes.get() + byteOffset, byteCount, flags, to, toLength)); +} + +static void Posix_setegid(JNIEnv* env, jobject, jint egid) { + throwIfMinusOne(env, "setegid", TEMP_FAILURE_RETRY(setegid(egid))); +} + +static void Posix_seteuid(JNIEnv* env, jobject, jint euid) { + throwIfMinusOne(env, "seteuid", TEMP_FAILURE_RETRY(seteuid(euid))); +} + +static void Posix_setgid(JNIEnv* env, jobject, jint gid) { + throwIfMinusOne(env, "setgid", TEMP_FAILURE_RETRY(setgid(gid))); +} + static void Posix_setsockoptByte(JNIEnv* env, jobject, jobject javaFd, jint level, jint option, jint value) { int fd = jniGetFDFromFileDescriptor(env, javaFd); u_char byte = value; @@ -792,8 +1058,8 @@ static void Posix_setsockoptGroupReq(JNIEnv* env, jobject, jobject javaFd, jint 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)) { + ScopedLocalRef<jobject> javaGroup(env, env->GetObjectField(javaGroupReq, grGroupFid)); + if (!inetAddressToSockaddr(env, javaGroup.get(), 0, &value.gr_group)) { return; } @@ -835,6 +1101,10 @@ static void Posix_setsockoptTimeval(JNIEnv* env, jobject, jobject javaFd, jint l throwIfMinusOne(env, "setsockopt", TEMP_FAILURE_RETRY(setsockopt(fd, level, option, &value, sizeof(value)))); } +static void Posix_setuid(JNIEnv* env, jobject, jint uid) { + throwIfMinusOne(env, "setuid", TEMP_FAILURE_RETRY(setuid(uid))); +} + 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))); @@ -899,6 +1169,16 @@ static jobject Posix_uname(JNIEnv* env, jobject) { return makeStructUtsname(env, buf); } +static jint Posix_waitpid(JNIEnv* env, jobject, jint pid, jobject javaStatus, jint options) { + int status; + int rc = throwIfMinusOne(env, "waitpid", TEMP_FAILURE_RETRY(waitpid(pid, &status, options))); + if (rc != -1) { + static jfieldID valueFid = env->GetFieldID(JniConstants::mutableIntClass, "value", "I"); + env->SetIntField(javaStatus, valueFid, status); + } + return rc; +} + static jint Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount) { ScopedBytesRO bytes(env, javaBytes); if (bytes.get() == NULL) { @@ -918,9 +1198,14 @@ static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buff } static JNINativeMethod gMethods[] = { + NATIVE_METHOD(Posix, accept, "(Ljava/io/FileDescriptor;Ljava/net/InetSocketAddress;)Ljava/io/FileDescriptor;"), NATIVE_METHOD(Posix, access, "(Ljava/lang/String;I)Z"), + NATIVE_METHOD(Posix, bind, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"), NATIVE_METHOD(Posix, chmod, "(Ljava/lang/String;I)V"), NATIVE_METHOD(Posix, close, "(Ljava/io/FileDescriptor;)V"), + NATIVE_METHOD(Posix, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"), + NATIVE_METHOD(Posix, dup, "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;"), + NATIVE_METHOD(Posix, dup2, "(Ljava/io/FileDescriptor;I)Ljava/io/FileDescriptor;"), NATIVE_METHOD(Posix, environ, "()[Ljava/lang/String;"), NATIVE_METHOD(Posix, fcntlVoid, "(Ljava/io/FileDescriptor;I)I"), NATIVE_METHOD(Posix, fcntlLong, "(Ljava/io/FileDescriptor;IJ)I"), @@ -932,14 +1217,22 @@ static JNINativeMethod gMethods[] = { 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, getegid, "()I"), + NATIVE_METHOD(Posix, geteuid, "()I"), + NATIVE_METHOD(Posix, getgid, "()I"), NATIVE_METHOD(Posix, getenv, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(Posix, getnameinfo, "(Ljava/net/InetAddress;I)Ljava/lang/String;"), + NATIVE_METHOD(Posix, getpid, "()I"), + NATIVE_METHOD(Posix, getppid, "()I"), + NATIVE_METHOD(Posix, getpwnam, "(Ljava/lang/String;)Llibcore/io/StructPasswd;"), + NATIVE_METHOD(Posix, getpwuid, "(I)Llibcore/io/StructPasswd;"), 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, getuid, "()I"), 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;"), @@ -958,11 +1251,17 @@ static JNINativeMethod gMethods[] = { 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, poll, "([Llibcore/io/StructPollfd;I)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, recvfromBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/InetSocketAddress;)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, sendtoBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/InetAddress;I)I"), + NATIVE_METHOD(Posix, setegid, "(I)V"), + NATIVE_METHOD(Posix, seteuid, "(I)V"), + NATIVE_METHOD(Posix, setgid, "(I)V"), 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"), @@ -970,6 +1269,7 @@ static JNINativeMethod gMethods[] = { 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, setuid, "(I)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;"), @@ -978,6 +1278,7 @@ static JNINativeMethod gMethods[] = { 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, waitpid, "(ILlibcore/util/MutableInt;I)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"), }; 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 deleted file mode 100644 index d7208b1..0000000 --- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp +++ /dev/null @@ -1,684 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "OSNetworkSystem" - -#include "AsynchronousSocketCloseMonitor.h" -#include "JNIHelp.h" -#include "JniConstants.h" -#include "JniException.h" -#include "NetFd.h" -#include "NetworkUtilities.h" -#include "ScopedPrimitiveArray.h" -#include "jni.h" -#include "valueOf.h" - -#include <arpa/inet.h> -#include <errno.h> -#include <net/if.h> -#include <netdb.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <sys/un.h> -#include <unistd.h> - -/* 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. - * - * @param address the sockaddr_storage structure to get the port from - * - * @return the port number, or -1 if the address family is unknown. - */ -static int getSocketAddressPort(sockaddr_storage* ss) { - switch (ss->ss_family) { - case AF_INET: - return ntohs(reinterpret_cast<sockaddr_in*>(ss)->sin_port); - case AF_INET6: - return ntohs(reinterpret_cast<sockaddr_in6*>(ss)->sin6_port); - default: - return -1; - } -} - -// Creates mapped addresses. -// TODO: can this move to inetAddressToSocketAddress? -class CompatibleSocketAddress { -public: - 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); - } - memcpy(&sin6->sin6_addr.s6_addr[12], &sin->sin_addr.s_addr, 4); - mCompatibleAddress = reinterpret_cast<const sockaddr*>(&mTmp); - } - } - // Returns a pointer to an IPv6 address. - const sockaddr* get() const { - return mCompatibleAddress; - } -private: - const sockaddr* mCompatibleAddress; - sockaddr_storage mTmp; -}; - -// Converts a number of milliseconds to a timeval. -static timeval toTimeval(long ms) { - timeval tv; - tv.tv_sec = ms / 1000; - tv.tv_usec = (ms - tv.tv_sec*1000) * 1000; - return tv; -} - -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); - } -} - -static jint OSNetworkSystem_writeDirect(JNIEnv* env, jobject, - jobject fileDescriptor, jint address, jint offset, jint count) { - if (count <= 0) { - return 0; - } - - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return 0; - } - - jbyte* src = reinterpret_cast<jbyte*>(static_cast<uintptr_t>(address + offset)); - - ssize_t bytesSent; - { - int intFd = fd.get(); - AsynchronousSocketCloseMonitor monitor(intFd); - bytesSent = NET_FAILURE_RETRY(fd, write(intFd, src, count)); - } - if (env->ExceptionOccurred()) { - return -1; - } - - if (bytesSent == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // We were asked to write to a non-blocking socket, but were told - // it would block, so report "no bytes written". - return 0; - } else { - jniThrowSocketException(env, errno); - return 0; - } - } - return bytesSent; -} - -static jint OSNetworkSystem_write(JNIEnv* env, jobject, - jobject fileDescriptor, jbyteArray byteArray, jint offset, jint count) { - ScopedByteArrayRW bytes(env, byteArray); - if (bytes.get() == NULL) { - return -1; - } - jint address = static_cast<jint>(reinterpret_cast<uintptr_t>(bytes.get())); - int result = OSNetworkSystem_writeDirect(env, NULL, fileDescriptor, address, offset, count); - return result; -} - -static jboolean OSNetworkSystem_connect(JNIEnv* env, jobject, jobject fileDescriptor, jobject inetAddr, jint port) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return JNI_FALSE; - } - - 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 netFd(env, fileDescriptor); - if (netFd.isClosed()) { - return JNI_FALSE; - } - - // 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 { - 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 ss; - if (!inetAddressToSocketAddress(env, inetAddress, port, &ss)) { - return; - } - - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return; - } - - const CompatibleSocketAddress compatibleAddress(ss, false); - int rc = TEMP_FAILURE_RETRY(bind(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage))); - if (rc == -1) { - jniThrowExceptionWithErrno(env, "java/net/BindException", errno); - } -} - -static void OSNetworkSystem_accept(JNIEnv* env, jobject, jobject serverFileDescriptor, - jobject newSocket, jobject clientFileDescriptor) { - - if (newSocket == NULL) { - jniThrowNullPointerException(env, NULL); - return; - } - - NetFd serverFd(env, serverFileDescriptor); - if (serverFd.isClosed()) { - return; - } - - sockaddr_storage ss; - socklen_t addrLen = sizeof(ss); - sockaddr* sa = reinterpret_cast<sockaddr*>(&ss); - - int clientFd; - { - int intFd = serverFd.get(); - AsynchronousSocketCloseMonitor monitor(intFd); - clientFd = NET_FAILURE_RETRY(serverFd, accept(intFd, sa, &addrLen)); - } - if (env->ExceptionOccurred()) { - return; - } - if (clientFd == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - jniThrowSocketTimeoutException(env, errno); - } else { - jniThrowSocketException(env, errno); - } - return; - } - - // Reset the inherited read timeout to the Java-specified default of 0. - timeval timeout(toTimeval(0)); - int rc = setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - if (rc == -1) { - LOGE("couldn't reset SO_RCVTIMEO on accepted socket fd %i: %s", clientFd, strerror(errno)); - jniThrowSocketException(env, errno); - } - - /* - * For network sockets, put the peer address and port in instance variables. - * We don't bother to do this for UNIX domain sockets, since most peers are - * anonymous anyway. - */ - if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) { - // Remote address and port. - jobject remoteAddress = socketAddressToInetAddress(env, &ss); - if (remoteAddress == NULL) { - close(clientFd); - return; - } - int remotePort = getSocketAddressPort(&ss); - - // Local port. - memset(&ss, 0, addrLen); - int rc = getsockname(clientFd, sa, &addrLen); - if (rc == -1) { - close(clientFd); - jniThrowSocketException(env, errno); - return; - } - int localPort = getSocketAddressPort(&ss); - - static jfieldID addressFid = env->GetFieldID(JniConstants::socketImplClass, "address", "Ljava/net/InetAddress;"); - static jfieldID localPortFid = env->GetFieldID(JniConstants::socketImplClass, "localport", "I"); - static jfieldID portFid = env->GetFieldID(JniConstants::socketImplClass, "port", "I"); - env->SetObjectField(newSocket, addressFid, remoteAddress); - env->SetIntField(newSocket, portFid, remotePort); - env->SetIntField(newSocket, localPortFid, localPort); - } - - jniSetFileDescriptorOfFD(env, clientFileDescriptor, clientFd); -} - -static void OSNetworkSystem_sendUrgentData(JNIEnv* env, jobject, - jobject fileDescriptor, jbyte value) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return; - } - - int rc = send(fd.get(), &value, 1, MSG_OOB); - if (rc == -1) { - jniThrowSocketException(env, errno); - } -} - -static void OSNetworkSystem_disconnectDatagram(JNIEnv* env, jobject, jobject fileDescriptor) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return; - } - - // To disconnect a datagram socket, we connect to a bogus address with - // the family AF_UNSPEC. - sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - ss.ss_family = AF_UNSPEC; - const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); - int rc = TEMP_FAILURE_RETRY(connect(fd.get(), sa, sizeof(ss))); - if (rc == -1) { - jniThrowSocketException(env, errno); - } -} - -// TODO: can we merge this with recvDirect? -static jint OSNetworkSystem_readDirect(JNIEnv* env, jobject, jobject fileDescriptor, - jint address, jint count) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return 0; - } - - jbyte* dst = reinterpret_cast<jbyte*>(static_cast<uintptr_t>(address)); - ssize_t bytesReceived; - { - int intFd = fd.get(); - AsynchronousSocketCloseMonitor monitor(intFd); - bytesReceived = NET_FAILURE_RETRY(fd, read(intFd, dst, count)); - } - if (env->ExceptionOccurred()) { - return -1; - } - if (bytesReceived == 0) { - return -1; - } else if (bytesReceived == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // We were asked to read a non-blocking socket with no data - // available, so report "no bytes read". - return 0; - } else { - jniThrowSocketException(env, errno); - return 0; - } - } else { - return bytesReceived; - } -} - -static jint OSNetworkSystem_read(JNIEnv* env, jclass, jobject fileDescriptor, - jbyteArray byteArray, jint offset, jint count) { - ScopedByteArrayRW bytes(env, byteArray); - if (bytes.get() == NULL) { - return -1; - } - jint address = static_cast<jint>(reinterpret_cast<uintptr_t>(bytes.get() + offset)); - return OSNetworkSystem_readDirect(env, NULL, fileDescriptor, address, count); -} - -// TODO: can we merge this with readDirect? -static jint OSNetworkSystem_recvDirect(JNIEnv* env, jobject, jobject fileDescriptor, jobject packet, - jint address, jint offset, jint length, jboolean peek, jboolean connected) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return 0; - } - - char* buf = reinterpret_cast<char*>(static_cast<uintptr_t>(address + offset)); - const int flags = peek ? MSG_PEEK : 0; - sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - socklen_t sockAddrLen = sizeof(ss); - sockaddr* from = connected ? NULL : reinterpret_cast<sockaddr*>(&ss); - socklen_t* fromLength = connected ? NULL : &sockAddrLen; - - ssize_t bytesReceived; - { - int intFd = fd.get(); - AsynchronousSocketCloseMonitor monitor(intFd); - bytesReceived = NET_FAILURE_RETRY(fd, recvfrom(intFd, buf, length, flags, from, fromLength)); - } - if (env->ExceptionOccurred()) { - return -1; - } - if (bytesReceived == -1) { - if (connected && errno == ECONNREFUSED) { - jniThrowException(env, "java/net/PortUnreachableException", ""); - } else if (errno == EAGAIN || errno == EWOULDBLOCK) { - jniThrowSocketTimeoutException(env, errno); - } else { - jniThrowSocketException(env, errno); - } - return 0; - } - - static jfieldID addressFid = env->GetFieldID(JniConstants::datagramPacketClass, "address", "Ljava/net/InetAddress;"); - static jfieldID lengthFid = env->GetFieldID(JniConstants::datagramPacketClass, "length", "I"); - static jfieldID portFid = env->GetFieldID(JniConstants::datagramPacketClass, "port", "I"); - if (packet != NULL) { - env->SetIntField(packet, lengthFid, bytesReceived); - if (!connected) { - jobject sender = socketAddressToInetAddress(env, &ss); - if (sender == NULL) { - return 0; - } - int port = getSocketAddressPort(&ss); - env->SetObjectField(packet, addressFid, sender); - env->SetIntField(packet, portFid, port); - } - } - return bytesReceived; -} - -static jint OSNetworkSystem_recv(JNIEnv* env, jobject, jobject fd, jobject packet, - jbyteArray javaBytes, jint offset, jint length, jboolean peek, jboolean connected) { - ScopedByteArrayRW bytes(env, javaBytes); - if (bytes.get() == NULL) { - return -1; - } - uintptr_t address = reinterpret_cast<uintptr_t>(bytes.get()); - return OSNetworkSystem_recvDirect(env, NULL, fd, packet, address, offset, length, peek, - connected); -} - - - - - - - - -static jint OSNetworkSystem_sendDirect(JNIEnv* env, jobject, jobject fileDescriptor, jint address, jint offset, jint length, jint port, jobject inetAddress) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - return -1; - } - - sockaddr_storage receiver; - if (inetAddress != NULL && !inetAddressToSocketAddress(env, inetAddress, port, &receiver)) { - return -1; - } - - int flags = 0; - char* buf = reinterpret_cast<char*>(static_cast<uintptr_t>(address + offset)); - sockaddr* to = inetAddress ? reinterpret_cast<sockaddr*>(&receiver) : NULL; - socklen_t toLength = inetAddress ? sizeof(receiver) : 0; - - ssize_t bytesSent; - { - int intFd = fd.get(); - AsynchronousSocketCloseMonitor monitor(intFd); - bytesSent = NET_FAILURE_RETRY(fd, sendto(intFd, buf, length, flags, to, toLength)); - } - if (env->ExceptionOccurred()) { - return -1; - } - if (bytesSent == -1) { - if (errno == ECONNRESET || errno == ECONNREFUSED) { - return 0; - } else { - jniThrowSocketException(env, errno); - } - } - return bytesSent; -} - -static jint OSNetworkSystem_send(JNIEnv* env, jobject, jobject fd, - jbyteArray data, jint offset, jint length, - jint port, jobject inetAddress) { - ScopedByteArrayRO bytes(env, data); - if (bytes.get() == NULL) { - return -1; - } - return OSNetworkSystem_sendDirect(env, NULL, fd, - reinterpret_cast<uintptr_t>(bytes.get()), offset, length, port, inetAddress); -} - - - - - - - - -static bool isValidFd(int fd) { - return fd >= 0 && fd < FD_SETSIZE; -} - -static bool initFdSet(JNIEnv* env, jobjectArray fdArray, jint count, fd_set* fdSet, int* maxFd) { - for (int i = 0; i < count; ++i) { - jobject fileDescriptor = env->GetObjectArrayElement(fdArray, i); - if (fileDescriptor == NULL) { - return false; - } - - const int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (!isValidFd(fd)) { - LOGE("selectImpl: ignoring invalid fd %i", fd); - continue; - } - - FD_SET(fd, fdSet); - - if (fd > *maxFd) { - *maxFd = fd; - } - } - return true; -} - -/* - * Note: fdSet has to be non-const because although on Linux FD_ISSET() is sane - * and takes a const fd_set*, it takes fd_set* on Mac OS. POSIX is not on our - * side here: - * http://www.opengroup.org/onlinepubs/000095399/functions/select.html - */ -static bool translateFdSet(JNIEnv* env, jobjectArray fdArray, jint count, fd_set& fdSet, jint* flagArray, size_t offset, jint op) { - for (int i = 0; i < count; ++i) { - jobject fileDescriptor = env->GetObjectArrayElement(fdArray, i); - if (fileDescriptor == NULL) { - return false; - } - - const int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (isValidFd(fd) && FD_ISSET(fd, &fdSet)) { - flagArray[i + offset] = op; - } else { - flagArray[i + offset] = SOCKET_OP_NONE; - } - } - return true; -} - -static jboolean OSNetworkSystem_selectImpl(JNIEnv* env, jclass, - jobjectArray readFDArray, jobjectArray writeFDArray, jint countReadC, - jint countWriteC, jintArray outFlags, jlong timeoutMs) { - - // Initialize the fd_sets. - int maxFd = -1; - fd_set readFds; - fd_set writeFds; - FD_ZERO(&readFds); - FD_ZERO(&writeFds); - bool initialized = initFdSet(env, readFDArray, countReadC, &readFds, &maxFd) && - initFdSet(env, writeFDArray, countWriteC, &writeFds, &maxFd); - if (!initialized) { - return -1; - } - - // Initialize the timeout, if any. - timeval tv; - timeval* tvp = NULL; - if (timeoutMs >= 0) { - tv = toTimeval(timeoutMs); - tvp = &tv; - } - - // Perform the select. - int result = select(maxFd + 1, &readFds, &writeFds, NULL, tvp); - if (result == 0) { - // Timeout. - return JNI_FALSE; - } else if (result == -1) { - // Error. - if (errno == EINTR) { - return JNI_FALSE; - } else { - jniThrowSocketException(env, errno); - return JNI_FALSE; - } - } - - // Translate the result into the int[] we're supposed to fill in. - ScopedIntArrayRW flagArray(env, outFlags); - if (flagArray.get() == NULL) { - return JNI_FALSE; - } - return translateFdSet(env, readFDArray, countReadC, readFds, flagArray.get(), 0, SOCKET_OP_READ) && - translateFdSet(env, writeFDArray, countWriteC, writeFds, flagArray.get(), countReadC, SOCKET_OP_WRITE); -} - -static void OSNetworkSystem_close(JNIEnv* env, jobject, jobject fileDescriptor) { - NetFd fd(env, fileDescriptor); - if (fd.isClosed()) { - // Socket.close doesn't throw if you try to close an already-closed socket. - env->ExceptionClear(); - return; - } - - int oldFd = fd.get(); - jniSetFileDescriptorOfFD(env, fileDescriptor, -1); - AsynchronousSocketCloseMonitor::signalBlockedThreads(oldFd); - close(oldFd); -} - -static JNINativeMethod gMethods[] = { - 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, 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"), - NATIVE_METHOD(OSNetworkSystem, readDirect, "(Ljava/io/FileDescriptor;II)I"), - NATIVE_METHOD(OSNetworkSystem, recv, "(Ljava/io/FileDescriptor;Ljava/net/DatagramPacket;[BIIZZ)I"), - NATIVE_METHOD(OSNetworkSystem, recvDirect, "(Ljava/io/FileDescriptor;Ljava/net/DatagramPacket;IIIZZ)I"), - NATIVE_METHOD(OSNetworkSystem, selectImpl, "([Ljava/io/FileDescriptor;[Ljava/io/FileDescriptor;II[IJ)Z"), - 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, write, "(Ljava/io/FileDescriptor;[BII)I"), - NATIVE_METHOD(OSNetworkSystem, writeDirect, "(Ljava/io/FileDescriptor;III)I"), -}; - -int register_org_apache_harmony_luni_platform_OSNetworkSystem(JNIEnv* env) { - AsynchronousSocketCloseMonitor::init(); - return jniRegisterNativeMethods(env, "org/apache/harmony/luni/platform/OSNetworkSystem", gMethods, NELEM(gMethods)); -} diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk index c7620b3..9535ce6 100644 --- a/luni/src/main/native/sub.mk +++ b/luni/src/main/native/sub.mk @@ -18,6 +18,7 @@ LOCAL_SRC_FILES := \ java_lang_ProcessManager.cpp \ java_lang_RealToString.cpp \ java_lang_StrictMath.cpp \ + java_lang_StringToReal.cpp \ java_lang_System.cpp \ java_math_NativeBN.cpp \ java_nio_ByteOrder.cpp \ @@ -38,12 +39,11 @@ LOCAL_SRC_FILES := \ libcore_icu_NativeNormalizer.cpp \ libcore_icu_NativePluralRules.cpp \ libcore_icu_TimeZones.cpp \ + libcore_io_AsynchronousCloseMonitor.cpp \ libcore_io_Memory.cpp \ libcore_io_OsConstants.cpp \ libcore_io_Posix.cpp \ libcore_net_RawSocket.cpp \ - org_apache_harmony_luni_platform_OSNetworkSystem.cpp \ - org_apache_harmony_luni_util_FloatingPointParser.cpp \ org_apache_harmony_xml_ExpatParser.cpp \ org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp \ readlink.cpp \ diff --git a/luni/src/test/java/libcore/io/Base64Test.java b/luni/src/test/java/libcore/io/Base64Test.java new file mode 100644 index 0000000..edf24d4 --- /dev/null +++ b/luni/src/test/java/libcore/io/Base64Test.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.io; + +import java.util.Arrays; +import junit.framework.TestCase; + +public final class Base64Test extends TestCase { + + public void testDecodeEmpty() throws Exception { + assertEquals("[]", Arrays.toString(Base64.decode(new byte[0]))); + } + + public void testEncode() throws Exception { + assertEncoded(""); + assertEncoded("Eg==", 0x12); + assertEncoded("EjQ=", 0x12, 0x34 ); + assertEncoded("EjRW", 0x12, 0x34, 0x56); + assertEncoded("EjRWeA==", 0x12, 0x34, 0x56, 0x78); + assertEncoded("EjRWeJo=", 0x12, 0x34, 0x56, 0x78, 0x9A); + assertEncoded("EjRWeJq8", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc); + } + + public void testEncodeDoesNotWrap() { + int[] data = new int[61]; + Arrays.fill(data, 0xff); + String expected = "///////////////////////////////////////////////////////////////////////" + + "//////////w=="; // 84 chars + assertEncoded(expected, data); + } + + public void assertEncoded(String expected , int... data) { + byte[] dataBytes = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + dataBytes[i] = (byte) data[i]; + } + assertEquals(expected, Base64.encode(dataBytes)); + } +} + diff --git a/luni/src/test/java/libcore/io/DiskLruCacheTest.java b/luni/src/test/java/libcore/io/DiskLruCacheTest.java new file mode 100644 index 0000000..808918d --- /dev/null +++ b/luni/src/test/java/libcore/io/DiskLruCacheTest.java @@ -0,0 +1,755 @@ +/* + * 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.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import junit.framework.TestCase; +import static libcore.io.DiskLruCache.JOURNAL_FILE; +import static libcore.io.DiskLruCache.MAGIC; +import static libcore.io.DiskLruCache.VERSION_1; +import tests.io.MockOs; + +public final class DiskLruCacheTest extends TestCase { + private final int appVersion = 100; + private String javaTmpDir; + private File cacheDir; + private File journalFile; + private DiskLruCache cache; + private final MockOs mockOs = new MockOs(); + + @Override public void setUp() throws Exception { + super.setUp(); + javaTmpDir = System.getProperty("java.io.tmpdir"); + cacheDir = new File(javaTmpDir, "DiskLruCacheTest"); + cacheDir.mkdir(); + journalFile = new File(cacheDir, JOURNAL_FILE); + for (File file : cacheDir.listFiles()) { + file.delete(); + } + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + mockOs.install(); + } + + @Override protected void tearDown() throws Exception { + mockOs.uninstall(); + cache.close(); + super.tearDown(); + } + + public void testEmptyCache() throws Exception { + cache.close(); + assertJournalEquals(); + } + + public void testWriteAndReadEntry() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + creator.set(0, "ABC"); + creator.set(1, "DE"); + assertNull(creator.getString(0)); + assertNull(creator.newInputStream(0)); + assertNull(creator.getString(1)); + assertNull(creator.newInputStream(1)); + creator.commit(); + + DiskLruCache.Snapshot snapshot = cache.get("k1"); + assertEquals("ABC", snapshot.getString(0)); + assertEquals("DE", snapshot.getString(1)); + } + + public void testReadAndWriteEntryAcrossCacheOpenAndClose() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + creator.set(0, "A"); + creator.set(1, "B"); + creator.commit(); + cache.close(); + + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + DiskLruCache.Snapshot snapshot = cache.get("k1"); + assertEquals("A", snapshot.getString(0)); + assertEquals("B", snapshot.getString(1)); + snapshot.close(); + } + + public void testJournalWithEditAndPublish() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed + creator.set(0, "AB"); + creator.set(1, "C"); + creator.commit(); + cache.close(); + assertJournalEquals("DIRTY k1", "CLEAN k1 2 1"); + } + + public void testRevertedNewFileIsRemoveInJournal() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed + creator.set(0, "AB"); + creator.set(1, "C"); + creator.abort(); + cache.close(); + assertJournalEquals("DIRTY k1", "REMOVE k1"); + } + + public void testUnterminatedEditIsRevertedOnClose() throws Exception { + cache.edit("k1"); + cache.close(); + assertJournalEquals("DIRTY k1", "REMOVE k1"); + } + + public void testJournalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + assertNull(cache.get("k1")); + creator.set(0, "A"); + creator.set(1, "BC"); + creator.commit(); + cache.close(); + assertJournalEquals("DIRTY k1", "CLEAN k1 1 2"); + } + + public void testJournalWithEditAndPublishAndRead() throws Exception { + DiskLruCache.Editor k1Creator = cache.edit("k1"); + k1Creator.set(0, "AB"); + k1Creator.set(1, "C"); + k1Creator.commit(); + DiskLruCache.Editor k2Creator = cache.edit("k2"); + k2Creator.set(0, "DEF"); + k2Creator.set(1, "G"); + k2Creator.commit(); + DiskLruCache.Snapshot k1Snapshot = cache.get("k1"); + k1Snapshot.close(); + cache.close(); + assertJournalEquals("DIRTY k1", "CLEAN k1 2 1", + "DIRTY k2", "CLEAN k2 3 1", + "READ k1"); + } + + public void testCannotOperateOnEditAfterPublish() throws Exception { + DiskLruCache.Editor editor = cache.edit("k1"); + editor.set(0, "A"); + editor.set(1, "B"); + editor.commit(); + assertInoperable(editor); + } + + public void testCannotOperateOnEditAfterRevert() throws Exception { + DiskLruCache.Editor editor = cache.edit("k1"); + editor.set(0, "A"); + editor.set(1, "B"); + editor.abort(); + assertInoperable(editor); + } + + public void testExplicitRemoveAppliedToDiskImmediately() throws Exception { + DiskLruCache.Editor editor = cache.edit("k1"); + editor.set(0, "ABC"); + editor.set(1, "B"); + editor.commit(); + File k1 = getCleanFile("k1", 0); + assertEquals("ABC", readFile(k1)); + cache.remove("k1"); + assertFalse(k1.exists()); + } + + /** + * Each read sees a snapshot of the file at the time read was called. + * This means that two reads of the same key can see different data. + */ + public void testReadAndWriteOverlapsMaintainConsistency() throws Exception { + DiskLruCache.Editor v1Creator = cache.edit("k1"); + v1Creator.set(0, "AAaa"); + v1Creator.set(1, "BBbb"); + v1Creator.commit(); + + DiskLruCache.Snapshot snapshot1 = cache.get("k1"); + InputStream inV1 = snapshot1.getInputStream(0); + assertEquals('A', inV1.read()); + assertEquals('A', inV1.read()); + + DiskLruCache.Editor v1Updater = cache.edit("k1"); + v1Updater.set(0, "CCcc"); + v1Updater.set(1, "DDdd"); + v1Updater.commit(); + + DiskLruCache.Snapshot snapshot2 = cache.get("k1"); + assertEquals("CCcc", snapshot2.getString(0)); + assertEquals("DDdd", snapshot2.getString(1)); + snapshot2.close(); + + assertEquals('a', inV1.read()); + assertEquals('a', inV1.read()); + assertEquals("BBbb", snapshot1.getString(1)); + snapshot1.close(); + } + + public void testOpenWithDirtyKeyDeletesAllFilesForThatKey() throws Exception { + cache.close(); + File cleanFile0 = getCleanFile("k1", 0); + File cleanFile1 = getCleanFile("k1", 1); + File dirtyFile0 = getDirtyFile("k1", 0); + File dirtyFile1 = getDirtyFile("k1", 1); + writeFile(cleanFile0, "A"); + writeFile(cleanFile1, "B"); + writeFile(dirtyFile0, "C"); + writeFile(dirtyFile1, "D"); + createJournal("CLEAN k1 1 1", "DIRTY k1"); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertFalse(cleanFile0.exists()); + assertFalse(cleanFile1.exists()); + assertFalse(dirtyFile0.exists()); + assertFalse(dirtyFile1.exists()); + assertNull(cache.get("k1")); + } + + public void testOpenWithInvalidVersionClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournalWithHeader(MAGIC, "0", "100", "2", ""); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + } + + public void testOpenWithInvalidAppVersionClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournalWithHeader(MAGIC, "1", "101", "2", ""); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + } + + public void testOpenWithInvalidValueCountClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournalWithHeader(MAGIC, "1", "100", "1", ""); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + } + + public void testOpenWithInvalidBlankLineClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournalWithHeader(MAGIC, "1", "100", "2", "x"); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + } + + public void testOpenWithInvalidJournalLineClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournal("CLEAN k1 1 1", "BOGUS"); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + assertNull(cache.get("k1")); + } + + public void testOpenWithInvalidFileSizeClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournal("CLEAN k1 0000x001 1"); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + assertNull(cache.get("k1")); + } + + public void testOpenWithTruncatedLineDiscardsThatLine() throws Exception { + cache.close(); + writeFile(getCleanFile("k1", 0), "A"); + writeFile(getCleanFile("k1", 1), "B"); + Writer writer = new FileWriter(journalFile); + writer.write(MAGIC + "\n" + VERSION_1 + "\n100\n2\n\nCLEAN k1 1 1"); // no trailing newline + writer.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertNull(cache.get("k1")); + } + + public void testOpenWithTooManyFileSizesClearsDirectory() throws Exception { + cache.close(); + generateSomeGarbageFiles(); + createJournal("CLEAN k1 1 1 1"); + cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); + assertGarbageFilesAllDeleted(); + assertNull(cache.get("k1")); + } + + public void testKeyWithSpaceNotPermitted() throws Exception { + try { + cache.edit("my key"); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testKeyWithNewlineNotPermitted() throws Exception { + try { + cache.edit("my\nkey"); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testKeyWithCarriageReturnNotPermitted() throws Exception { + try { + cache.edit("my\rkey"); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testNullKeyThrows() throws Exception { + try { + cache.edit(null); + fail(); + } catch (NullPointerException expected) { + } + } + + public void testCreateNewEntryWithTooFewValuesFails() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + creator.set(1, "A"); + try { + creator.commit(); + fail(); + } catch (IllegalStateException expected) { + } + + assertFalse(getCleanFile("k1", 0).exists()); + assertFalse(getCleanFile("k1", 1).exists()); + assertFalse(getDirtyFile("k1", 0).exists()); + assertFalse(getDirtyFile("k1", 1).exists()); + assertNull(cache.get("k1")); + + DiskLruCache.Editor creator2 = cache.edit("k1"); + creator2.set(0, "B"); + creator2.set(1, "C"); + creator2.commit(); + } + + public void testRevertWithTooFewValues() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + creator.set(1, "A"); + creator.abort(); + assertFalse(getCleanFile("k1", 0).exists()); + assertFalse(getCleanFile("k1", 1).exists()); + assertFalse(getDirtyFile("k1", 0).exists()); + assertFalse(getDirtyFile("k1", 1).exists()); + assertNull(cache.get("k1")); + } + + public void testUpdateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception { + DiskLruCache.Editor creator = cache.edit("k1"); + creator.set(0, "A"); + creator.set(1, "B"); + creator.commit(); + + DiskLruCache.Editor updater = cache.edit("k1"); + updater.set(0, "C"); + updater.commit(); + + DiskLruCache.Snapshot snapshot = cache.get("k1"); + assertEquals("C", snapshot.getString(0)); + assertEquals("B", snapshot.getString(1)); + snapshot.close(); + } + + public void testEvictOnInsert() throws Exception { + cache.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); + + set("A", "a", "aaa"); // size 4 + set("B", "bb", "bbbb"); // size 6 + assertEquals(10, cache.size()); + + // cause the size to grow to 12 should evict 'A' + set("C", "c", "c"); + cache.flush(); + assertEquals(8, cache.size()); + assertAbsent("A"); + assertValue("B", "bb", "bbbb"); + assertValue("C", "c", "c"); + + // causing the size to grow to 10 should evict nothing + set("D", "d", "d"); + cache.flush(); + assertEquals(10, cache.size()); + assertAbsent("A"); + assertValue("B", "bb", "bbbb"); + assertValue("C", "c", "c"); + assertValue("D", "d", "d"); + + // causing the size to grow to 18 should evict 'B' and 'C' + set("E", "eeee", "eeee"); + cache.flush(); + assertEquals(10, cache.size()); + assertAbsent("A"); + assertAbsent("B"); + assertAbsent("C"); + assertValue("D", "d", "d"); + assertValue("E", "eeee", "eeee"); + } + + public void testEvictOnUpdate() throws Exception { + cache.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); + + set("A", "a", "aa"); // size 3 + set("B", "b", "bb"); // size 3 + set("C", "c", "cc"); // size 3 + assertEquals(9, cache.size()); + + // causing the size to grow to 11 should evict 'A' + set("B", "b", "bbbb"); + cache.flush(); + assertEquals(8, cache.size()); + assertAbsent("A"); + assertValue("B", "b", "bbbb"); + assertValue("C", "c", "cc"); + } + + public void testEvictionHonorsLruFromCurrentSession() throws Exception { + cache.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); + set("A", "a", "a"); + set("B", "b", "b"); + set("C", "c", "c"); + set("D", "d", "d"); + set("E", "e", "e"); + cache.get("B").close(); // 'B' is now least recently used + + // causing the size to grow to 12 should evict 'A' + set("F", "f", "f"); + // causing the size to grow to 12 should evict 'C' + set("G", "g", "g"); + cache.flush(); + assertEquals(10, cache.size()); + assertAbsent("A"); + assertValue("B", "b", "b"); + assertAbsent("C"); + assertValue("D", "d", "d"); + assertValue("E", "e", "e"); + assertValue("F", "f", "f"); + } + + public void testEvictionHonorsLruFromPreviousSession() throws Exception { + set("A", "a", "a"); + set("B", "b", "b"); + set("C", "c", "c"); + set("D", "d", "d"); + set("E", "e", "e"); + set("F", "f", "f"); + cache.get("B").close(); // 'B' is now least recently used + assertEquals(12, cache.size()); + cache.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); + + set("G", "g", "g"); + cache.flush(); + assertEquals(10, cache.size()); + assertAbsent("A"); + assertValue("B", "b", "b"); + assertAbsent("C"); + assertValue("D", "d", "d"); + assertValue("E", "e", "e"); + assertValue("F", "f", "f"); + assertValue("G", "g", "g"); + } + + public void testCacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception { + cache.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); + set("A", "aaaaa", "aaaaaa"); // size=11 + cache.flush(); + assertAbsent("A"); + } + + public void testCacheSingleValueOfSizeGreaterThanMaxSize() throws Exception { + cache.close(); + cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); + set("A", "aaaaaaaaaaa", "a"); // size=12 + cache.flush(); + assertAbsent("A"); + } + + public void testConstructorDoesNotAllowZeroCacheSize() throws Exception { + try { + DiskLruCache.open(cacheDir, appVersion, 2, 0); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testConstructorDoesNotAllowZeroValuesPerEntry() throws Exception { + try { + DiskLruCache.open(cacheDir, appVersion, 0, 10); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testRemoveAbsentElement() throws Exception { + cache.remove("A"); + } + + public void testReadingTheSameStreamMultipleTimes() throws Exception { + set("A", "a", "b"); + DiskLruCache.Snapshot snapshot = cache.get("A"); + assertSame(snapshot.getInputStream(0), snapshot.getInputStream(0)); + snapshot.close(); + } + + public void testRebuildJournalOnRepeatedReads() throws Exception { + set("A", "a", "a"); + set("B", "b", "b"); + long lastJournalLength = 0; + while (true) { + long journalLength = journalFile.length(); + assertValue("A", "a", "a"); + assertValue("B", "b", "b"); + if (journalLength < lastJournalLength) { + System.out.printf("Journal compacted from %s bytes to %s bytes\n", + lastJournalLength, journalLength); + break; // test passed! + } + lastJournalLength = journalLength; + } + } + + public void testRebuildJournalOnRepeatedEdits() throws Exception { + long lastJournalLength = 0; + while (true) { + long journalLength = journalFile.length(); + set("A", "a", "a"); + set("B", "b", "b"); + if (journalLength < lastJournalLength) { + System.out.printf("Journal compacted from %s bytes to %s bytes\n", + lastJournalLength, journalLength); + break; + } + lastJournalLength = journalLength; + } + + // sanity check that a rebuilt journal behaves normally + assertValue("A", "a", "a"); + assertValue("B", "b", "b"); + } + + public void testOpenCreatesDirectoryIfNecessary() throws Exception { + cache.close(); + File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary"); + cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE); + set("A", "a", "a"); + assertTrue(new File(dir, "A.0").exists()); + assertTrue(new File(dir, "A.1").exists()); + assertTrue(new File(dir, "journal").exists()); + } + + public void testFileDeletedExternally() throws Exception { + set("A", "a", "a"); + getCleanFile("A", 1).delete(); + assertNull(cache.get("A")); + } + + public void testFileBecomesInaccessibleDuringReadResultsInIoException() throws Exception { + set("A", "aaaaa", "a"); + DiskLruCache.Snapshot snapshot = cache.get("A"); + InputStream in = snapshot.getInputStream(0); + assertEquals('a', in.read()); + mockOs.enqueueFault("read"); + try { + in.read(); + fail(); + } catch (IOException expected) { + } + snapshot.close(); + } + + public void testFileBecomesInaccessibleDuringWriteIsSilentlyDiscarded() throws Exception { + set("A", "a", "a"); + DiskLruCache.Editor editor = cache.edit("A"); + OutputStream out0 = editor.newOutputStream(0); + out0.write('b'); + out0.close(); + OutputStream out1 = editor.newOutputStream(1); + out1.write('c'); + mockOs.enqueueFault("write"); + out1.write('c'); // this doesn't throw... + out1.close(); + editor.commit(); // ... but this will abort + assertAbsent("A"); + } + + private void assertJournalEquals(String... expectedBodyLines) throws Exception { + List<String> expectedLines = new ArrayList<String>(); + expectedLines.add(MAGIC); + expectedLines.add(VERSION_1); + expectedLines.add("100"); + expectedLines.add("2"); + expectedLines.add(""); + expectedLines.addAll(Arrays.asList(expectedBodyLines)); + assertEquals(expectedLines, readJournalLines()); + } + + private void createJournal(String... bodyLines) throws Exception { + createJournalWithHeader(MAGIC, VERSION_1, "100", "2", "", bodyLines); + } + + private void createJournalWithHeader(String magic, String version, String appVersion, + String valueCount, String blank, String... bodyLines) throws Exception { + Writer writer = new FileWriter(journalFile); + writer.write(magic + "\n"); + writer.write(version + "\n"); + writer.write(appVersion + "\n"); + writer.write(valueCount + "\n"); + writer.write(blank + "\n"); + for (String line : bodyLines) { + writer.write(line); + writer.write('\n'); + } + writer.close(); + } + + private List<String> readJournalLines() throws Exception { + List<String> result = new ArrayList<String>(); + BufferedReader reader = new BufferedReader(new FileReader(journalFile)); + String line; + while ((line = reader.readLine()) != null) { + result.add(line); + } + reader.close(); + return result; + } + + private File getCleanFile(String key, int index) { + return new File(cacheDir, key + "." + index); + } + + private File getDirtyFile(String key, int index) { + return new File(cacheDir, key + "." + index + ".tmp"); + } + + private String readFile(File file) throws Exception { + Reader reader = new FileReader(file); + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + reader.close(); + return writer.toString(); + } + + public void writeFile(File file, String content) throws Exception { + FileWriter writer = new FileWriter(file); + writer.write(content); + writer.close(); + } + + private void assertInoperable(DiskLruCache.Editor editor) throws Exception { + try { + editor.getString(0); + fail(); + } catch (IllegalStateException expected) { + } + try { + editor.set(0, "A"); + fail(); + } catch (IllegalStateException expected) { + } + try { + editor.newInputStream(0); + fail(); + } catch (IllegalStateException expected) { + } + try { + editor.newOutputStream(0); + fail(); + } catch (IllegalStateException expected) { + } + try { + editor.commit(); + fail(); + } catch (IllegalStateException expected) { + } + try { + editor.abort(); + fail(); + } catch (IllegalStateException expected) { + } + } + + private void generateSomeGarbageFiles() throws Exception { + File dir1 = new File(cacheDir, "dir1"); + File dir2 = new File(dir1, "dir2"); + writeFile(getCleanFile("g1", 0), "A"); + writeFile(getCleanFile("g1", 1), "B"); + writeFile(getCleanFile("g2", 0), "C"); + writeFile(getCleanFile("g2", 1), "D"); + writeFile(getCleanFile("g2", 1), "D"); + writeFile(new File(cacheDir, "otherFile0"), "E"); + dir1.mkdir(); + dir2.mkdir(); + writeFile(new File(dir2, "otherFile1"), "F"); + } + + private void assertGarbageFilesAllDeleted() throws Exception { + assertFalse(getCleanFile("g1", 0).exists()); + assertFalse(getCleanFile("g1", 1).exists()); + assertFalse(getCleanFile("g2", 0).exists()); + assertFalse(getCleanFile("g2", 1).exists()); + assertFalse(new File(cacheDir, "otherFile0").exists()); + assertFalse(new File(cacheDir, "dir1").exists()); + } + + private void set(String key, String value0, String value1) throws Exception { + DiskLruCache.Editor editor = cache.edit(key); + editor.set(0, value0); + editor.set(1, value1); + editor.commit(); + } + + private void assertAbsent(String key) throws Exception { + DiskLruCache.Snapshot snapshot = cache.get(key); + if (snapshot != null) { + snapshot.close(); + fail(); + } + assertFalse(getCleanFile(key, 0).exists()); + assertFalse(getCleanFile(key, 1).exists()); + assertFalse(getDirtyFile(key, 0).exists()); + assertFalse(getDirtyFile(key, 1).exists()); + } + + private void assertValue(String key, String value0, String value1) throws Exception { + DiskLruCache.Snapshot snapshot = cache.get(key); + assertEquals(value0, snapshot.getString(0)); + assertEquals(value1, snapshot.getString(1)); + assertTrue(getCleanFile(key, 0).exists()); + assertTrue(getCleanFile(key, 1).exists()); + snapshot.close(); + } +}
\ No newline at end of file diff --git a/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java b/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java index 8b706cd..a1b9920 100644 --- a/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java +++ b/luni/src/test/java/libcore/java/io/FileOutputStreamTest.java @@ -40,4 +40,31 @@ public class FileOutputStreamTest extends TestCase { } catch (IOException expected) { } } + + public void testClose() throws Exception { + FileOutputStream fos = new FileOutputStream(File.createTempFile("FileOutputStreamTest", "tmp")); + + // Closing an already-closed stream is a no-op... + fos.close(); + // ...as is flushing... + fos.flush(); + + // ...but any explicit write is an error. + byte[] bytes = "hello".getBytes(); + try { + fos.write(bytes); + fail(); + } catch (IOException expected) { + } + try { + fos.write(bytes, 0, 2); + fail(); + } catch (IOException expected) { + } + try { + fos.write(42); + fail(); + } catch (IOException expected) { + } + } } diff --git a/luni/src/test/java/libcore/java/io/OldFileOutputStreamTest.java b/luni/src/test/java/libcore/java/io/OldFileOutputStreamTest.java deleted file mode 100644 index 3661b3e..0000000 --- a/luni/src/test/java/libcore/java/io/OldFileOutputStreamTest.java +++ /dev/null @@ -1,204 +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.java.io; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; - -public class OldFileOutputStreamTest extends junit.framework.TestCase { - - public String fileName; - - FileOutputStream fos; - - FileInputStream fis; - - File f; - - String tmpDirName = System.getProperty("java.io.tmpdir"); - - File tmpDir = new File(tmpDirName); - - public String fileString = "Test_All_Tests\nTest_java_io_BufferedInputStream\nTest_java_io_BufferedOutputStream\nTest_java_io_ByteArrayInputStream\nTest_java_io_ByteArrayOutputStream\nTest_java_io_DataInputStream\nTest_java_io_File\nTest_java_io_FileDescriptor\nTest_java_io_FileInputStream\nTest_java_io_FileNotFoundException\nTest_FileOutputStream\nTest_java_io_FilterInputStream\nTest_java_io_FilterOutputStream\nTest_java_io_InputStream\nTest_java_io_IOException\nTest_java_io_OutputStream\nTest_java_io_PrintStream\nTest_java_io_RandomAccessFile\nTest_java_io_SyncFailedException\nTest_java_lang_AbstractMethodError\nTest_java_lang_ArithmeticException\nTest_java_lang_ArrayIndexOutOfBoundsException\nTest_java_lang_ArrayStoreException\nTest_java_lang_Boolean\nTest_java_lang_Byte\nTest_java_lang_Character\nTest_java_lang_Class\nTest_java_lang_ClassCastException\nTest_java_lang_ClassCircularityError\nTest_java_lang_ClassFormatError\nTest_java_lang_ClassLoader\nTest_java_lang_ClassNotFoundException\nTest_java_lang_CloneNotSupportedException\nTest_java_lang_Double\nTest_java_lang_Error\nTest_java_lang_Exception\nTest_java_lang_ExceptionInInitializerError\nTest_java_lang_Float\nTest_java_lang_IllegalAccessError\nTest_java_lang_IllegalAccessException\nTest_java_lang_IllegalArgumentException\nTest_java_lang_IllegalMonitorStateException\nTest_java_lang_IllegalThreadStateException\nTest_java_lang_IncompatibleClassChangeError\nTest_java_lang_IndexOutOfBoundsException\nTest_java_lang_InstantiationError\nTest_java_lang_InstantiationException\nTest_java_lang_Integer\nTest_java_lang_InternalError\nTest_java_lang_InterruptedException\nTest_java_lang_LinkageError\nTest_java_lang_Long\nTest_java_lang_Math\nTest_java_lang_NegativeArraySizeException\nTest_java_lang_NoClassDefFoundError\nTest_java_lang_NoSuchFieldError\nTest_java_lang_NoSuchMethodError\nTest_java_lang_NullPointerException\nTest_java_lang_Number\nTest_java_lang_NumberFormatException\nTest_java_lang_Object\nTest_java_lang_OutOfMemoryError\nTest_java_lang_RuntimeException\nTest_java_lang_SecurityManager\nTest_java_lang_Short\nTest_java_lang_StackOverflowError\nTest_java_lang_String\nTest_java_lang_StringBuffer\nTest_java_lang_StringIndexOutOfBoundsException\nTest_java_lang_System\nTest_java_lang_Thread\nTest_java_lang_ThreadDeath\nTest_java_lang_ThreadGroup\nTest_java_lang_Throwable\nTest_java_lang_UnknownError\nTest_java_lang_UnsatisfiedLinkError\nTest_java_lang_VerifyError\nTest_java_lang_VirtualMachineError\nTest_java_lang_vm_Image\nTest_java_lang_vm_MemorySegment\nTest_java_lang_vm_ROMStoreException\nTest_java_lang_vm_VM\nTest_java_lang_Void\nTest_java_net_BindException\nTest_java_net_ConnectException\nTest_java_net_DatagramPacket\nTest_java_net_DatagramSocket\nTest_java_net_DatagramSocketImpl\nTest_java_net_InetAddress\nTest_java_net_NoRouteToHostException\nTest_java_net_PlainDatagramSocketImpl\nTest_java_net_PlainSocketImpl\nTest_java_net_Socket\nTest_java_net_SocketException\nTest_java_net_SocketImpl\nTest_java_net_SocketInputStream\nTest_java_net_SocketOutputStream\nTest_java_net_UnknownHostException\nTest_java_util_ArrayEnumerator\nTest_java_util_Date\nTest_java_util_EventObject\nTest_java_util_HashEnumerator\nTest_java_util_Hashtable\nTest_java_util_Properties\nTest_java_util_ResourceBundle\nTest_java_util_tm\nTest_java_util_Vector\n"; - - public void test_ConstructorLjava_io_File() throws Exception { - try { - fos = new FileOutputStream(tmpDir); - fail("Test 1: FileNotFoundException expected."); - } catch (FileNotFoundException e) { - // Expected. - } - - f = new File(fileName = System.getProperty("java.io.tmpdir"), "fos.tst"); - fos = new FileOutputStream(f); - } - - public void test_ConstructorLjava_io_FileZ() throws Exception { - try { - fos = new FileOutputStream(tmpDir, false); - fail("Test 1: FileNotFoundException expected."); - } catch (FileNotFoundException e) { - // Expected. - } - - f = new File(tmpDirName, "fos.tst"); - fos = new FileOutputStream(f, false); - fos.write("FZ1".getBytes(), 0, 3); - fos.close(); - // Append data to existing file - fos = new FileOutputStream(f, true); - fos.write(fileString.getBytes()); - fos.close(); - byte[] buf = new byte[fileString.length() + 3]; - fis = new FileInputStream(f); - fis.read(buf, 0, buf.length); - assertTrue("Test 2: Failed to create appending stream.", new String(buf, 0, - buf.length).equals("FZ1" + fileString)); - fis.close(); - - // Check that the existing file is overwritten - fos = new FileOutputStream(f, false); - fos.write("FZ2".getBytes(), 0, 3); - fos.close(); - fis = new FileInputStream(f); - int bytesRead = fis.read(buf, 0, buf.length); - assertTrue("Test 3: Failed to overwrite stream.", new String(buf, 0, - bytesRead).equals("FZ2")); - } - - public void test_ConstructorLjava_lang_String() throws Exception { - try { - fos = new FileOutputStream(tmpDirName); - fail("Test 1: FileNotFoundException expected."); - } catch (FileNotFoundException e) { - // Expected. - } - } - - public void test_ConstructorLjava_lang_StringZ() throws Exception { - try { - fos = new FileOutputStream(tmpDirName, true); - fail("Test 1: FileNotFoundException expected."); - } catch (FileNotFoundException e) { - // Expected. - } - - f = new File(tmpDirName, "fos.tst"); - fos = new FileOutputStream(f.getPath(), false); - fos.write("HI".getBytes(), 0, 2); - fos.close(); - // Append data to existing file - fos = new FileOutputStream(f.getPath(), true); - fos.write(fileString.getBytes()); - fos.close(); - byte[] buf = new byte[fileString.length() + 2]; - fis = new FileInputStream(f.getPath()); - fis.read(buf, 0, buf.length); - assertTrue("Failed to create appending stream", new String(buf, 0, - buf.length).equals("HI" + fileString)); - fis.close(); - - // Check that the existing file is overwritten - fos = new FileOutputStream(f.getPath(), false); - fos.write("HI".getBytes(), 0, 2); - fos.close(); - fis = new FileInputStream(f.getPath()); - int bytesRead = fis.read(buf, 0, buf.length); - assertTrue("Failed to overwrite stream", new String(buf, 0, - bytesRead).equals("HI")); - } - - public void test_write$B() throws Exception { - // Test for method void java.io.FileOutputStream.write(byte []) - f = new File(System.getProperty("java.io.tmpdir"), "output.tst"); - fos = new FileOutputStream(f.getPath()); - fos.write(fileString.getBytes()); - fos.close(); - try { - fos.write(fileString.getBytes()); - fail("Test 1: IOException expected."); - } catch (IOException e) { - // Expected. - } - - fis = new FileInputStream(f.getPath()); - byte rbytes[] = new byte[4000]; - fis.read(rbytes, 0, fileString.length()); - assertTrue("Test 2: Incorrect string written or read.", - new String(rbytes, 0, fileString.length()).equals(fileString)); - } - - public void test_writeI() throws IOException { - // Test for method void java.io.FileOutputStream.write(int) - f = new File(System.getProperty("java.io.tmpdir"), "output.tst"); - fos = new FileOutputStream(f.getPath()); - fos.write('t'); - fos.close(); - try { - fos.write(42); - fail("Test: IOException expected."); - } catch (IOException e) { - // Expected. - } - - fis = new FileInputStream(f.getPath()); - assertEquals("Test 1: Incorrect char written or read.", - 't', fis.read()); - } - - public void test_write$BII3() { - try { - new FileOutputStream(new FileDescriptor()).write(new byte[1], 0, 0); - } catch (Exception e) { - fail("Unexpected exception: " + e); - } - } - - public void test_getChannel() throws Exception { - // Make sure that system properties are set correctly - if (tmpDir == null) { - throw new Exception("System property java.io.tmpdir not defined."); - } - File tmpfile = File.createTempFile("FileOutputStream", "tmp"); - tmpfile.deleteOnExit(); - FileOutputStream fos = new FileOutputStream(tmpfile); - byte[] b = new byte[10]; - for (int i = 10; i < b.length; i++) { - b[i] = (byte) i; - } - fos.write(b); - fos.flush(); - fos.close(); - FileOutputStream f = new FileOutputStream(tmpfile, true); - assertEquals(10, f.getChannel().position()); - } - - protected void tearDown() throws Exception { - super.tearDown(); - if (f != null) - f.delete(); - if (fis != null) - fis.close(); - if (fos != null) - fos.close(); - } -} diff --git a/luni/src/test/java/libcore/java/io/SerializationTest.java b/luni/src/test/java/libcore/java/io/SerializationTest.java new file mode 100644 index 0000000..74becfe --- /dev/null +++ b/luni/src/test/java/libcore/java/io/SerializationTest.java @@ -0,0 +1,39 @@ +/* + * 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.Serializable; +import junit.framework.TestCase; +import libcore.java.util.SerializableTester; + +public final class SerializationTest extends TestCase { + + // http://b/4471249 + public void testSerializeFieldMadeTransient() throws Exception { + // this was created by serializing a FieldMadeTransient with a non-0 transientInt + String s = "aced0005737200346c6962636f72652e6a6176612e696f2e53657269616c697a6174696f6e54657" + + "374244669656c644d6164655472616e7369656e74000000000000000002000149000c7472616e736" + + "9656e74496e747870abababab"; + FieldMadeTransient deserialized = (FieldMadeTransient) SerializableTester.deserializeHex(s); + assertEquals(0, deserialized.transientInt); + } + + static class FieldMadeTransient implements Serializable { + private static final long serialVersionUID = 0L; + private transient int transientInt; + } +} diff --git a/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java b/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java index 470c2e4..d2b1b0a 100644 --- a/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java +++ b/luni/src/test/java/libcore/java/lang/IntegralToStringTest.java @@ -31,4 +31,11 @@ public final class IntegralToStringTest extends TestCase { assertEquals("ffffffff", IntegralToString.intToHexString(-1, false, 0)); } + + public void testBytesToHexString() { + assertEquals("abcdef", IntegralToString.bytesToHexString( + new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, false)); + assertEquals("ABCDEF", IntegralToString.bytesToHexString( + new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true)); + } } diff --git a/luni/src/test/java/libcore/java/net/CookiesTest.java b/luni/src/test/java/libcore/java/net/CookiesTest.java index 69dea61..87aa571 100644 --- a/luni/src/test/java/libcore/java/net/CookiesTest.java +++ b/luni/src/test/java/libcore/java/net/CookiesTest.java @@ -268,6 +268,39 @@ public class CookiesTest extends TestCase { + "b=\"banana\";$Path=\"/\";$Domain=\".local\""); } + public void testRedirectsDoNotIncludeTooManyCookies() throws Exception { + MockWebServer redirectTarget = new MockWebServer(); + redirectTarget.enqueue(new MockResponse().setBody("A")); + redirectTarget.play(); + + MockWebServer redirectSource = new MockWebServer(); + redirectSource.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + .addHeader("Location: " + redirectTarget.getUrl("/"))); + redirectSource.play(); + + CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); + HttpCookie cookie = new HttpCookie("c", "cookie"); + cookie.setDomain(".local"); + cookie.setPath("/"); + String portList = Integer.toString(redirectSource.getPort()); + cookie.setPortlist(portList); + cookieManager.getCookieStore().add(redirectSource.getUrl("/").toURI(), cookie); + CookieHandler.setDefault(cookieManager); + + get(redirectSource, "/"); + RecordedRequest request = redirectSource.takeRequest(); + + assertContains(request.getHeaders(), "Cookie: $Version=\"1\"; " + + "c=\"cookie\";$Path=\"/\";$Domain=\".local\";$Port=\"" + portList + "\""); + + for (String header : redirectTarget.takeRequest().getHeaders()) { + if (header.startsWith("Cookie")) { + fail(header); + } + } + } + /** * Test which headers show up where. The cookie manager should be notified of both * user-specified and derived headers like {@code Content-Length}. Headers named {@code Cookie} diff --git a/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java b/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java index 07e0897..0b4009c 100644 --- a/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java +++ b/luni/src/test/java/libcore/java/net/OldDatagramSocketTest.java @@ -267,7 +267,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { fail("Exception during test : " + e.getMessage()); } - if ("true".equals(System.getProperty("run.ipv6tests"))) { System.out .println("Running test_connectLjava_net_InetAddressI" + "(DatagramSocketTest) with IPv6GlobalAddressJcl4: " @@ -285,7 +284,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { } catch (Exception e) { fail("Exception during test : " + e.getMessage()); } - } try { // Create a connected datagram socket to test @@ -627,7 +625,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { + e.toString()); } - if ("true".equals(System.getProperty("run.ipv6tests"))) { System.out .println("Running test_connectLjava_net_InetAddressI(DatagramSocketTest) with IPv6 address"); try { @@ -643,7 +640,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { "Unexcpected exception when trying to connect at native level with bad IPv6 address for signature with no exception to be returned: " + e.toString()); } - } } public void test_disconnect() { @@ -659,7 +655,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { fail("Exception during test : " + e.getMessage()); } - if ("true".equals(System.getProperty("run.ipv6tests"))) { System.out .println("Running test_disconnect(DatagramSocketTest) with IPv6GlobalAddressJcl4: " + Support_Configuration.IPv6GlobalAddressJcl4); @@ -675,8 +670,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { } catch (Exception e) { fail("Exception during test : " + e.getMessage()); } - } - } public void test_getInetAddress() { @@ -719,49 +712,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { } - public void test_getLocalAddress() { - // Test for method java.net.InetAddress - // java.net.DatagramSocket.getLocalAddress() - InetAddress local = null; - try { - int portNumber = Support_PortManager.getNextPortForUDP(); - local = InetAddress.getLocalHost(); - ds = new java.net.DatagramSocket(portNumber, local); - assertTrue("Returned incorrect address. Got:" - + ds.getLocalAddress() - + " wanted: " - + InetAddress.getByName(InetAddress.getLocalHost() - .getHostName()), InetAddress.getByName( - InetAddress.getLocalHost().getHostName()).equals( - ds.getLocalAddress())); - - // now validate thet behaviour when the any address is returned - String preferIPv4StackValue = System - .getProperty("java.net.preferIPv4Stack"); - String preferIPv6AddressesValue = System - .getProperty("java.net.preferIPv6Addresses"); - DatagramSocket s = new DatagramSocket(0); - if (((preferIPv4StackValue == null) || preferIPv4StackValue - .equalsIgnoreCase("false")) - && (preferIPv6AddressesValue != null) - && (preferIPv6AddressesValue.equals("true"))) { - assertTrue( - "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=false " - + s.getLocalSocketAddress(), s - .getLocalAddress() instanceof Inet6Address); - } else { - assertTrue( - "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=true " - + s.getLocalSocketAddress(), s - .getLocalAddress() instanceof Inet4Address); - } - s.close(); - } catch (Exception e) { - fail( - "Exception during getLocalAddress: " + local + " - " + e); - } - } - public void test_getLocalPort() { // Test for method int java.net.DatagramSocket.getLocalPort() try { @@ -1314,148 +1264,92 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { } public void test_bindLjava_net_SocketAddress() throws Exception { - class mySocketAddress extends SocketAddress { - - public mySocketAddress() { - } - } - - DatagramServer server = null; - try { - // now create a socket that is not bound and then bind it - int[] ports = Support_PortManager.getNextPortsForUDP(3); - int portNumber = ports[0]; - int serverPortNumber = ports[1]; - DatagramSocket theSocket = new DatagramSocket( - new InetSocketAddress(InetAddress.getLocalHost(), - portNumber)); - - // validate that the localSocketAddress reflects the address we - // bound to - assertTrue("Local address not correct after bind:" - + theSocket.getLocalSocketAddress().toString() - + "Expected: " - + (new InetSocketAddress(InetAddress.getLocalHost(), - portNumber)).toString(), theSocket - .getLocalSocketAddress().equals( - new InetSocketAddress(InetAddress.getLocalHost(), - portNumber))); - - // now make sure that datagrams sent from this socket appear to come - // from the address we bound to - InetAddress localHost = InetAddress.getLocalHost(); - portNumber = ports[2]; - DatagramSocket ds = new DatagramSocket((SocketAddress) null); - ds.bind(new InetSocketAddress(localHost, portNumber)); - - try { - server = new DatagramServer(serverPortNumber, localHost); - server.start(); - Thread.sleep(1000); - } catch (Exception e) { - fail( - "Failed to set up datagram server for bin datagram socket test "); - } + int[] ports = Support_PortManager.getNextPortsForUDP(3); + int serverPortNumber = ports[1]; - ds.connect(new InetSocketAddress(localHost, serverPortNumber)); + // now create a socket that is not bound and then bind it + InetAddress localHost = InetAddress.getLocalHost(); + InetSocketAddress localAddress1 = new InetSocketAddress(localHost, ports[0]); + DatagramSocket theSocket = new DatagramSocket(localAddress1); - byte[] sendBytes = { 'T', 'e', 's', 't', 0 }; - DatagramPacket send = new DatagramPacket(sendBytes, - sendBytes.length); - ds.send(send); - Thread.sleep(1000); - ds.close(); - assertTrue( - "Address in packet sent does not match address bound to:" - + server.rdp.getAddress() + ":" - + server.rdp.getPort() + ":" + localHost + ":" - + portNumber, (server.rdp.getAddress() - .equals(localHost)) - && (server.rdp.getPort() == portNumber)); - - // validate if we pass in null that it picks an address for us and - // all is ok - theSocket = new DatagramSocket((SocketAddress) null); - theSocket.bind(null); - assertNotNull("Bind with null did not work", theSocket - .getLocalSocketAddress()); - theSocket.close(); + // validate that the localSocketAddress reflects the address we bound to + assertEquals(localAddress1, theSocket.getLocalSocketAddress()); - // now check the error conditions + // now make sure that datagrams sent from this socket appear to come + // from the address we bound to + InetSocketAddress localAddress2 = new InetSocketAddress(localHost, ports[2]); + DatagramSocket ds = new DatagramSocket((SocketAddress) null); + ds.bind(localAddress2); - // Address we cannot bind to - theSocket = new DatagramSocket((SocketAddress) null); - try { - theSocket - .bind(new InetSocketAddress( - InetAddress - .getByAddress(Support_Configuration.nonLocalAddressBytes), - Support_PortManager.getNextPortForUDP())); - fail("No exception when binding to bad address"); - } catch (SocketException ex) { - } - theSocket.close(); + DatagramServer server = new DatagramServer(serverPortNumber, localHost); + server.start(); + Thread.sleep(1000); - // Address that we have allready bound to - ports = Support_PortManager.getNextPortsForUDP(2); - theSocket = new DatagramSocket((SocketAddress) null); - DatagramSocket theSocket2 = new DatagramSocket(ports[0]); - try { - InetSocketAddress theAddress = new InetSocketAddress( - InetAddress.getLocalHost(), ports[1]); - theSocket.bind(theAddress); - theSocket2.bind(theAddress); - fail("No exception binding to address that is not available"); - } catch (SocketException ex) { - //expected - } - theSocket.close(); - theSocket2.close(); + ds.connect(new InetSocketAddress(localHost, serverPortNumber)); - /* - SecurityManager sm = new SecurityManager() { + byte[] sendBytes = { 'T', 'e', 's', 't', 0 }; + DatagramPacket send = new DatagramPacket(sendBytes, sendBytes.length); + ds.send(send); + Thread.sleep(1000); + ds.close(); + // Check that the address in the packet matches the bound address. + assertEquals(localAddress2, server.rdp.getSocketAddress()); - public void checkPermission(Permission perm) { - } + if (server != null) { + server.stopServer(); + } + } - public void checkListen(int port) { - throw new SecurityException(); - } - }; + public void test_bindLjava_net_SocketAddress_null() throws Exception { + // validate if we pass in null that it picks an address for us. + DatagramSocket theSocket = new DatagramSocket((SocketAddress) null); + theSocket.bind(null); + assertNotNull(theSocket.getLocalSocketAddress()); + theSocket.close(); + } - ports = Support_PortManager.getNextPortsForUDP(2); - ds = new DatagramSocket(null); - SecurityManager oldSm = System.getSecurityManager(); - System.setSecurityManager(sm); - try { + public void test_bindLjava_net_SocketAddress_bad_address() throws Exception { + // Address we cannot bind to + DatagramSocket theSocket = new DatagramSocket((SocketAddress) null); + try { + InetAddress badAddress = InetAddress.getByAddress(Support_Configuration.nonLocalAddressBytes); + theSocket.bind(new InetSocketAddress(badAddress, Support_PortManager.getNextPortForUDP())); + fail("No exception when binding to bad address"); + } catch (SocketException expected) { + } + theSocket.close(); + } - ds.bind(new InetSocketAddress(localHost, ports[0])); - fail("SecurityException should be thrown."); - } catch (SecurityException e) { - // expected - } catch (SocketException e) { - fail("SocketException was thrown."); - } finally { - System.setSecurityManager(oldSm); - } - */ + public void test_bindLjava_net_SocketAddress_address_in_use() throws Exception { + // Address that we have already bound to + int[] ports = Support_PortManager.getNextPortsForUDP(2); + DatagramSocket theSocket1 = new DatagramSocket((SocketAddress) null); + DatagramSocket theSocket2 = new DatagramSocket(ports[0]); + try { + InetSocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), ports[1]); + theSocket1.bind(theAddress); + theSocket2.bind(theAddress); + fail("No exception binding to address that is not available"); + } catch (SocketException expected) { + } + theSocket1.close(); + theSocket2.close(); + } - // unsupported SocketAddress subclass - theSocket = new DatagramSocket((SocketAddress) null); - try { - theSocket.bind(new mySocketAddress()); - fail("No exception when binding using unsupported SocketAddress subclass"); - } catch (IllegalArgumentException ex) { + public void test_bindLjava_net_SocketAddress_unsupported_address_type() throws Exception { + class mySocketAddress extends SocketAddress { + public mySocketAddress() { } - theSocket.close(); - - } catch (Exception e) { - fail("Unexpected exception during bind test : " + e.getMessage()); } - if (server != null) { - server.stopServer(); + // unsupported SocketAddress subclass + DatagramSocket theSocket = new DatagramSocket((SocketAddress) null); + try { + theSocket.bind(new mySocketAddress()); + fail("No exception when binding using unsupported SocketAddress subclass"); + } catch (IllegalArgumentException expected) { } + theSocket.close(); } public void test_connectLjava_net_SocketAddress() { @@ -1884,70 +1778,6 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { } } - public void test_getLocalSocketAddress() throws Exception { - int portNumber = Support_PortManager.getNextPortForUDP(); - DatagramSocket s = new DatagramSocket(new InetSocketAddress( - InetAddress.getLocalHost(), portNumber)); - assertTrue("Returned incorrect InetSocketAddress(1):" - + s.getLocalSocketAddress().toString() - + "Expected: " - + (new InetSocketAddress(InetAddress.getLocalHost(), - portNumber)).toString(), s.getLocalSocketAddress() - .equals( - new InetSocketAddress(InetAddress.getLocalHost(), - portNumber))); - s.close(); - - InetSocketAddress remoteAddress = (InetSocketAddress) s - .getRemoteSocketAddress(); - - // now create a socket that is not bound and validate we get the - // right answer - DatagramSocket theSocket = new DatagramSocket((SocketAddress) null); - assertNull( - "Returned incorrect InetSocketAddress -unbound socket- Expected null", - theSocket.getLocalSocketAddress()); - - // now bind the socket and make sure we get the right answer - portNumber = Support_PortManager.getNextPortForUDP(); - theSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), - portNumber)); - assertTrue("Returned incorrect InetSocketAddress(2):" - + theSocket.getLocalSocketAddress().toString() - + "Expected: " - + (new InetSocketAddress(InetAddress.getLocalHost(), - portNumber)).toString(), theSocket - .getLocalSocketAddress().equals( - new InetSocketAddress(InetAddress.getLocalHost(), - portNumber))); - theSocket.close(); - - // now validate thet behaviour when the any address is returned - s = new DatagramSocket(0); - - String preferIPv4StackValue = System - .getProperty("java.net.preferIPv4Stack"); - String preferIPv6AddressesValue = System - .getProperty("java.net.preferIPv6Addresses"); - if (((preferIPv4StackValue == null) || preferIPv4StackValue - .equalsIgnoreCase("false")) - && (preferIPv6AddressesValue != null) - && (preferIPv6AddressesValue.equals("true"))) { - assertTrue( - "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=false " - + s.getLocalSocketAddress(), - ((InetSocketAddress) s.getLocalSocketAddress()) - .getAddress() instanceof Inet6Address); - } else { - assertTrue( - "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=true " - + s.getLocalSocketAddress(), - ((InetSocketAddress) s.getLocalSocketAddress()) - .getAddress() instanceof Inet4Address); - } - s.close(); - } - public void test_setReuseAddressZ() throws Exception { // test case were we set it to false DatagramSocket theSocket1 = null; @@ -2198,6 +2028,7 @@ public class OldDatagramSocketTest extends junit.framework./*Socket*/TestCase { DatagramChannel channel = DatagramChannel.open(); DatagramSocket socket = channel.socket(); assertEquals(channel, socket.getChannel()); + socket.close(); } class TestDatagramSocketImplFactory implements DatagramSocketImplFactory { diff --git a/luni/src/test/java/libcore/java/net/OldResponseCacheTest.java b/luni/src/test/java/libcore/java/net/OldResponseCacheTest.java deleted file mode 100644 index d292414..0000000 --- a/luni/src/test/java/libcore/java/net/OldResponseCacheTest.java +++ /dev/null @@ -1,231 +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.java.net; - -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.HttpURLConnection; -import java.net.NetPermission; -import java.net.ResponseCache; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.security.Permission; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import junit.framework.TestCase; -import tests.support.Support_PortManager; -import tests.support.Support_TestWebData; -import tests.support.Support_TestWebServer; - -public class OldResponseCacheTest extends TestCase { - - - - public void test_GetDefault() throws Exception { - assertNull(ResponseCache.getDefault()); - } - - public void test_SetDefaultLjava_net_ResponseCache_Normal() throws Exception { - ResponseCache rc1 = new MockResponseCache(); - ResponseCache rc2 = new MockResponseCache(); - ResponseCache.setDefault(rc1); - assertSame(ResponseCache.getDefault(), rc1); - ResponseCache.setDefault(rc2); - assertSame(ResponseCache.getDefault(), rc2); - ResponseCache.setDefault(null); - assertNull(ResponseCache.getDefault()); - } - - public void test_get() throws Exception { - String uri = "http://localhost/"; - URL url = new URL(uri); - TestResponseCache cache = new TestResponseCache(uri, true); - ResponseCache.setDefault(cache); - HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); - httpCon.setUseCaches(true); - httpCon.connect(); - try { - Thread.sleep(5000); - } catch(Exception e) {} - - InputStream is = httpCon.getInputStream(); - byte[] array = new byte [10]; - is.read(array); - assertEquals(url.toURI(), cache.getWasCalled); - assertEquals("Cache test", new String(array)); - is.close(); - httpCon.disconnect(); - - } - - public void test_put() throws Exception { - // Create test ResponseCache - TestResponseCache cache = new TestResponseCache( - "http://localhost/not_cached", false); - ResponseCache.setDefault(cache); - - // Start Server - int port = Support_PortManager.getNextPort(); - Support_TestWebServer s = new Support_TestWebServer(); - try { - s.initServer(port, 10000, false); - Thread.currentThread().sleep(2500); - - // Create connection to server - URL url = new URL("http://localhost:" + port + "/test1"); - HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); - httpCon.setUseCaches(true); - httpCon.connect(); - Thread.currentThread().sleep(2500); - - // Check that a call to the cache was made. - assertEquals(url.toURI(), cache.getWasCalled); - // Make the HttpConnection get the content. It should try to - // put it into the cache. - httpCon.getContent(); - // Check if put was called - assertEquals(url.toURI(), cache.putWasCalled); - - // get the - InputStream is = httpCon.getInputStream(); - - byte[] array = new byte[Support_TestWebData.test1.length]; - is.read(array); - assertTrue(Arrays.equals(Support_TestWebData.tests[0], array)); - is.close(); - httpCon.disconnect(); - } finally { - s.close(); - } - } - - /* - * MockResponseCache for testSetDefault(ResponseCache) - */ - class MockResponseCache extends ResponseCache { - - public CacheResponse get(URI arg0, String arg1, Map arg2) - throws IOException { - return null; - } - - public CacheRequest put(URI arg0, URLConnection arg1) - throws IOException { - return null; - } - } - - /* - * MockSecurityMaanger. It denies NetPermission("getResponseCache") and - * NetPermission("setResponseCache"). - */ - class MockSM extends SecurityManager { - public void checkPermission(Permission permission) { - if (permission instanceof NetPermission) { - if ("setResponseCache".equals(permission.getName())) { - throw new SecurityException(); - } - } - - if (permission instanceof NetPermission) { - if ("getResponseCache".equals(permission.getName())) { - - throw new SecurityException(); - } - } - - if (permission instanceof RuntimePermission) { - if ("setSecurityManager".equals(permission.getName())) { - return; - } - } - } - } - - class TestCacheResponse extends CacheResponse { - InputStream is = null; - - public TestCacheResponse(String body) { - is = new ByteArrayInputStream(body.getBytes()); - } - - @Override - public InputStream getBody() { - return is; - } - - @Override - public Map<String, List<String>> getHeaders() throws IOException { - return new HashMap<String, List<String>>(); - } - } - - class TestCacheRequest extends CacheRequest { - - @Override - public OutputStream getBody() { - return null; - } - - @Override - public void abort() { - } - } - - class TestResponseCache extends ResponseCache { - - URI uri1 = null; - boolean testGet = false; - - public URI getWasCalled = null; - public URI putWasCalled = null; - - TestResponseCache(String uri, boolean testGet) { - try { - uri1 = new URI(uri); - } catch (URISyntaxException e) { - } - this.testGet = testGet; - } - - @Override - public CacheResponse get(URI uri, String rqstMethod, Map rqstHeaders) { - getWasCalled = uri; - if (testGet && uri.equals(uri1)) { - return new TestCacheResponse("Cache test"); - } - return null; - } - - @Override - public CacheRequest put(URI uri, URLConnection conn) { - putWasCalled = uri; - if (!testGet && uri.equals(uri1)) { - return new TestCacheRequest(); - } - return null; - } - } -} diff --git a/luni/src/test/java/libcore/java/net/OldSocketTest.java b/luni/src/test/java/libcore/java/net/OldSocketTest.java index 6247719..42f7b78 100644 --- a/luni/src/test/java/libcore/java/net/OldSocketTest.java +++ b/luni/src/test/java/libcore/java/net/OldSocketTest.java @@ -20,6 +20,7 @@ package libcore.java.net; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.BindException; import java.net.ConnectException; import java.net.Inet4Address; import java.net.Inet6Address; @@ -104,13 +105,7 @@ public class OldSocketTest extends OldSocketTestCase { try { socket = new Socket(InetAddress.getByName(null), sport); InetAddress address = socket.getLocalAddress(); - if (Boolean.getBoolean("java.net.preferIPv6Addresses")) { - assertTrue( - address.equals(InetAddress.getByName("::1")) || - address.equals(InetAddress.getByName("0:0:0:0:0:0:0:1"))); - } else { - assertEquals(address, InetAddress.getByName("127.0.0.1")); - } + assertTrue(address.isLoopbackAddress()); } finally { try { socket.close(); @@ -118,111 +113,31 @@ public class OldSocketTest extends OldSocketTestCase { } } - public void test_ConstructorLjava_lang_StringILjava_net_InetAddressI() - throws IOException { - // Test for method java.net.Socket(java.lang.String, int, - // java.net.InetAddress, int) + public void test_ConstructorLjava_lang_StringILjava_net_InetAddressI1() throws IOException { int sport = startServer("Cons String,I,InetAddress,I"); int portNumber = Support_PortManager.getNextPort(); s = new Socket(InetAddress.getLocalHost().getHostName(), sport, InetAddress.getLocalHost(), portNumber); assertTrue("Failed to create socket", s.getPort() == sport); + } - if (("true".equals(System.getProperty("java.net.preferIPv6Addresses"))) - && !("true".equals(System - .getProperty("java.net.preferIPv4Stack")))) { - - // ALTERNATE IPv6 TEST - if ("true".equals(System.getProperty("run.ipv6tests"))) { - System.out - .println("Running testConstructorLjava_lang_StringILjava_net_InetAddressI(OldSocketTest) with IPv6GlobalAddressJcl4: " - + Support_Configuration.IPv6GlobalAddressJcl4); - int testPort = Support_PortManager.getNextPort(); - Socket s1 = null, s2 = null; - try { - s1 = new Socket( - Support_Configuration.IPv6GlobalAddressJcl4, 80, - InetAddress.getLocalHost(), testPort); - } catch (IOException e) { - // check here if InetAddress.getLocalHost() is returning the - // loopback address. - // if so that is likely the cause of the failure - String warning = ""; - try { - InetAddress returnedLocalHost = InetAddress - .getLocalHost(); - // don't use isLoopbackAddress for some configurations - // as they do not have it - if (returnedLocalHost.isLoopbackAddress()) { - warning = " - WARNING RETURNED LOCAL HOST IS THE LOOPBACK ADDRESS - MACHINE IS LIKELY NOT CONFIGURED CORRECTLY - THIS LIKELY CAUSED THE FAILURE"; - - } - } catch (Exception ex) { - warning = " - WARNING COULD NOT GET LOCAL HOST - " + ex; - } - - fail("Exception creating 1st socket" + warning + ": " + e); - } - boolean exception = false; - try { - s2 = new Socket( - Support_Configuration.IPv6GlobalAddressJcl4, 80, - InetAddress.getLocalHost(), testPort); - } catch (IOException e) { - exception = true; - } - try { - s1.close(); - if (!exception) - s2.close(); - } catch (IOException e) { - } - assertTrue("Was able to create two sockets on same port", - exception); - } - - } else { - int testPort = Support_PortManager.getNextPort(); - Socket s1 = null, s2 = null; - int serverPort = ss.getLocalPort(); - try { - s1 = new Socket("127.0.0.1", serverPort, InetAddress - .getLocalHost(), testPort); - } catch (IOException e) { - e.printStackTrace(); - - // check here if InetAddress.getLocalHost() is returning the - // loopback address. - // if so that is likely the cause of the failure - String warning = ""; - try { - InetAddress returnedLocalHost = InetAddress.getLocalHost(); - // don't use isLoopbackAddress for some configurations as - // they do not have it - if (returnedLocalHost.isLoopbackAddress()) { - warning = " - WARNING RETURNED LOCAL HOST IS THE LOOPBACK ADDRESS - MACHINE IS LIKELY NOT CONFIGURED CORRECTLY - THIS LIKELY CAUSED THE FAILURE"; - - } - } catch (Exception ex) { - warning = " - WARNING COULD NOT GET LOCAL HOST - " + ex; - } - - fail("Exception creating 1st socket" + warning + ": " + e); - } - boolean exception = false; + public void test_ConstructorLjava_lang_StringILjava_net_InetAddressI2() throws IOException { + int testPort = Support_PortManager.getNextPort(); + Socket s1 = new Socket("www.google.com", 80, null, testPort); + try { + Socket s2 = new Socket("www.google.com", 80, null, testPort); try { - s2 = new Socket("127.0.0.1", serverPort, InetAddress - .getLocalHost(), testPort); - } catch (IOException e) { - exception = true; + s2.close(); + } catch (IOException ignored) { } + fail("second connect should have failed with EADDRINUSE"); + } catch (BindException expected) { + // success! + } finally { try { s1.close(); - if (!exception) - s2.close(); - } catch (IOException e) { + } catch (IOException ignored) { } - assertTrue("Was able to create two sockets on same port", exception); } } @@ -344,31 +259,13 @@ public class OldSocketTest extends OldSocketTestCase { assertEquals("Returned incorrect InetAddress", InetAddress.getLocalHost(), s.getLocalAddress()); - // now validate that behaviour when the any address is returned - String preferIPv4StackValue = System - .getProperty("java.net.preferIPv4Stack"); - String preferIPv6AddressesValue = System - .getProperty("java.net.preferIPv6Addresses"); - + // now check behavior when the ANY address is returned s = new Socket(); s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0)); - if (((preferIPv4StackValue == null) || preferIPv4StackValue - .equalsIgnoreCase("false")) - && (preferIPv6AddressesValue != null) - && (preferIPv6AddressesValue.equals("true"))) { - assertTrue( - "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=false " - + s.getLocalSocketAddress(), + assertTrue("ANY address not IPv6: " + s.getLocalSocketAddress(), s.getLocalAddress() instanceof Inet6Address); - } else { - assertTrue( - "ANY address not returned correctly (getLocalAddress) with preferIPv6Addresses=true, preferIPv4Stack=true " - + s.getLocalSocketAddress(), - s.getLocalAddress() instanceof Inet4Address); - } s.close(); - } public void test_getLocalPort() throws IOException { @@ -865,47 +762,15 @@ public class OldSocketTest extends OldSocketTestCase { s = new Socket(); s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0)); - String preferIPv4StackValue = System - .getProperty("java.net.preferIPv4Stack"); - String preferIPv6AddressesValue = System - .getProperty("java.net.preferIPv6Addresses"); - if (((preferIPv4StackValue == null) || preferIPv4StackValue - .equalsIgnoreCase("false")) - && (preferIPv6AddressesValue != null) - && (preferIPv6AddressesValue.equals("true"))) { - assertTrue( - "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=false " - + s.getLocalSocketAddress(), - ((InetSocketAddress) s.getLocalSocketAddress()) - .getAddress() instanceof Inet6Address); - } else { - assertTrue( - "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=true " - + s.getLocalSocketAddress(), - ((InetSocketAddress) s.getLocalSocketAddress()) - .getAddress() instanceof Inet4Address); - } + assertTrue("ANY address not IPv6: " + s.getLocalSocketAddress(), + ((InetSocketAddress) s.getLocalSocketAddress()).getAddress() instanceof Inet6Address); s.close(); // now validate the same for getLocalAddress s = new Socket(); s.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0)); - if (((preferIPv4StackValue == null) || preferIPv4StackValue - .equalsIgnoreCase("false")) - && (preferIPv6AddressesValue != null) - && (preferIPv6AddressesValue.equals("true"))) { - assertTrue( - "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=false " - + s.getLocalSocketAddress(), - ((InetSocketAddress) s.getLocalSocketAddress()) - .getAddress() instanceof Inet6Address); - } else { - assertTrue( - "ANY address not returned correctly with preferIPv6Addresses=true, preferIPv4Stack=true " - + s.getLocalSocketAddress(), - ((InetSocketAddress) s.getLocalSocketAddress()) - .getAddress() instanceof Inet4Address); - } + assertTrue("ANY address not IPv6: " + s.getLocalSocketAddress(), + ((InetSocketAddress) s.getLocalSocketAddress()).getAddress() instanceof Inet6Address); s.close(); } diff --git a/luni/src/test/java/libcore/java/net/ServerSocketTest.java b/luni/src/test/java/libcore/java/net/ServerSocketTest.java new file mode 100644 index 0000000..fe9d423 --- /dev/null +++ b/luni/src/test/java/libcore/java/net/ServerSocketTest.java @@ -0,0 +1,46 @@ +/* + * 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.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class ServerSocketTest extends junit.framework.TestCase { + public void testTimeoutAfterAccept() throws Exception { + final ServerSocket ss = new ServerSocket(0); + ss.setReuseAddress(true); + // On Unix, the receive timeout is inherited by the result of accept(2). + // Java specifies that it should always be 0 instead. + ss.setSoTimeout(1234); + final Socket[] result = new Socket[1]; + Thread t = new Thread(new Runnable() { + public void run() { + try { + result[0] = ss.accept(); + } catch (IOException ex) { + ex.printStackTrace(); + fail(); + } + } + }); + t.start(); + new Socket(ss.getInetAddress(), ss.getLocalPort()); + t.join(); + assertEquals(0, result[0].getSoTimeout()); + } +} diff --git a/luni/src/test/java/libcore/java/net/SocketTest.java b/luni/src/test/java/libcore/java/net/SocketTest.java index 9fa09b7..e796e1e 100644 --- a/luni/src/test/java/libcore/java/net/SocketTest.java +++ b/luni/src/test/java/libcore/java/net/SocketTest.java @@ -53,7 +53,7 @@ public class SocketTest extends junit.framework.TestCase { try { // Bind to an ephemeral local port. s.bind(new InetSocketAddress("localhost", 0)); - assertTrue(s.getLocalAddress().isLoopbackAddress()); + assertTrue(s.getLocalAddress().toString(), s.getLocalAddress().isLoopbackAddress()); // What local port did we get? int localPort = s.getLocalPort(); assertTrue(localPort > 0); diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index 68cff88..6393e02 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -57,6 +57,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import junit.framework.TestCase; import libcore.java.security.TestKeyStore; import libcore.javax.net.ssl.TestSSLContext; import tests.http.MockResponse; @@ -69,7 +70,7 @@ import static tests.http.SocketPolicy.SHUTDOWN_INPUT_AT_END; import static tests.http.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; import tests.net.StuckServer; -public class URLConnectionTest extends junit.framework.TestCase { +public final class URLConnectionTest extends TestCase { private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { @@ -77,6 +78,9 @@ public class URLConnectionTest extends junit.framework.TestCase { } }; + /** base64("username:password") */ + private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ="; + private MockWebServer server = new MockWebServer(); private String hostName; @@ -105,8 +109,11 @@ public class URLConnectionTest extends junit.framework.TestCase { HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); urlConnection.addRequestProperty("D", "e"); urlConnection.addRequestProperty("D", "f"); + assertEquals("f", urlConnection.getRequestProperty("D")); + assertEquals("f", urlConnection.getRequestProperty("d")); Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties(); assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); + assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d"))); try { requestHeaders.put("G", Arrays.asList("h")); fail("Modified an unmodifiable view."); @@ -128,7 +135,9 @@ public class URLConnectionTest extends junit.framework.TestCase { } catch (NullPointerException expected) { } urlConnection.setRequestProperty("NullValue", null); // should fail silently! + assertNull(urlConnection.getRequestProperty("NullValue")); urlConnection.addRequestProperty("AnotherNullValue", null); // should fail silently! + assertNull(urlConnection.getRequestProperty("AnotherNullValue")); urlConnection.getResponseCode(); RecordedRequest request = server.takeRequest(); @@ -149,13 +158,27 @@ public class URLConnectionTest extends junit.framework.TestCase { fail("Set header after connect"); } catch (IllegalStateException expected) { } + try { + urlConnection.getRequestProperties(); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testGetRequestPropertyReturnsLastValue() throws Exception { + server.play(); + HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); + urlConnection.addRequestProperty("A", "value1"); + urlConnection.addRequestProperty("A", "value2"); + assertEquals("value2", urlConnection.getRequestProperty("A")); } public void testResponseHeaders() throws IOException, InterruptedException { server.enqueue(new MockResponse() .setStatus("HTTP/1.0 200 Fantastic") - .addHeader("A: b") .addHeader("A: c") + .addHeader("B: d") + .addHeader("A: e") .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); server.play(); @@ -165,17 +188,38 @@ public class URLConnectionTest extends junit.framework.TestCase { assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null)); Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields(); assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null)); - assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A"))); + assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A"))); + assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a"))); try { responseHeaders.put("N", Arrays.asList("o")); fail("Modified an unmodifiable view."); } catch (UnsupportedOperationException expected) { } try { - responseHeaders.get("A").add("d"); + responseHeaders.get("A").add("f"); fail("Modified an unmodifiable view."); } catch (UnsupportedOperationException expected) { } + assertEquals("A", urlConnection.getHeaderFieldKey(0)); + assertEquals("c", urlConnection.getHeaderField(0)); + assertEquals("B", urlConnection.getHeaderFieldKey(1)); + assertEquals("d", urlConnection.getHeaderField(1)); + assertEquals("A", urlConnection.getHeaderFieldKey(2)); + assertEquals("e", urlConnection.getHeaderField(2)); + } + + public void testGetErrorStreamOnSuccessfulRequest() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertNull(connection.getErrorStream()); + } + + public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception { + server.enqueue(new MockResponse().setResponseCode(404).setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE)); } // Check that if we don't read to the end of a response, the next request on the @@ -541,7 +585,9 @@ public class URLConnectionTest extends junit.framework.TestCase { RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); - server.enqueue(new MockResponse().clearHeaders()); // for CONNECT + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) + .clearHeaders()); server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); server.play(); @@ -572,7 +618,9 @@ public class URLConnectionTest extends junit.framework.TestCase { TestSSLContext testSSLContext = TestSSLContext.create(); server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); - server.enqueue(new MockResponse().clearHeaders()); // for CONNECT + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) + .clearHeaders()); server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); server.play(); @@ -598,6 +646,39 @@ public class URLConnectionTest extends junit.framework.TestCase { assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); } + public void testProxyAuthenticateOnConnect() throws Exception { + Authenticator.setDefault(SIMPLE_AUTHENTICATOR); + TestSSLContext testSSLContext = TestSSLContext.create(); + server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); + server.enqueue(new MockResponse() + .setResponseCode(407) + .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); + server.enqueue(new MockResponse() + .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) + .clearHeaders()); + server.enqueue(new MockResponse().setBody("A")); + server.play(); + + URL url = new URL("https://android.com/foo"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( + server.toProxyAddress()); + connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); + connection.setHostnameVerifier(new RecordingHostnameVerifier()); + assertContent("A", connection); + + RecordedRequest connect1 = server.takeRequest(); + assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine()); + assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*"); + + RecordedRequest connect2 = server.takeRequest(); + assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine()); + assertContains(connect2.getHeaders(), "Proxy-Authorization: Basic " + BASE_64_CREDENTIALS); + + RecordedRequest get = server.takeRequest(); + assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); + assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*"); + } + public void testDisconnectedConnection() throws IOException { server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); server.play(); @@ -613,6 +694,22 @@ public class URLConnectionTest extends junit.framework.TestCase { } } + public void testDisconnectBeforeConnect() throws IOException { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.disconnect(); + + assertContent("A", connection); + assertEquals(200, connection.getResponseCode()); + } + + public void testDefaultRequestProperty() throws Exception { + URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A"); + assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")); + } + /** * Reads {@code count} characters from the stream. If the stream is * exhausted before {@code count} characters can be read, the remaining @@ -863,6 +960,104 @@ public class URLConnectionTest extends junit.framework.TestCase { assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); } + public void testSetValidRequestMethod() throws Exception { + server.play(); + assertValidRequestMethod("GET"); + assertValidRequestMethod("DELETE"); + assertValidRequestMethod("HEAD"); + assertValidRequestMethod("OPTIONS"); + assertValidRequestMethod("POST"); + assertValidRequestMethod("PUT"); + assertValidRequestMethod("TRACE"); + } + + private void assertValidRequestMethod(String requestMethod) throws Exception { + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.setRequestMethod(requestMethod); + assertEquals(requestMethod, connection.getRequestMethod()); + } + + public void testSetInvalidRequestMethodLowercase() throws Exception { + server.play(); + assertInvalidRequestMethod("get"); + } + + public void testSetInvalidRequestMethodConnect() throws Exception { + server.play(); + assertInvalidRequestMethod("CONNECT"); + } + + private void assertInvalidRequestMethod(String requestMethod) throws Exception { + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + try { + connection.setRequestMethod(requestMethod); + fail(); + } catch (ProtocolException expected) { + } + } + + public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception { + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + try { + connection.setFixedLengthStreamingMode(-2); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testCanSetNegativeChunkedStreamingMode() throws Exception { + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.setChunkedStreamingMode(-2); + } + + public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + try { + connection.setFixedLengthStreamingMode(1); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + try { + connection.setChunkedStreamingMode(1); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.setChunkedStreamingMode(1); + try { + connection.setFixedLengthStreamingMode(1); + fail(); + } catch (IllegalStateException expected) { + } + } + + public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.setFixedLengthStreamingMode(1); + try { + connection.setChunkedStreamingMode(1); + fail(); + } catch (IllegalStateException expected) { + } + } + public void testSecureFixedLengthStreaming() throws Exception { testSecureStreamingPost(StreamingMode.FIXED_LENGTH); } @@ -939,8 +1134,7 @@ public class URLConnectionTest extends junit.framework.TestCase { for (int i = 0; i < 3; i++) { request = server.takeRequest(); assertEquals("POST / HTTP/1.1", request.getRequestLine()); - assertContains(request.getHeaders(), "Authorization: Basic " - + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") + assertContains(request.getHeaders(), "Authorization: Basic " + BASE_64_CREDENTIALS); assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); } } @@ -970,8 +1164,7 @@ public class URLConnectionTest extends junit.framework.TestCase { for (int i = 0; i < 3; i++) { request = server.takeRequest(); assertEquals("GET / HTTP/1.1", request.getRequestLine()); - assertContains(request.getHeaders(), "Authorization: Basic " - + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") + assertContains(request.getHeaders(), "Authorization: Basic " + BASE_64_CREDENTIALS); } } @@ -1089,6 +1282,66 @@ public class URLConnectionTest extends junit.framework.TestCase { server2.shutdown(); } + public void testResponse300MultipleChoiceWithPost() throws Exception { + // Chrome doesn't follow the redirect, but Firefox and the RI both do + testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE); + } + + public void testResponse301MovedPermanentlyWithPost() throws Exception { + testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM); + } + + public void testResponse302MovedTemporarilyWithPost() throws Exception { + testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP); + } + + public void testResponse303SeeOtherWithPost() throws Exception { + testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER); + } + + private void testResponseRedirectedWithPost(int redirectCode) throws Exception { + server.enqueue(new MockResponse() + .setResponseCode(redirectCode) + .addHeader("Location: /page2") + .setBody("This page has moved!")); + server.enqueue(new MockResponse().setBody("Page 2")); + server.play(); + + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/page1").openConnection(); + connection.setDoOutput(true); + byte[] requestBody = { 'A', 'B', 'C', 'D' }; + OutputStream outputStream = connection.getOutputStream(); + outputStream.write(requestBody); + outputStream.close(); + assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + assertTrue(connection.getDoOutput()); + + RecordedRequest page1 = server.takeRequest(); + assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine()); + assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); + + RecordedRequest page2 = server.takeRequest(); + assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); + } + + public void testResponse305UseProxy() throws Exception { + server.play(); + server.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_USE_PROXY) + .addHeader("Location: " + server.getUrl("/")) + .setBody("This page has moved!")); + server.enqueue(new MockResponse().setBody("Proxy Response")); + + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/foo").openConnection(); + // Fails on the RI, which gets "Proxy Response" + assertEquals("This page has moved!", + readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + + RecordedRequest page1 = server.takeRequest(); + assertEquals("GET /foo HTTP/1.1", page1.getRequestLine()); + assertEquals(1, server.getRequestCount()); + } + public void testHttpsWithCustomTrustManager() throws Exception { RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); RecordingTrustManager trustManager = new RecordingTrustManager(); @@ -1494,6 +1747,115 @@ public class URLConnectionTest extends junit.framework.TestCase { assertEquals(-1, in.read()); // throws IOException in Gingerbread } + public void testGetContent() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + InputStream in = (InputStream) connection.getContent(); + assertEquals("A", readAscii(in, Integer.MAX_VALUE)); + } + + public void testGetContentOfType() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + try { + connection.getContent(null); + fail(); + } catch (NullPointerException expected) { + } + try { + connection.getContent(new Class[] { null }); + fail(); + } catch (NullPointerException expected) { + } + assertNull(connection.getContent(new Class[] { getClass() })); + connection.disconnect(); + } + + public void testGetOutputStreamOnGetFails() throws Exception { + server.enqueue(new MockResponse()); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + try { + connection.getOutputStream(); + fail(); + } catch (ProtocolException expected) { + } + } + + public void testGetOutputAfterGetInputStreamFails() throws Exception { + server.enqueue(new MockResponse()); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.setDoOutput(true); + try { + connection.getInputStream(); + connection.getOutputStream(); + fail(); + } catch (ProtocolException expected) { + } + } + + public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception { + server.enqueue(new MockResponse()); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.connect(); + try { + connection.setDoOutput(true); + fail(); + } catch (IllegalStateException expected) { + } + try { + connection.setDoInput(true); + fail(); + } catch (IllegalStateException expected) { + } + connection.disconnect(); + } + + public void testClientSendsContentLength() throws Exception { + server.enqueue(new MockResponse().setBody("A")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.setDoOutput(true); + OutputStream out = connection.getOutputStream(); + out.write(new byte[] { 'A', 'B', 'C' }); + out.close(); + assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + RecordedRequest request = server.takeRequest(); + assertContains(request.getHeaders(), "Content-Length: 3"); + } + + public void testGetContentLengthConnects() throws Exception { + server.enqueue(new MockResponse().setBody("ABC")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertEquals(3, connection.getContentLength()); + connection.disconnect(); + } + + public void testGetContentTypeConnects() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Content-Type: text/plain") + .setBody("ABC")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertEquals("text/plain", connection.getContentType()); + connection.disconnect(); + } + + public void testGetContentEncodingConnects() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Content-Encoding: identity") + .setBody("ABC")); + server.play(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + assertEquals("identity", connection.getContentEncoding()); + connection.disconnect(); + } + /** * Returns a gzipped copy of {@code bytes}. */ diff --git a/luni/src/test/java/libcore/java/net/URLTest.java b/luni/src/test/java/libcore/java/net/URLTest.java index 1765630..f00cb25 100644 --- a/luni/src/test/java/libcore/java/net/URLTest.java +++ b/luni/src/test/java/libcore/java/net/URLTest.java @@ -18,10 +18,28 @@ package libcore.java.net; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.MalformedURLException; import java.net.URL; import junit.framework.TestCase; +import libcore.java.util.SerializableTester; public class URLTest extends TestCase { + + public void testUrlParts() throws Exception { + URL url = new URL("http://username:password@host:8080/directory/file?query#ref"); + assertEquals("http", url.getProtocol()); + assertEquals("username:password@host:8080", url.getAuthority()); + assertEquals("/directory/file", url.getPath()); + assertEquals("ref", url.getRef()); + + assertEquals("username:password", url.getUserInfo()); + assertEquals("host", url.getHost()); + assertEquals(8080, url.getPort()); + assertEquals("/directory/file?query", url.getFile()); + assertEquals("query", url.getQuery()); + + assertEquals(80, url.getDefaultPort()); + } // http://code.google.com/p/android/issues/detail?id=12724 public void testExplicitPort() throws Exception { URL url = new URL("http://www.google.com:80/example?language[id]=2"); @@ -78,4 +96,44 @@ public class URLTest extends TestCase { assertFalse(new URL("file", null, -1, "/a/").equals(new URL("file:/a/"))); assertFalse(new URL("http", null, 80, "/a/").equals(new URL("http:/a/"))); } + + public void testUrlSerialization() throws Exception { + String s = "aced00057372000c6a6176612e6e65742e55524c962537361afce472030006490004706f72744c0" + + "009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c65710" + + "07e00014c0004686f737471007e00014c000870726f746f636f6c71007e00014c000372656671007" + + "e00017870ffffffff74000e757365723a7061737340686f73747400102f706174682f66696c653f7" + + "175657279740004686f7374740004687474707400046861736878"; + URL url = new URL("http://user:pass@host/path/file?query#hash"); + new SerializableTester<URL>(url, s).test(); + } + + /** + * The serialized form of a URL includes its hash code. But the hash code + * is not documented. Check that we don't return a deserialized hash code + * from a deserialized value. + */ + public void testUrlSerializationWithHashCode() throws Exception { + String s = "aced00057372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436" + + "f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696" + + "e673b4c000466696c6571007e00014c0004686f737471007e00014c000870726f746f636f6c71007" + + "e00014c000372656671007e00017870cdf0efacffffffff74000e757365723a7061737340686f737" + + "47400102f706174682f66696c653f7175657279740004686f7374740004687474707400046861736" + + "878"; + final URL url = new URL("http://user:pass@host/path/file?query#hash"); + new SerializableTester<URL>(url, s) { + @Override protected void verify(URL deserialized) { + assertEquals(url.hashCode(), deserialized.hashCode()); + } + }.test(); + } + + public void testOnlySupportedProtocols() { + try { + new URL("abcd://host"); + fail(); + } catch (MalformedURLException expected) { + } + } + + // TODO: test resolve relative URL } diff --git a/luni/src/test/java/libcore/java/nio/BufferTest.java b/luni/src/test/java/libcore/java/nio/BufferTest.java index 6f3f948..255db36 100644 --- a/luni/src/test/java/libcore/java/nio/BufferTest.java +++ b/luni/src/test/java/libcore/java/nio/BufferTest.java @@ -638,4 +638,20 @@ public class BufferTest extends TestCase { } catch (BufferOverflowException expected) { } } + + public void testCharBufferSubSequence() throws Exception { + ByteBuffer b = ByteBuffer.allocateDirect(10).order(ByteOrder.nativeOrder()); + b.putChar('H'); + b.putChar('e'); + b.putChar('l'); + b.putChar('l'); + b.putChar('o'); + b.flip(); + + assertEquals("Hello", b.asCharBuffer().toString()); + + CharBuffer cb = b.asCharBuffer(); + CharSequence cs = cb.subSequence(0, cb.length()); + assertEquals("Hello", cs.toString()); + } } diff --git a/luni/src/test/java/libcore/java/nio/SelectorTest.java b/luni/src/test/java/libcore/java/nio/SelectorTest.java deleted file mode 100644 index 9bc1b04..0000000 --- a/luni/src/test/java/libcore/java/nio/SelectorTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package libcore.java.nio; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.nio.channels.NoConnectionPendingException; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import junit.framework.TestCase; -import tests.support.Support_PortManager; - -public class SelectorTest extends TestCase { - private static final int WAIT_TIME = 100; - private static final int PORT = Support_PortManager.getNextPort(); - private static final InetSocketAddress LOCAL_ADDRESS = new InetSocketAddress( - "127.0.0.1", PORT); - - Selector selector; - ServerSocketChannel ssc; - - protected void setUp() throws Exception { - super.setUp(); - ssc = ServerSocketChannel.open(); - ssc.configureBlocking(false); - ServerSocket ss = ssc.socket(); - InetSocketAddress address = new InetSocketAddress(PORT); - ss.bind(address); - selector = Selector.open(); - } - - protected void tearDown() throws Exception { - try { - ssc.close(); - } catch (Exception e) { - // do nothing - } - try { - selector.close(); - } catch (Exception e) { - // do nothing - } - super.tearDown(); - } - - // http://code.google.com/p/android/issues/detail?id=4237 - public void test_connectFinish_fails() throws Exception { - final SocketChannel channel = SocketChannel.open(); - channel.configureBlocking(false); - channel.register(selector, SelectionKey.OP_CONNECT); - final Boolean[] fail = new Boolean[1]; - new Thread() { - public void run() { - try { - while (selector.isOpen()) { - if (selector.select() != 0) { - for (SelectionKey key : selector.selectedKeys()) { - if (key.isValid() && key.isConnectable()) { - try { - channel.finishConnect(); - synchronized (fail) { - fail[0] = Boolean.FALSE; - fail.notify(); - } - } - catch (NoConnectionPendingException _) { - synchronized (fail) { - fail[0] = Boolean.TRUE; - fail.notify(); - } - } - } - } - } - } - } catch (Exception _) {} - } - }.start(); - Thread.sleep(WAIT_TIME); - channel.connect(LOCAL_ADDRESS); - long time = System.currentTimeMillis(); - synchronized (fail) { - while (System.currentTimeMillis() - time < WAIT_TIME || fail[0] == null) { - fail.wait(WAIT_TIME); - } - } - if (fail[0] == null) { - fail("test does not work"); - } else if (fail[0].booleanValue()) { - fail(); - } - } - - // http://code.google.com/p/android/issues/detail?id=15388 - public void testInterrupted() throws IOException { - Thread.currentThread().interrupt(); - int count = selector.select(); - assertEquals(0, count); - } - - public void testManyWakeupCallsTriggerOnlyOneWakeup() throws Exception { - selector.wakeup(); - selector.wakeup(); - selector.wakeup(); - selector.select(); - - // create a latch that will reach 0 when select returns - final CountDownLatch selectReturned = new CountDownLatch(1); - Thread thread = new Thread(new Runnable() { - @Override public void run() { - try { - selector.select(); - selectReturned.countDown(); - } catch (IOException ignored) { - } - } - }); - thread.start(); - - // select doesn't ever return, so await() times out and returns false - assertFalse(selectReturned.await(500, TimeUnit.MILLISECONDS)); - } -} - diff --git a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java index d982785..13757d2 100644 --- a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java @@ -42,4 +42,17 @@ public class DatagramChannelTest extends junit.framework.TestCase { } catch (IllegalArgumentException expected) { } } + + // http://code.google.com/p/android/issues/detail?id=16579 + public void testNonBlockingRecv() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + try { + dc.configureBlocking(false); + dc.socket().bind(null); + // Should return immediately, since we're non-blocking. + assertNull(dc.receive(ByteBuffer.allocate(2048))); + } finally { + dc.close(); + } + } } diff --git a/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java index e221675..415ae0a 100644 --- a/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/FileChannelTest.java @@ -92,6 +92,22 @@ public class FileChannelTest extends junit.framework.TestCase { assertEquals(8, fc.write(buffers)); fc.close(); assertEquals(8, tmp.length()); - assertEquals("abcdABCD", new String(IoUtils.readFileAsByteArray(tmp.getPath()), "US-ASCII")); + assertEquals("abcdABCD", new String(IoUtils.readFileAsString(tmp.getPath()))); + } + + public void test_append() throws Exception { + File tmp = File.createTempFile("FileChannelTest", "tmp"); + FileOutputStream fos = new FileOutputStream(tmp, true); + FileChannel fc = fos.getChannel(); + + fc.write(ByteBuffer.wrap("hello".getBytes("US-ASCII"))); + fc.position(0); + // The RI reports whatever position you set... + assertEquals(0, fc.position()); + // ...but writes to the end of the file. + fc.write(ByteBuffer.wrap(" world".getBytes("US-ASCII"))); + fos.close(); + + assertEquals("hello world", new String(IoUtils.readFileAsString(tmp.getPath()))); } } diff --git a/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java index 9388888..7e2310b 100644 --- a/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/OldFileChannelTest.java @@ -156,11 +156,13 @@ public final class OldFileChannelTest extends TestCase { writeOnlyFileChannel.write(writeBuffer); writeOnlyFileChannel.force(false); + fis.close(); readBuffer = new byte[CONTENT_AS_BYTES_LENGTH]; fis = new FileInputStream(fileOfWriteOnlyFileChannel); fis.read(readBuffer); assertTrue(Arrays.equals(CONTENT_AS_BYTES, readBuffer)); + fis.close(); } diff --git a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java index 96cec03..aa958ad 100644 --- a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java @@ -16,10 +16,98 @@ package libcore.java.nio.channels; import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.nio.channels.NoConnectionPendingException; +import java.nio.channels.SelectionKey; import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import tests.net.StuckServer; public class SelectorTest extends TestCase { + public void testNonBlockingConnect_immediate() throws Exception { + // Test the case where we [probably] connect immediately. + Selector selector = Selector.open(); + ServerSocketChannel ssc = ServerSocketChannel.open(); + try { + ssc.configureBlocking(false); + ssc.socket().bind(null); + + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + sc.connect(ssc.socket().getLocalSocketAddress()); + SelectionKey key = sc.register(selector, SelectionKey.OP_CONNECT); + assertEquals(1, selector.select()); + assertEquals(SelectionKey.OP_CONNECT, key.readyOps()); + sc.finishConnect(); + } finally { + selector.close(); + ssc.close(); + } + } + + public void testNonBlockingConnect_slow() throws Exception { + // Test the case where we have to wait for the connection. + Selector selector = Selector.open(); + StuckServer ss = new StuckServer(); + try { + SocketChannel sc = SocketChannel.open(); + sc.configureBlocking(false); + ss.unblockAfterMs(2000); + sc.connect(ss.getLocalSocketAddress()); + SelectionKey key = sc.register(selector, SelectionKey.OP_CONNECT); + assertEquals(1, selector.select()); + assertEquals(SelectionKey.OP_CONNECT, key.readyOps()); + sc.finishConnect(); + } finally { + selector.close(); + ss.close(); + } + } + + // http://code.google.com/p/android/issues/detail?id=15388 + public void testInterrupted() throws IOException { + Selector selector = Selector.open(); + try { + Thread.currentThread().interrupt(); + int count = selector.select(); + assertEquals(0, count); + } finally { + selector.close(); + } + } + + public void testManyWakeupCallsTriggerOnlyOneWakeup() throws Exception { + final Selector selector = Selector.open(); + try { + selector.wakeup(); + selector.wakeup(); + selector.wakeup(); + selector.select(); + + // create a latch that will reach 0 when select returns + final CountDownLatch selectReturned = new CountDownLatch(1); + Thread thread = new Thread(new Runnable() { + @Override public void run() { + try { + selector.select(); + selectReturned.countDown(); + } catch (IOException ignored) { + } + } + }); + thread.start(); + + // select doesn't ever return, so await() times out and returns false + assertFalse(selectReturned.await(500, TimeUnit.MILLISECONDS)); + } finally { + selector.close(); + } + } /** * We previously leaked a file descriptor for each selector instance created. diff --git a/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java new file mode 100644 index 0000000..e66096c --- /dev/null +++ b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java @@ -0,0 +1,34 @@ +/* + * 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.nio.channels; + +import java.nio.channels.ServerSocketChannel; + +public class ServerSocketChannelTest extends junit.framework.TestCase { + // http://code.google.com/p/android/issues/detail?id=16579 + public void testNonBlockingAccept() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + try { + ssc.configureBlocking(false); + ssc.socket().bind(null); + // Should return immediately, since we're non-blocking. + assertNull(ssc.accept()); + } finally { + ssc.close(); + } + } +} diff --git a/luni/src/test/java/libcore/java/util/EventObjectTest.java b/luni/src/test/java/libcore/java/util/EventObjectTest.java new file mode 100644 index 0000000..a80e8c7 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/EventObjectTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util; + +import java.util.EventObject; +import junit.framework.TestCase; + +public final class EventObjectTest extends TestCase { + + public void testConstructor() { + try { + new EventObject(null); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testGetSource() { + Object source = new Object(); + assertSame(source, new EventObject(source).getSource()); + } + + public void testToString() { + assertEquals("java.util.EventObject[source=x]", new EventObject("x").toString()); + } + + public void testSerializationNullsOutSource() { + String s = "aced0005737200156a6176612e7574696c2e4576656e744f626a6563744" + + "c8d094e186d7da80200007870"; + Object source = new Object(); + EventObject eventObject = new EventObject(source); + new SerializableTester<EventObject>(eventObject, s) { + @Override protected boolean equals(EventObject a, EventObject b) { + return a.getSource() == null + || b.getSource() == null + || a.getSource() == b.getSource(); + } + }.test(); + } +} diff --git a/luni/src/test/java/libcore/java/util/SerializableTester.java b/luni/src/test/java/libcore/java/util/SerializableTester.java index 20318d7..50b6435 100644 --- a/luni/src/test/java/libcore/java/util/SerializableTester.java +++ b/luni/src/test/java/libcore/java/util/SerializableTester.java @@ -78,20 +78,20 @@ public class SerializableTester<T> { } } - private byte[] serialize(Object object) throws IOException { + private static byte[] serialize(Object object) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); new ObjectOutputStream(out).writeObject(object); return out.toByteArray(); } - private Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException { + private static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); Object result = in.readObject(); assertEquals(-1, in.read()); return result; } - private String hexEncode(byte[] bytes) { + private static String hexEncode(byte[] bytes) { StringBuilder result = new StringBuilder(bytes.length * 2); for (byte b : bytes) { result.append(String.format("%02x", b)); @@ -99,12 +99,19 @@ public class SerializableTester<T> { return result.toString(); } - private byte[] hexDecode(String s) { + private static byte[] hexDecode(String s) { byte[] result = new byte[s.length() / 2]; for (int i = 0; i < result.length; i++) { result[i] = (byte) Integer.parseInt(s.substring(i*2, i*2 + 2), 16); } return result; } -} + public static String serializeHex(Object object) throws IOException { + return hexEncode(serialize(object)); + } + + public static Object deserializeHex(String hex) throws IOException, ClassNotFoundException { + return deserialize(hexDecode(hex)); + } +} diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java index dde2ec8..39ac19d 100644 --- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java +++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java @@ -132,6 +132,16 @@ public class TimeZoneTest extends junit.framework.TestCase { } } + // http://code.google.com/p/android/issues/detail?id=16608 + public void testHelsinkiOverflow() throws Exception { + long time = 2147483648000L;// Tue, 19 Jan 2038 03:14:08 GMT + TimeZone timeZone = TimeZone.getTimeZone("Europe/Helsinki"); + long offset = timeZone.getOffset(time); + // This might cause us trouble if Europe/Helsinki changes its rules. See the bug for + // details of the intent of this test. + assertEquals(7200000, offset); + } + // http://code.google.com/p/android/issues/detail?id=11918 public void testHasSameRules() throws Exception { TimeZone denver = TimeZone.getTimeZone ("America/Denver") ; diff --git a/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java b/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java index ca8c790..5981b0b 100644 --- a/luni/src/test/java/libcore/java/net/HttpResponseCacheTest.java +++ b/luni/src/test/java/libcore/net/http/HttpResponseCacheTest.java @@ -14,17 +14,22 @@ * limitations under the License. */ -package libcore.java.net; +package libcore.net.http; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.lang.reflect.InvocationHandler; import java.net.CacheRequest; import java.net.CacheResponse; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.ResponseCache; import java.net.SecureCacheResponse; @@ -40,12 +45,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.TimeZone; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -53,24 +59,35 @@ 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; +import tests.io.MockOs; public final class HttpResponseCacheTest extends TestCase { private MockWebServer server = new MockWebServer(); - private HttpResponseCache cache = new HttpResponseCache(); + private HttpResponseCache cache; + private final MockOs mockOs = new MockOs(); + private final CookieManager cookieManager = new CookieManager(); @Override protected void setUp() throws Exception { super.setUp(); + + String tmp = System.getProperty("java.io.tmpdir"); + File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); + cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); ResponseCache.setDefault(cache); + mockOs.install(); + CookieHandler.setDefault(cookieManager); } @Override protected void tearDown() throws Exception { - ResponseCache.setDefault(null); + mockOs.uninstall(); server.shutdown(); + ResponseCache.setDefault(null); + cache.getCache().delete(); + CookieHandler.setDefault(null); super.tearDown(); } @@ -153,11 +170,14 @@ public final class HttpResponseCacheTest extends TestCase { // 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()); + CacheResponse cached = cache.get(url.toURI(), "GET", + Collections.<String, List<String>>emptyMap()); + if (shouldPut) { + assertNotNull(Integer.toString(responseCode), cached); + cached.getBody().close(); + } else { + assertNull(Integer.toString(responseCode), cached); + } server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers } @@ -249,8 +269,8 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("spiders", readAscii(urlConnection, "spiders".length())); assertEquals(-1, in.read()); in.close(); - assertEquals(1, cache.getSuccessCount()); - assertEquals(0, cache.getAbortCount()); + assertEquals(1, cache.getWriteSuccessCount()); + assertEquals(0, cache.getWriteAbortCount()); urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached! in = urlConnection.getInputStream(); @@ -260,10 +280,11 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("Fantastic", urlConnection.getResponseMessage()); assertEquals(-1, in.read()); - assertEquals(1, cache.getMissCount()); + in.close(); + assertEquals(1, cache.getWriteSuccessCount()); + assertEquals(0, cache.getWriteAbortCount()); + assertEquals(2, cache.getRequestCount()); assertEquals(1, cache.getHitCount()); - assertEquals(1, cache.getSuccessCount()); - assertEquals(0, cache.getAbortCount()); } public void testSecureResponseCaching() throws IOException { @@ -290,7 +311,8 @@ public final class HttpResponseCacheTest extends TestCase { connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); assertEquals("ABC", readAscii(connection)); - assertEquals(1, cache.getMissCount()); + assertEquals(2, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); assertEquals(1, cache.getHitCount()); assertEquals(suite, connection.getCipherSuite()); @@ -318,7 +340,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("DEF", readAscii(connection)); } - public void testResponseCachingAndRedirects() throws IOException { + public void testResponseCachingAndRedirects() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) @@ -337,10 +359,38 @@ public final class HttpResponseCacheTest extends TestCase { connection = server.getUrl("/").openConnection(); // cached! assertEquals("ABC", readAscii(connection)); - assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2 + assertEquals(4, cache.getRequestCount()); // 2 requests + 2 redirects + assertEquals(2, cache.getNetworkCount()); assertEquals(2, cache.getHitCount()); } + public void testRedirectToCachedResult() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("ABC")); + server.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) + .addHeader("Location: /foo")); + server.enqueue(new MockResponse().setBody("DEF")); + server.play(); + + assertEquals("ABC", readAscii(server.getUrl("/foo").openConnection())); + RecordedRequest request1 = server.takeRequest(); + assertEquals("GET /foo HTTP/1.1", request1.getRequestLine()); + assertEquals(0, request1.getSequenceNumber()); + + assertEquals("ABC", readAscii(server.getUrl("/bar").openConnection())); + RecordedRequest request2 = server.takeRequest(); + assertEquals("GET /bar HTTP/1.1", request2.getRequestLine()); + assertEquals(1, request2.getSequenceNumber()); + + // an unrelated request should reuse the pooled connection + assertEquals("DEF", readAscii(server.getUrl("/baz").openConnection())); + RecordedRequest request3 = server.takeRequest(); + assertEquals("GET /baz HTTP/1.1", request3.getRequestLine()); + assertEquals(2, request3.getSequenceNumber()); + } + public void testSecureResponseCachingAndRedirects() throws IOException { TestSSLContext testSSLContext = TestSSLContext.create(); server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); @@ -364,7 +414,7 @@ public final class HttpResponseCacheTest extends TestCase { connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); assertEquals("ABC", readAscii(connection)); - assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2 + assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4 assertEquals(2, cache.getHitCount()); } @@ -423,14 +473,16 @@ public final class HttpResponseCacheTest extends TestCase { reader.readLine(); fail("This implementation silently ignored a truncated HTTP body."); } catch (IOException expected) { + } finally { + reader.close(); } - assertEquals(1, cache.getAbortCount()); - assertEquals(0, cache.getSuccessCount()); + assertEquals(1, cache.getWriteAbortCount()); + assertEquals(0, cache.getWriteSuccessCount()); URLConnection connection = server.getUrl("/").openConnection(); assertEquals("Request #2", readAscii(connection)); - assertEquals(1, cache.getAbortCount()); - assertEquals(1, cache.getSuccessCount()); + assertEquals(1, cache.getWriteAbortCount()); + assertEquals(1, cache.getWriteSuccessCount()); } public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { @@ -462,12 +514,12 @@ public final class HttpResponseCacheTest extends TestCase { } catch (IOException expected) { } - assertEquals(1, cache.getAbortCount()); - assertEquals(0, cache.getSuccessCount()); + assertEquals(1, cache.getWriteAbortCount()); + assertEquals(0, cache.getWriteSuccessCount()); connection = server.getUrl("/").openConnection(); assertEquals("Request #2", readAscii(connection)); - assertEquals(1, cache.getAbortCount()); - assertEquals(1, cache.getSuccessCount()); + assertEquals(1, cache.getWriteAbortCount()); + assertEquals(1, cache.getWriteSuccessCount()); } public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception { @@ -881,6 +933,20 @@ public final class HttpResponseCacheTest extends TestCase { connection.getHeaderField("Warning")); } + public void testRequestMaxStaleNotHonoredWithMustRevalidate() throws IOException { + server.enqueue(new MockResponse().setBody("A") + .addHeader("Cache-Control: max-age=120, must-revalidate") + .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES))); + server.enqueue(new MockResponse().setBody("B")); + + server.play(); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + + URLConnection connection = server.getUrl("/").openConnection(); + connection.addRequestProperty("Cache-Control", "max-stale=180"); + assertEquals("B", readAscii(connection)); + } + public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException { // (no responses enqueued) server.play(); @@ -999,6 +1065,19 @@ public final class HttpResponseCacheTest extends TestCase { return server.takeRequest(); } + public void testSetIfModifiedSince() throws Exception { + Date since = new Date(); + server.enqueue(new MockResponse().setBody("A")); + server.play(); + + URL url = server.getUrl("/"); + URLConnection connection = url.openConnection(); + connection.setIfModifiedSince(since.getTime()); + assertEquals("A", readAscii(connection)); + RecordedRequest request = server.takeRequest(); + assertTrue(request.getHeaders().contains("If-Modified-Since: " + formatDate(since))); + } + public void testClientSuppliedConditionWithoutCachedResult() throws Exception { server.enqueue(new MockResponse() .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); @@ -1055,11 +1134,154 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(url.openConnection())); } - public void testCacheControlMustRevalidate() throws Exception { - fail("Cache-Control: must-revalidate"); // TODO + public void testContentLocationDoesNotPopulateCache() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Content-Location: /bar") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/foo").openConnection())); + assertEquals("B", readAscii(server.getUrl("/bar").openConnection())); + } + + public void testUseCachesFalseDoesNotWriteToCache() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("A").setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URLConnection connection = server.getUrl("/").openConnection(); + connection.setUseCaches(false); + assertEquals("A", readAscii(connection)); + assertEquals("B", readAscii(server.getUrl("/").openConnection())); } - public void testVaryResponsesAreNotSupported() throws Exception { + public void testUseCachesFalseDoesNotReadFromCache() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("A").setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + URLConnection connection = server.getUrl("/").openConnection(); + connection.setUseCaches(false); + assertEquals("B", readAscii(connection)); + } + + public void testDefaultUseCachesSetsInitialValueOnly() throws Exception { + URL url = new URL("http://localhost/"); + URLConnection c1 = url.openConnection(); + URLConnection c2 = url.openConnection(); + assertTrue(c1.getDefaultUseCaches()); + c1.setDefaultUseCaches(false); + try { + assertTrue(c1.getUseCaches()); + assertTrue(c2.getUseCaches()); + URLConnection c3 = url.openConnection(); + assertFalse(c3.getUseCaches()); + } finally { + c1.setDefaultUseCaches(true); + } + } + + public void testConnectionIsReturnedToPoolAfterConditionalSuccess() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/a").openConnection())); + assertEquals("A", readAscii(server.getUrl("/a").openConnection())); + assertEquals("B", readAscii(server.getUrl("/b").openConnection())); + + assertEquals(0, server.takeRequest().getSequenceNumber()); + assertEquals(1, server.takeRequest().getSequenceNumber()); + assertEquals(2, server.takeRequest().getSequenceNumber()); + } + + public void testStatisticsConditionalCacheMiss() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.enqueue(new MockResponse().setBody("C")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals(1, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(0, cache.getHitCount()); + assertEquals("B", readAscii(server.getUrl("/").openConnection())); + assertEquals("C", readAscii(server.getUrl("/").openConnection())); + assertEquals(3, cache.getRequestCount()); + assertEquals(3, cache.getNetworkCount()); + assertEquals(0, cache.getHitCount()); + } + + public void testStatisticsConditionalCacheHit() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals(1, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(0, cache.getHitCount()); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals(3, cache.getRequestCount()); + assertEquals(3, cache.getNetworkCount()); + assertEquals(2, cache.getHitCount()); + } + + public void testStatisticsFullCacheHit() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("A")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals(1, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(0, cache.getHitCount()); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals(3, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(2, cache.getHitCount()); + } + + public void testVaryMatchesChangedRequestHeaderField() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + HttpURLConnection frConnection = (HttpURLConnection) url.openConnection(); + frConnection.addRequestProperty("Accept-Language", "fr-CA"); + assertEquals("A", readAscii(frConnection)); + + HttpURLConnection enConnection = (HttpURLConnection) url.openConnection(); + enConnection.addRequestProperty("Accept-Language", "en-US"); + assertEquals("B", readAscii(enConnection)); + } + + public void testVaryMatchesUnchangedRequestHeaderField() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") @@ -1071,22 +1293,324 @@ public final class HttpResponseCacheTest extends TestCase { URLConnection connection1 = url.openConnection(); connection1.addRequestProperty("Accept-Language", "fr-CA"); assertEquals("A", readAscii(connection1)); + URLConnection connection2 = url.openConnection(); + connection2.addRequestProperty("Accept-Language", "fr-CA"); + assertEquals("A", readAscii(connection2)); + } + + public void testVaryMatchesAbsentRequestHeaderField() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Foo") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + } + + public void testVaryMatchesAddedRequestHeaderField() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Foo") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + URLConnection fooConnection = server.getUrl("/").openConnection(); + fooConnection.addRequestProperty("Foo", "bar"); + assertEquals("B", readAscii(fooConnection)); + } + + public void testVaryMatchesRemovedRequestHeaderField() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Foo") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URLConnection fooConnection = server.getUrl("/").openConnection(); + fooConnection.addRequestProperty("Foo", "bar"); + assertEquals("A", readAscii(fooConnection)); + assertEquals("B", readAscii(server.getUrl("/").openConnection())); + } + + public void testVaryFieldsAreCaseInsensitive() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: ACCEPT-LANGUAGE") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + URLConnection connection1 = url.openConnection(); + connection1.addRequestProperty("Accept-Language", "fr-CA"); + assertEquals("A", readAscii(connection1)); + URLConnection connection2 = url.openConnection(); + connection2.addRequestProperty("accept-language", "fr-CA"); + assertEquals("A", readAscii(connection2)); + } + public void testVaryMultipleFieldsWithMatch() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language, Accept-Charset") + .addHeader("Vary: Accept-Encoding") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + URLConnection connection1 = url.openConnection(); + connection1.addRequestProperty("Accept-Language", "fr-CA"); + connection1.addRequestProperty("Accept-Charset", "UTF-8"); + connection1.addRequestProperty("Accept-Encoding", "identity"); + assertEquals("A", readAscii(connection1)); URLConnection connection2 = url.openConnection(); connection2.addRequestProperty("Accept-Language", "fr-CA"); + connection2.addRequestProperty("Accept-Charset", "UTF-8"); + connection2.addRequestProperty("Accept-Encoding", "identity"); + assertEquals("A", readAscii(connection2)); + } + + public void testVaryMultipleFieldsWithNoMatch() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language, Accept-Charset") + .addHeader("Vary: Accept-Encoding") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + URLConnection frConnection = url.openConnection(); + frConnection.addRequestProperty("Accept-Language", "fr-CA"); + frConnection.addRequestProperty("Accept-Charset", "UTF-8"); + frConnection.addRequestProperty("Accept-Encoding", "identity"); + assertEquals("A", readAscii(frConnection)); + URLConnection enConnection = url.openConnection(); + enConnection.addRequestProperty("Accept-Language", "en-CA"); + enConnection.addRequestProperty("Accept-Charset", "UTF-8"); + enConnection.addRequestProperty("Accept-Encoding", "identity"); + assertEquals("B", readAscii(enConnection)); + } + + public void testVaryMultipleFieldValuesWithMatch() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + URLConnection connection1 = url.openConnection(); + connection1.addRequestProperty("Accept-Language", "fr-CA, fr-FR"); + connection1.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection1)); + + URLConnection connection2 = url.openConnection(); + connection2.addRequestProperty("Accept-Language", "fr-CA, fr-FR"); + connection2.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection2)); + } + + public void testVaryMultipleFieldValuesWithNoMatch() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + URLConnection connection1 = url.openConnection(); + connection1.addRequestProperty("Accept-Language", "fr-CA, fr-FR"); + connection1.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection1)); + + URLConnection connection2 = url.openConnection(); + connection2.addRequestProperty("Accept-Language", "fr-CA"); + connection2.addRequestProperty("Accept-Language", "en-US"); assertEquals("B", readAscii(connection2)); } - public void testContentLocationDoesNotPopulateCache() throws Exception { + public void testVaryAsterisk() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") - .addHeader("Content-Location: /bar") + .addHeader("Vary: *") .setBody("A")); server.enqueue(new MockResponse().setBody("B")); server.play(); - assertEquals("A", readAscii(server.getUrl("/foo").openConnection())); - assertEquals("B", readAscii(server.getUrl("/bar").openConnection())); + assertEquals("A", readAscii(server.getUrl("/").openConnection())); + assertEquals("B", readAscii(server.getUrl("/").openConnection())); + } + + public void testVaryAndHttps() throws Exception { + TestSSLContext testSSLContext = TestSSLContext.create(); + server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + HttpsURLConnection connection1 = (HttpsURLConnection) url.openConnection(); + connection1.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); + connection1.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection1)); + + HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection(); + connection2.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); + connection2.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection2)); + } + + public void testDiskWriteFailureCacheDegradation() throws Exception { + Deque<InvocationHandler> writeHandlers = mockOs.getHandlers("write"); + int i = 0; + boolean hasMoreScenarios = true; + while (hasMoreScenarios) { + mockOs.enqueueNormal("write", i++); + mockOs.enqueueFault("write"); + exercisePossiblyFaultyCache(false); + hasMoreScenarios = writeHandlers.isEmpty(); + writeHandlers.clear(); + } + System.out.println("Exercising the cache performs " + (i - 1) + " writes."); + } + + public void testDiskReadFailureCacheDegradation() throws Exception { + Deque<InvocationHandler> readHandlers = mockOs.getHandlers("read"); + int i = 0; + boolean hasMoreScenarios = true; + while (hasMoreScenarios) { + mockOs.enqueueNormal("read", i++); + mockOs.enqueueFault("read"); + exercisePossiblyFaultyCache(true); + hasMoreScenarios = readHandlers.isEmpty(); + readHandlers.clear(); + } + System.out.println("Exercising the cache performs " + (i - 1) + " reads."); + } + + public void testCachePlusCookies() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Set-Cookie: a=FIRST; domain=.local;") + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse() + .addHeader("Set-Cookie: a=SECOND; domain=.local;") + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + URL url = server.getUrl("/"); + assertEquals("A", readAscii(url.openConnection())); + assertCookies(url, "a=FIRST"); + assertEquals("A", readAscii(url.openConnection())); + assertCookies(url, "a=SECOND"); + } + + public void testGetHeadersReturnsNetworkEndToEndHeaders() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Allow: GET, HEAD") + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse() + .addHeader("Allow: GET, HEAD, PUT") + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + URLConnection connection1 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection1)); + assertEquals("GET, HEAD", connection1.getHeaderField("Allow")); + + URLConnection connection2 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection2)); + assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow")); + } + + public void testGetHeadersReturnsCachedHopByHopHeaders() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Transfer-Encoding: identity") + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse() + .addHeader("Transfer-Encoding: none") + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + URLConnection connection1 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection1)); + assertEquals("identity", connection1.getHeaderField("Transfer-Encoding")); + + URLConnection connection2 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection2)); + assertEquals("identity", connection2.getHeaderField("Transfer-Encoding")); + } + + public void testGetHeadersDeletesCached100LevelWarnings() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Warning: 199 test danger") + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + URLConnection connection1 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection1)); + assertEquals("199 test danger", connection1.getHeaderField("Warning")); + + URLConnection connection2 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection2)); + assertEquals(null, connection2.getHeaderField("Warning")); + } + + public void testGetHeadersRetainsCached200LevelWarnings() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Warning: 299 test danger") + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Cache-Control: max-age=0") + .setBody("A")); + server.enqueue(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + URLConnection connection1 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection1)); + assertEquals("299 test danger", connection1.getHeaderField("Warning")); + + URLConnection connection2 = server.getUrl("/").openConnection(); + assertEquals("A", readAscii(connection2)); + assertEquals("299 test danger", connection2.getHeaderField("Warning")); + } + + public void assertCookies(URL url, String... expectedCookies) throws Exception { + List<String> actualCookies = new ArrayList<String>(); + for (HttpCookie cookie : cookieManager.getCookieStore().get(url.toURI())) { + actualCookies.add(cookie.toString()); + } + assertEquals(Arrays.asList(expectedCookies), actualCookies); + } + + public void testCachePlusRange() throws Exception { + assertNotCached(new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_PARTIAL) + .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) + .addHeader("Content-Range: bytes 100-100/200") + .addHeader("Cache-Control: max-age=60")); } /** @@ -1095,7 +1619,10 @@ public final class HttpResponseCacheTest extends TestCase { * future. */ private String formatDate(long delta, TimeUnit timeUnit) { - Date date = new Date(System.currentTimeMillis() + timeUnit.toMillis(delta)); + return formatDate(new Date(System.currentTimeMillis() + timeUnit.toMillis(delta))); + } + + private String formatDate(Date date) { DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); rfc1123.setTimeZone(TimeZone.getTimeZone("UTC")); return rfc1123.format(date); @@ -1121,6 +1648,31 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(url.openConnection())); } + private void exercisePossiblyFaultyCache(boolean permitReadBodyFailures) throws Exception { + server.shutdown(); + server = new MockWebServer(); + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/" + UUID.randomUUID()); + assertEquals("A", readAscii(url.openConnection())); + + URLConnection connection = url.openConnection(); + InputStream in = connection.getInputStream(); + try { + int bodyChar = in.read(); + assertTrue(bodyChar == 'A' || bodyChar == 'B'); + assertEquals(-1, in.read()); + } catch (IOException e) { + if (!permitReadBodyFailures) { + throw e; + } + } + } + /** * @return the request with the conditional get headers. */ @@ -1261,16 +1813,14 @@ public final class HttpResponseCacheTest extends TestCase { return bytesOut.toByteArray(); } - private static class InsecureResponseCache extends ResponseCache { - private final HttpResponseCache delegate = new HttpResponseCache(); - + private class InsecureResponseCache extends ResponseCache { @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { - return delegate.put(uri, connection); + return cache.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); + final CacheResponse response = cache.get(uri, requestMethod, requestHeaders); if (response instanceof SecureCacheResponse) { return new CacheResponse() { @Override public InputStream getBody() throws IOException { diff --git a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java index 0f38a5d..ae684c3 100644 --- a/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java +++ b/luni/src/test/java/libcore/net/http/ParsedHeadersTest.java @@ -57,16 +57,15 @@ public final class ParsedHeadersTest extends TestCase { public void testQuotedFieldName() { RawHeaders headers = new RawHeaders(); - headers.add("Cache-Control", "private=\"Set-Cookie\""); + 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 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); } @@ -74,7 +73,6 @@ public final class ParsedHeadersTest extends TestCase { 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); } @@ -83,7 +81,6 @@ public final class ParsedHeadersTest extends TestCase { 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); } @@ -92,14 +89,12 @@ public final class ParsedHeadersTest extends TestCase { 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() { @@ -145,7 +140,6 @@ public final class ParsedHeadersTest extends TestCase { 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); @@ -153,7 +147,6 @@ public final class ParsedHeadersTest extends TestCase { assertEquals(60, parsedHeaders.maxAgeSeconds); assertEquals(70, parsedHeaders.sMaxAgeSeconds); assertTrue(parsedHeaders.isPublic); - assertEquals("a", parsedHeaders.privateField); assertTrue(parsedHeaders.mustRevalidate); } diff --git a/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java b/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java index 2db8e82..84b7942 100644 --- a/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java +++ b/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java @@ -16,6 +16,16 @@ package libcore.xml; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import junit.framework.Assert; import junit.framework.TestCase; import org.apache.harmony.xml.ExpatReader; @@ -27,20 +37,8 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.ext.DefaultHandler2; import org.xml.sax.helpers.DefaultHandler; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.net.ServerSocket; -import java.net.Socket; +import tests.http.MockResponse; +import tests.http.MockWebServer; public class ExpatSaxParserTest extends TestCase { @@ -586,98 +584,44 @@ public class ExpatSaxParserTest extends TestCase { } public void testExternalEntityDownload() throws IOException, SAXException { - class Server implements Runnable { - - private final ServerSocket serverSocket; - - Server() throws IOException { - serverSocket = new ServerSocket(8080); - } - - public void run() { - try { - Socket socket = serverSocket.accept(); - - final InputStream in = socket.getInputStream(); - Thread inputThread = new Thread() { - public void run() { - try { - byte[] buffer = new byte[1024]; - while (in.read(buffer) > -1) { /* ignore */ } - } catch (IOException e) { - e.printStackTrace(); - } - } - }; - inputThread.setDaemon(true); - inputThread.start(); - - OutputStream out = socket.getOutputStream(); - - String body = "<bar></bar>"; - String response = "HTTP/1.0 200 OK\n" - + "Content-Length: " + body.length() + "\n" - + "\n" - + body; - - out.write(response.getBytes("UTF-8")); - out.close(); - serverSocket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } + final MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody("<bar></bar>")); + server.play(); class Handler extends DefaultHandler { + final List<String> elementNames = new ArrayList<String>(); - List<String> elementNames = new ArrayList<String>(); - - public InputSource resolveEntity(String publicId, String systemId) - throws IOException, SAXException { + @Override public InputSource resolveEntity(String publicId, String systemId) + throws IOException { // The parser should have resolved the systemId. - assertEquals("http://localhost:8080/systemBar", systemId); + assertEquals(server.getUrl("/systemBar").toString(), systemId); return new InputSource(systemId); } - @Override - public void startElement(String uri, String localName, String qName, - Attributes attributes) throws SAXException { + @Override public void startElement(String uri, String localName, String qName, + Attributes attributes) { elementNames.add(localName); } - @Override - public void endElement(String uri, String localName, String qName) - throws SAXException { + @Override public void endElement(String uri, String localName, String qName) { elementNames.add("/" + localName); } } - // Start server to serve up the XML for 'systemBar'. - Thread serverThread = new Thread(new Server()); - serverThread.setDaemon(true); - serverThread.start(); - // 'systemBar', the external entity, is relative to 'systemFoo': Reader in = new StringReader("<?xml version=\"1.0\"?>\n" + "<!DOCTYPE foo [\n" + " <!ENTITY bar SYSTEM 'systemBar'>\n" + "]>\n" + "<foo>&bar;</foo>"); - ExpatReader reader = new ExpatReader(); - Handler handler = new Handler(); - reader.setContentHandler(handler); reader.setEntityResolver(handler); - InputSource source = new InputSource(in); - source.setSystemId("http://localhost:8080/systemFoo"); + source.setSystemId(server.getUrl("/systemFoo").toString()); reader.parse(source); - - assertEquals(Arrays.asList("foo", "bar", "/bar", "/foo"), - handler.elementNames); + assertEquals(Arrays.asList("foo", "bar", "/bar", "/foo"), handler.elementNames); } /** diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java index 216af26..c516f67 100644 --- a/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java +++ b/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https/HttpsURLConnectionTest.java @@ -973,7 +973,7 @@ public class HttpsURLConnectionTest extends TestCase { log("Authentication required..."); // send Authentication Request os.write(respAuthenticationRequired.getBytes()); - // read response + // read request num = is.read(buff); if (num == -1) { // this connection was closed, @@ -990,8 +990,8 @@ public class HttpsURLConnectionTest extends TestCase { log("Got authenticated request:\n" + message); log("------------------"); // check provided authorization credentials - assertTrue("Received message does not contain authorization credentials", - message.toLowerCase().indexOf("proxy-authorization:") > 0); + assertTrue("no proxy-authorization credentials: " + message, + message.toLowerCase().indexOf("proxy-authorization:") != -1); } assertTrue(message.startsWith("CONNECT")); diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java index 5284749..534cb86 100644 --- a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java +++ b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/InetAddressTest.java @@ -360,8 +360,8 @@ public class InetAddressTest extends junit.framework.TestCase { public void test_hashCode() { int hashCode = getHashCode(Support_Configuration.InetTestIP); - int ip6HashCode = getHashCode(Support_Configuration.InetTestIP6); - int ip6LOHashCode = getHashCode(Support_Configuration.InetTestIP6LO); + int ip6HashCode = getHashCode("fe80::20d:60ff:fe24:7410"); + int ip6LOHashCode = getHashCode("::1"); assertFalse("Hash collision", hashCode == ip6HashCode); assertFalse("Hash collision", ip6HashCode == ip6LOHashCode); assertFalse("Hash collision", hashCode == ip6LOHashCode); diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java index dd6a0f0..f2d64cd 100644 --- a/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java +++ b/luni/src/test/java/org/apache/harmony/luni/tests/java/net/URLConnectionTest.java @@ -71,24 +71,8 @@ public class URLConnectionTest extends TestCase { private JarURLConnection jarURLCon; - private URL jarURL; - private URLConnection gifURLCon; - private URL gifURL; - - public boolean isGetCalled; - - public boolean isPutCalled; - - private Map<String, List<String>> mockHeaderMap; - - private InputStream mockIs = new MockInputStream(); - - public boolean isCacheWriteCalled; - - public boolean isAbortCalled; - /** * {@link java.net.URLConnection#addRequestProperty(String, String)} */ @@ -220,107 +204,8 @@ public class URLConnectionTest extends TestCase { } } - class MockCachedResponseCache extends ResponseCache { - - public CacheResponse get(URI arg0, String arg1, Map arg2) - throws IOException { - if (null == arg0 || null == arg1 || null == arg2) { - throw new NullPointerException(); - } - isGetCalled = true; - return new MockCacheResponse(); - } - - public CacheRequest put(URI arg0, URLConnection arg1) - throws IOException { - if (null == arg0 || null == arg1) { - throw new NullPointerException(); - } - isPutCalled = true; - return new MockCacheRequest(); - } - } - - class MockNonCachedResponseCache extends ResponseCache { - - public CacheResponse get(URI arg0, String arg1, Map arg2) - throws IOException { - isGetCalled = true; - return null; - } - - public CacheRequest put(URI arg0, URLConnection arg1) - throws IOException { - isPutCalled = true; - return new MockCacheRequest(); - } - } - - class MockCacheRequest extends CacheRequest { - - public OutputStream getBody() throws IOException { - isCacheWriteCalled = true; - return new MockOutputStream(); - } - - public void abort() { - isAbortCalled = true; - } - - } - - class MockInputStream extends InputStream { - - public int read() throws IOException { - return 4711; - } - - public int read(byte[] arg0, int arg1, int arg2) throws IOException { - return 1; - } - - public int read(byte[] arg0) throws IOException { - return 1; - } - - } - - class MockOutputStream extends OutputStream { - - public void write(int b) throws IOException { - isCacheWriteCalled = true; - } - - public void write(byte[] b, int off, int len) throws IOException { - isCacheWriteCalled = true; - } - - public void write(byte[] b) throws IOException { - isCacheWriteCalled = true; - } - } - - class MockCacheResponse extends CacheResponse { - - public Map<String, List<String>> getHeaders() throws IOException { - return mockHeaderMap; - } - - public InputStream getBody() throws IOException { - return mockIs; - } - } - - private static int port; - static String getContentType(String fileName) throws IOException { - String resourceName = "org/apache/harmony/luni/tests/" + fileName; - URL url = ClassLoader.getSystemClassLoader().getResource(resourceName); - assertNotNull("Cannot find test resource " + resourceName, url); - return url.openConnection().getContentType(); - } - URL url; URL url2; @@ -349,10 +234,7 @@ public class URLConnectionTest extends TestCase { fileURLCon = fileURL.openConnection(); jarURLCon = openJarURLConnection(); - jarURL = jarURLCon.getURL(); - gifURLCon = openGifURLConnection(); - gifURL = gifURLCon.getURL(); } @Override @@ -679,7 +561,7 @@ public class URLConnectionTest extends TestCase { public void test_getDate() { // should be greater than 930000000000L which represents the past assertTrue("getDate gave wrong date: " + uc.getDate(), - uc.getDate() > 930000000000L); + uc.getDate() > 930000000000L); } /** @@ -722,65 +604,6 @@ public class URLConnectionTest extends TestCase { /** * @throws IOException - * {@link java.net.URLConnection#getDefaultUseCaches()} - */ - public void test_getDefaultUseCaches_CachedRC() throws IOException { - boolean oldSetting = uc.getDefaultUseCaches(); - - ResponseCache old = ResponseCache.getDefault(); - ResponseCache rc = new MockCachedResponseCache(); - ResponseCache.setDefault(rc); - - // Recreate the connection so that we get the cache from ResponseCache. - uc2 = url2.openConnection(); - - uc2.setUseCaches(true); - - uc.setDefaultUseCaches(false); - - // uc unaffected - assertTrue(uc.getUseCaches()); - // uc2 unaffected - assertTrue(uc2.getUseCaches()); - - //test get - assertFalse("getDefaultUseCaches should have returned false", uc - .getDefaultUseCaches()); - - // subsequent connections should have default value - URL url3 = new URL("http://localhost:" + port + "/test2"); - URLConnection uc3 = url3.openConnection(); - assertFalse(uc3.getUseCaches()); - - // test if uc does not cache but uc2 does - isGetCalled = false; - isPutCalled = false; - - // test uc - uc.setDoOutput(true); - assertFalse(isGetCalled); - uc.connect(); - assertFalse(isGetCalled); - assertFalse(isPutCalled); - OutputStream os = uc.getOutputStream(); - assertFalse(isPutCalled); - assertFalse(isGetCalled); - - os.close(); - - //uc2 should be unaffected - uc2.setDoOutput(true); - assertFalse(isGetCalled); - uc2.connect(); - assertTrue(isGetCalled); - assertFalse(isPutCalled); - - uc.setDefaultUseCaches(oldSetting); - ResponseCache.setDefault(null); - } - - /** - * @throws IOException * {@link java.net.URLConnection#getDoInput()} */ public void test_getDoInput() throws IOException { diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/util/Base64Test.java b/luni/src/test/java/org/apache/harmony/luni/tests/util/Base64Test.java deleted file mode 100644 index 70318ed..0000000 --- a/luni/src/test/java/org/apache/harmony/luni/tests/util/Base64Test.java +++ /dev/null @@ -1,45 +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 org.apache.harmony.luni.tests.util; - -import org.apache.harmony.luni.util.Base64; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Base64 encoder/decoder test. - */ -public class Base64Test extends TestCase { - - /** - * Checks the result on empty parameter. - */ - public static void testDecodeEmpty() throws Exception { - // Regression for HARMONY-1513 - byte[] result = Base64.decode(new byte[0]); - assertEquals("The length of the result differs from expected", - 0, result.length); - } - - public static Test suite() { - return new TestSuite(Base64Test.class); - } -} - diff --git a/luni/src/test/java/tests/api/java/util/EventObjectTest.java b/luni/src/test/java/tests/api/java/util/EventObjectTest.java deleted file mode 100644 index 201d9f3..0000000 --- a/luni/src/test/java/tests/api/java/util/EventObjectTest.java +++ /dev/null @@ -1,74 +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 tests.api.java.util; - -import java.util.EventObject; - -public class EventObjectTest extends junit.framework.TestCase { - - Object myObject; - - EventObject myEventObject; - - /** - * java.util.EventObject#EventObject(java.lang.Object) - */ - public void test_ConstructorLjava_lang_Object() { - try { - new EventObject(null); - fail ("IllegalArgumentException expected"); - } catch (IllegalArgumentException e) { - //expected - } - } - - /** - * java.util.EventObject#getSource() - */ - public void test_getSource() { - // Test for method java.lang.Object java.util.EventObject.getSource() - assertTrue("Wrong source returned", - myEventObject.getSource() == myObject); - } - - /** - * java.util.EventObject#toString() - */ - public void test_toString() { - // Test for method java.lang.String java.util.EventObject.toString() - assertTrue("Incorrect toString returned: " + myEventObject.toString(), - myEventObject.toString().indexOf( - "java.util.EventObject[source=java.lang.Object@") == 0); - } - - /** - * Sets up the fixture, for example, open a network connection. This method - * is called before a test is executed. - */ - protected void setUp() { - myObject = new Object(); - myEventObject = new EventObject(myObject); - } - - /** - * Tears down the fixture, for example, close a network connection. This - * method is called after a test is executed. - */ - protected void tearDown() { - } -} diff --git a/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java b/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java index d868cb2..8a02f9c 100644 --- a/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java +++ b/luni/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java @@ -40,7 +40,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import javax.security.cert.X509Certificate; import junit.framework.TestCase; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; import org.apache.harmony.xnet.tests.support.mySSLSession; import tests.support.Support_PortManager; diff --git a/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java b/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java index 3c49fc4..e2ebdbd 100644 --- a/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java +++ b/luni/src/test/java/tests/api/javax/net/ssl/HttpsURLConnectionTest.java @@ -46,71 +46,45 @@ public class HttpsURLConnectionTest extends TestCase { /** * javax.net.ssl.HttpsURLConnection#HttpsURLConnection(java_net_URL) */ - public final void test_Constructor() { - try { - MyHttpsURLConnection huc = new MyHttpsURLConnection(new URL("https://www.fortify.net/")); - } catch (Exception e) { - fail("Unexpected exception: " + e.toString()); - } - try { - MyHttpsURLConnection huc = new MyHttpsURLConnection(null); - } catch (Exception e) { - fail("Unexpected exception " + e.toString()); - } + public final void test_Constructor() throws Exception { + new MyHttpsURLConnection(new URL("https://www.fortify.net/")); + new MyHttpsURLConnection(null); } /** * javax.net.ssl.HttpsURLConnection#getCipherSuite() */ - public final void test_getCipherSuite() { + public final void test_getCipherSuite() throws Exception { + URL url = new URL("https://localhost:55555"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try { - URL url = new URL("https://localhost:55555"); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - try { - connection.getCipherSuite(); - fail("IllegalStateException wasn't thrown"); - } catch (IllegalStateException ise) { - //expected - } - } catch (Exception e) { - fail("Unexpected exception " + e + " for exception case"); + connection.getCipherSuite(); + fail("IllegalStateException wasn't thrown"); + } catch (IllegalStateException expected) { } - try { - HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/")); - assertEquals("CipherSuite", con.getCipherSuite()); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/")); + assertEquals("CipherSuite", con.getCipherSuite()); } /** * javax.net.ssl.HttpsURLConnection#getLocalCertificates() */ - public final void test_getLocalCertificates() { + public final void test_getLocalCertificates() throws Exception { + URL url = new URL("https://localhost:55555"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try { - URL url = new URL("https://localhost:55555"); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - try { - connection.getLocalCertificates(); - fail("IllegalStateException wasn't thrown"); - } catch (IllegalStateException ise) { - //expected - } - } catch (Exception e) { - fail("Unexpected exception " + e + " for exception case"); + connection.getLocalCertificates(); + fail("IllegalStateException wasn't thrown"); + } catch (IllegalStateException expected) { } - try { - HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508"); - assertNull(con.getLocalCertificates()); - con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509"); - Certificate[] cert = con.getLocalCertificates(); - assertNotNull(cert); - assertEquals(1, cert.length); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508"); + assertNull(con.getLocalCertificates()); + con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509"); + Certificate[] cert = con.getLocalCertificates(); + assertNotNull(cert); + assertEquals(1, cert.length); } /** @@ -148,96 +122,67 @@ public class HttpsURLConnectionTest extends TestCase { /** * javax.net.ssl.HttpsURLConnection#getLocalPrincipal() */ - public final void test_getLocalPrincipal() { + public final void test_getLocalPrincipal() throws Exception { + URL url = new URL("https://localhost:55555"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try { - URL url = new URL("https://localhost:55555"); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - try { - connection.getLocalPrincipal(); - fail("IllegalStateException wasn't thrown"); - } catch (IllegalStateException ise) { - //expected - } - } catch (Exception e) { - fail("Unexpected exception " + e + " for exception case"); + connection.getLocalPrincipal(); + fail("IllegalStateException wasn't thrown"); + } catch (IllegalStateException expected) { } - try { - HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508"); - assertNull(con.getLocalPrincipal()); - con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509"); - assertNotNull("Local principal is null", con.getLocalPrincipal()); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508"); + assertNull(con.getLocalPrincipal()); + con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509"); + assertNotNull("Local principal is null", con.getLocalPrincipal()); } /** * javax.net.ssl.HttpsURLConnection#getPeerPrincipal() */ public final void test_getPeerPrincipal() throws Exception { + URL url = new URL("https://localhost:55555"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try { - URL url = new URL("https://localhost:55555"); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - try { - connection.getPeerPrincipal(); - fail("IllegalStateException wasn't thrown"); - } catch (IllegalStateException ise) { - //expected - } - } catch (Exception e) { - fail("Unexpected exception " + e + " for exception case"); + connection.getPeerPrincipal(); + fail("IllegalStateException wasn't thrown"); + } catch (IllegalStateException expected) { } HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508"); try { Principal p = con.getPeerPrincipal(); fail("SSLPeerUnverifiedException wasn't thrown"); - } catch (SSLPeerUnverifiedException e) { - //expected + } catch (SSLPeerUnverifiedException expected) { } con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509"); - try { - Principal p = con.getPeerPrincipal(); - assertNotNull(p); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + Principal p = con.getPeerPrincipal(); + assertNotNull(p); } /** * javax.net.ssl.HttpsURLConnection#getServerCertificates() */ public final void test_getServerCertificates() throws Exception { + URL url = new URL("https://localhost:55555"); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); try { - URL url = new URL("https://localhost:55555"); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - try { - connection.getServerCertificates(); - fail("IllegalStateException wasn't thrown"); - } catch (IllegalStateException ise) { - //expected - } - } catch (Exception e) { - fail("Unexpected exception " + e + " for exception case"); + connection.getServerCertificates(); + fail("IllegalStateException wasn't thrown"); + } catch (IllegalStateException expected) { } HttpsURLConnection con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.508"); try { - Certificate[] cert = con.getServerCertificates(); + con.getServerCertificates(); fail("SSLPeerUnverifiedException wasn't thrown"); - } catch (SSLPeerUnverifiedException e) { - //expected + } catch (SSLPeerUnverifiedException expected) { } con = new MyHttpsURLConnection(new URL("https://www.fortify.net/"), "X.509"); - try { - Certificate[] cert = con.getServerCertificates(); - assertNotNull(cert); - assertEquals(1, cert.length); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + Certificate[] cert = con.getServerCertificates(); + assertNotNull(cert); + assertEquals(1, cert.length); } /** @@ -258,16 +203,13 @@ public class HttpsURLConnectionTest extends TestCase { try { HttpsURLConnection.setDefaultHostnameVerifier(null); fail("No expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // expected + } catch (IllegalArgumentException expected) { } HostnameVerifier def = HttpsURLConnection.getDefaultHostnameVerifier(); try { myHostnameVerifier hnv = new myHostnameVerifier(); HttpsURLConnection.setDefaultHostnameVerifier(hnv); assertEquals(hnv, HttpsURLConnection.getDefaultHostnameVerifier()); - } catch (Exception e) { - fail("Unexpected exception " + e); } finally { HttpsURLConnection.setDefaultHostnameVerifier(def); } @@ -281,14 +223,10 @@ public class HttpsURLConnectionTest extends TestCase { try { con.setHostnameVerifier(null); fail("No expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - } - try { - myHostnameVerifier hnv = new myHostnameVerifier(); - con.setHostnameVerifier(hnv); - } catch (Exception e) { - fail("Unexpected exception " + e); + } catch (IllegalArgumentException expected) { } + myHostnameVerifier hnv = new myHostnameVerifier(); + con.setHostnameVerifier(hnv); } /** @@ -298,15 +236,11 @@ public class HttpsURLConnectionTest extends TestCase { try { HttpsURLConnection.setDefaultSSLSocketFactory(null); fail("No expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - } - try { - SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory - .getDefault(); - HttpsURLConnection.setDefaultSSLSocketFactory(ssf); - } catch (Exception e) { - fail("Unexpected exception " + e); + } catch (IllegalArgumentException expected) { } + SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory + .getDefault(); + HttpsURLConnection.setDefaultSSLSocketFactory(ssf); } /** @@ -317,15 +251,11 @@ public class HttpsURLConnectionTest extends TestCase { try { con.setSSLSocketFactory(null); fail("No expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - } - try { - SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory - .getDefault(); - con.setSSLSocketFactory(ssf); - } catch (Exception e) { - fail("Unexpected exception " + e); + } catch (IllegalArgumentException expected) { } + SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory + .getDefault(); + con.setSSLSocketFactory(ssf); } } @@ -353,32 +283,30 @@ class MyHttpsURLConnection extends javax.net.ssl.HttpsURLConnection { * @see javax.net.ssl.HttpsURLConnection#getLocalCertificates() */ public Certificate[] getLocalCertificates() { - Certificate cert = null; try { CertificateFactory cf = CertificateFactory.getInstance(typeDone); byte[] barr = TestUtils.getX509Certificate_v1(); ByteArrayInputStream bis = new ByteArrayInputStream(barr); - cert = cf.generateCertificate(bis); + Certificate cert = cf.generateCertificate(bis); + return new Certificate[] { cert }; } catch (CertificateException se) { - cert = null; + return null; } - return cert == null ? null : new Certificate[]{cert}; } /* * @see javax.net.ssl.HttpsURLConnection#getServerCertificates() */ public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException { - Certificate cert = null; try { CertificateFactory cf = CertificateFactory.getInstance(typeDone); byte[] barr = TestUtils.getX509Certificate_v3(); ByteArrayInputStream bis = new ByteArrayInputStream(barr); - cert = cf.generateCertificate(bis); + Certificate cert = cf.generateCertificate(bis); + return new Certificate[] { cert }; } catch (CertificateException se) { throw new SSLPeerUnverifiedException("No server's end-entity certificate"); } - return cert == null ? null : new Certificate[]{cert}; } /* diff --git a/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java b/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java index 6c8e657..cc96782 100644 --- a/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java +++ b/luni/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java @@ -18,7 +18,7 @@ package tests.api.javax.net.ssl; import junit.framework.TestCase; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; import tests.support.Support_PortManager; @@ -470,13 +470,13 @@ public class SSLServerSocketTest extends TestCase { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, null, null); for (int i = 0; i < 2048; ++i) { - sslContext.getServerSocketFactory().createServerSocket(); + sslContext.getServerSocketFactory().createServerSocket().close(); } // Test the other codepath, which copies a seed from a byte[]. sslContext.init(keyManagers, null, new SecureRandom()); for (int i = 0; i < 2048; ++i) { - sslContext.getServerSocketFactory().createServerSocket(); + sslContext.getServerSocketFactory().createServerSocket().close(); } } } diff --git a/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java b/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java index 70c0eb2..ec23cae 100644 --- a/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java +++ b/luni/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java @@ -36,7 +36,7 @@ import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import junit.framework.TestCase; -import org.apache.harmony.luni.util.Base64; +import libcore.io.Base64; import tests.api.javax.net.ssl.HandshakeCompletedEventTest.MyHandshakeListener; import tests.api.javax.net.ssl.HandshakeCompletedEventTest.TestTrustManager; import tests.support.Support_PortManager; diff --git a/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java index 84ac212..ab60f72 100644 --- a/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java @@ -16,31 +16,34 @@ package tests.api.javax.net.ssl; import dalvik.annotation.AndroidOnly; - -import javax.net.ssl.*; -import javax.security.cert.X509Certificate; - -import java.net.*; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.util.Arrays; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; - +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.util.Arrays; +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.security.cert.X509Certificate; import junit.framework.TestCase; - -import org.apache.harmony.luni.util.Base64; - +import libcore.io.Base64; import tests.api.javax.net.ssl.HandshakeCompletedEventTest.TestTrustManager; import tests.support.Support_PortManager; +import libcore.java.security.StandardNames; public class SSLSocketTest extends TestCase { public class HandshakeCL implements HandshakeCompletedListener { - HandshakeCL() { - super(); - } public void handshakeCompleted(HandshakeCompletedEvent event) { } } @@ -48,257 +51,164 @@ public class SSLSocketTest extends TestCase { /** * javax.net.ssl.SSLSocket#SSLSocket() */ - public void testConstructor_01() { - try { - SSLSocket ssl = getSSLSocket(); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + public void testConstructor_01() throws Exception { + SSLSocket ssl = getSSLSocket(); + assertNotNull(ssl); + ssl.close(); } /** - * @throws IOException - * @throws UnknownHostException * javax.net.ssl.SSLSocket#SSLSocket(InetAddress address, int port) */ public void testConstructor_02() throws UnknownHostException, IOException { - SSLSocket ssl; int sport = startServer("Cons InetAddress,I"); int[] invalidPort = {-1, Integer.MIN_VALUE, 65536, Integer.MAX_VALUE}; - ssl = getSSLSocket(InetAddress.getLocalHost(), sport); + SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost(), sport); assertNotNull(ssl); assertEquals(sport, ssl.getPort()); + ssl.close(); try { - ssl = getSSLSocket(InetAddress.getLocalHost(), sport + 1); - fail("IOException wasn't thrown ..."); - } catch (IOException e) { - //expected + getSSLSocket(InetAddress.getLocalHost(), sport + 1); + fail(); + } catch (IOException expected) { } for (int i = 0; i < invalidPort.length; i++) { try { - ssl = getSSLSocket(InetAddress.getLocalHost(), invalidPort[i]); - fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]); + getSSLSocket(InetAddress.getLocalHost(), invalidPort[i]); + fail(); + } catch (IllegalArgumentException expected) { } } } /** - * @throws IOException - * @throws UnknownHostException * javax.net.ssl.SSLSocket#SSLSocket(InetAddress address, int port, * InetAddress clientAddress, int clientPort) */ public void testConstructor_03() throws UnknownHostException, IOException { - SSLSocket ssl; int sport = startServer("Cons InetAddress,I,InetAddress,I"); int portNumber = Support_PortManager.getNextPort(); - ssl = getSSLSocket(InetAddress.getLocalHost(), sport, - InetAddress.getLocalHost(), portNumber); + SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost(), sport, + InetAddress.getLocalHost(), portNumber); assertNotNull(ssl); assertEquals(sport, ssl.getPort()); assertEquals(portNumber, ssl.getLocalPort()); + ssl.close(); try { - ssl = getSSLSocket(InetAddress.getLocalHost(), 8081, InetAddress.getLocalHost(), 8082); - fail("IOException wasn't thrown ..."); - } catch (IOException e) { - //expected + getSSLSocket(InetAddress.getLocalHost(), 8081, InetAddress.getLocalHost(), 8082); + fail(); + } catch (IOException expected) { } try { - ssl = getSSLSocket(InetAddress.getLocalHost(), -1, - InetAddress.getLocalHost(), sport + 1); - fail("IllegalArgumentException wasn't thrown for -1"); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for -1"); + getSSLSocket(InetAddress.getLocalHost(), -1, InetAddress.getLocalHost(), sport + 1); + fail(); + } catch (IllegalArgumentException expected) { } try { - ssl = getSSLSocket(InetAddress.getLocalHost(), sport, - InetAddress.getLocalHost(), -1); - fail("IllegalArgumentException wasn't thrown for -1"); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for -1"); + getSSLSocket(InetAddress.getLocalHost(), sport, InetAddress.getLocalHost(), -1); + fail(); + } catch (IllegalArgumentException expected) { } try { - ssl = getSSLSocket(InetAddress.getLocalHost(), Integer.MIN_VALUE, - InetAddress.getLocalHost(), sport + 1); - fail("IOException wasn't thrown for " + Integer.MIN_VALUE); - } catch (IOException ioe) { - // expected on RI - } catch (IllegalArgumentException iae) { - // expected on Android - } catch (Exception e) { - fail(e + " was thrown instead of IOException for " - + Integer.MIN_VALUE); + getSSLSocket(InetAddress.getLocalHost(), Integer.MIN_VALUE, + InetAddress.getLocalHost(), sport + 1); + fail(); + } catch (IOException expectedOnRI) { + assertTrue(StandardNames.IS_RI); + } catch (IllegalArgumentException expectedOnAndroid) { + assertFalse(StandardNames.IS_RI); } try { - ssl = getSSLSocket(InetAddress.getLocalHost(), sport, - InetAddress.getLocalHost(), Integer.MIN_VALUE); - fail("IllegalArgumentException wasn't thrown for " - + Integer.MIN_VALUE); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for " - + Integer.MIN_VALUE); - } - - try { - ssl = getSSLSocket(InetAddress.getLocalHost(), 65536, - InetAddress.getLocalHost(), sport + 1); - fail("IOException wasn't thrown for 65536"); - } catch (IOException ioe) { - // expected on RI - } catch (IllegalArgumentException iae) { - // expected on Android - } catch (Exception e) { - fail(e + " was thrown instead of IOException for 65536"); - } - try { - ssl = getSSLSocket(InetAddress.getLocalHost(), sport, - InetAddress.getLocalHost(), 65536); - fail("IllegalArgumentException wasn't thrown for 65536"); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for 65536"); - } - - try { - ssl = getSSLSocket(InetAddress.getLocalHost(), Integer.MAX_VALUE, - InetAddress.getLocalHost(), sport + 1); - fail("IOException wasn't thrown for " + Integer.MAX_VALUE); - } catch (IOException ioe) { - // expected on RI - } catch (IllegalArgumentException iae) { - // expected on Android - } catch (Exception e) { - fail(e + " was thrown instead of IOException for " - + Integer.MAX_VALUE); - } - try { - ssl = getSSLSocket(InetAddress.getLocalHost(), sport, - InetAddress.getLocalHost(), Integer.MAX_VALUE); - fail("IllegalArgumentException wasn't thrown for " - + Integer.MAX_VALUE); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for " - + Integer.MAX_VALUE); + getSSLSocket(InetAddress.getLocalHost(), sport, + InetAddress.getLocalHost(), Integer.MAX_VALUE); + fail(); + } catch (IllegalArgumentException expectedOnAndroid) { + assertFalse(StandardNames.IS_RI); } } /** - * @throws IOException - * @throws UnknownHostException * javax.net.ssl.SSLSocket#SSLSocket(String host, int port) */ public void testConstructor_04() throws UnknownHostException, IOException { - SSLSocket ssl; int sport = startServer("Cons String,I"); int[] invalidPort = {-1, Integer.MIN_VALUE, 65536, Integer.MAX_VALUE}; - ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport); + SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport); assertNotNull(ssl); assertEquals(sport, ssl.getPort()); + ssl.close(); try { - ssl = getSSLSocket("localhost", 8082); - fail("IOException wasn't thrown ..."); - } catch (IOException e) { - //expected + getSSLSocket("localhost", 8082); + fail(); + } catch (IOException expected) { } for (int i = 0; i < invalidPort.length; i++) { try { - ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i]); - fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]); + getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i]); + fail(); + } catch (IllegalArgumentException expected) { } } try { - ssl = getSSLSocket("bla-bla", sport); - fail("UnknownHostException wasn't thrown"); - } catch (UnknownHostException uhp) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of UnknownHostException"); + getSSLSocket("bla-bla", sport); + fail(); + } catch (UnknownHostException expected) { } } /** - * @throws IOException - * @throws UnknownHostException * javax.net.ssl.SSLSocket#SSLSocket(String host, int port, InetAddress clientAddress, * int clientPort) */ public void testConstructor_05() throws UnknownHostException, IOException { - SSLSocket ssl; int sport = startServer("Cons String,I,InetAddress,I"); int portNumber = Support_PortManager.getNextPort(); int[] invalidPort = {-1, Integer.MIN_VALUE, 65536, Integer.MAX_VALUE}; - ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport, - InetAddress.getLocalHost(), portNumber); + SSLSocket ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport, + InetAddress.getLocalHost(), portNumber); assertNotNull(ssl); assertEquals(sport, ssl.getPort()); assertEquals(portNumber, ssl.getLocalPort()); try { - ssl = getSSLSocket("localhost", 8081, InetAddress.getLocalHost(), 8082); - fail("IOException wasn't thrown ..."); - } catch (IOException e) { - //expected + getSSLSocket("localhost", 8081, InetAddress.getLocalHost(), 8082); + fail(); + } catch (IOException expected) { } for (int i = 0; i < invalidPort.length; i++) { portNumber = Support_PortManager.getNextPort(); try { - ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i], - InetAddress.getLocalHost(), portNumber); - fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]); + getSSLSocket(InetAddress.getLocalHost().getHostName(), invalidPort[i], + InetAddress.getLocalHost(), portNumber); + fail(); + } catch (IllegalArgumentException expected) { } try { - ssl = getSSLSocket(InetAddress.getLocalHost().getHostName(), sport, - InetAddress.getLocalHost(), invalidPort[i]); - fail("IllegalArgumentException wasn't thrown for " + invalidPort[i]); - } catch (IllegalArgumentException iae) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException for " + invalidPort[i]); + getSSLSocket(InetAddress.getLocalHost().getHostName(), sport, + InetAddress.getLocalHost(), invalidPort[i]); + fail(); + } catch (IllegalArgumentException expected) { } } portNumber = Support_PortManager.getNextPort(); try { - ssl = getSSLSocket("bla-bla", sport, InetAddress.getLocalHost(), portNumber); - fail("UnknownHostException wasn't thrown"); - } catch (UnknownHostException uhp) { - // expected - } catch (Exception e) { - fail(e + " was thrown instead of UnknownHostException"); + getSSLSocket("bla-bla", sport, InetAddress.getLocalHost(), portNumber); + fail(); + } catch (UnknownHostException expected) { } } @@ -307,18 +217,17 @@ public class SSLSocketTest extends TestCase { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, null, null); for (int i = 0; i < 2048; ++i) { - sslContext.getSocketFactory().createSocket(); + sslContext.getSocketFactory().createSocket().close(); } // Test the other codepath, which copies a seed from a byte[]. sslContext.init(null, null, new SecureRandom()); for (int i = 0; i < 2048; ++i) { - sslContext.getSocketFactory().createSocket(); + sslContext.getSocketFactory().createSocket().close(); } } /** - * @throws IOException * javax.net.ssl.SSLSocket#addHandshakeCompletedListener(HandshakeCompletedListener listener) */ @AndroidOnly("RI doesn't throw the specified IAE") @@ -327,19 +236,14 @@ public class SSLSocketTest extends TestCase { HandshakeCompletedListener ls = new HandshakeCL(); try { ssl.addHandshakeCompletedListener(null); - fail("IllegalArgumentException wasn't thrown"); - } catch (IllegalArgumentException iae) { - //expected - } - try { - ssl.addHandshakeCompletedListener(ls); - } catch (Exception e) { - fail("Unexpected exception " + e); + fail(); + } catch (IllegalArgumentException expected) { } + ssl.addHandshakeCompletedListener(ls); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#removeHandshakeCompletedListener(HandshakeCompletedListener listener) */ public void test_removeHandshakeCompletedListener() throws IOException { @@ -347,29 +251,21 @@ public class SSLSocketTest extends TestCase { HandshakeCompletedListener ls = new HandshakeCL(); try { ssl.removeHandshakeCompletedListener(null); - fail("IllegalArgumentException wasn't thrown"); - } catch (IllegalArgumentException iae) { - //expected + fail(); + } catch (IllegalArgumentException expected) { } try { ssl.removeHandshakeCompletedListener(ls); - } catch (IllegalArgumentException iae) { - //expected - } catch (Exception e) { - fail("Unexpected exception " + e); + } catch (IllegalArgumentException expected) { } ssl.addHandshakeCompletedListener(ls); - try { - ssl.removeHandshakeCompletedListener(ls); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + ssl.removeHandshakeCompletedListener(ls); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#setEnableSessionCreation(boolean flag) * javax.net.ssl.SSLSocket#getEnableSessionCreation() */ @@ -380,11 +276,10 @@ public class SSLSocketTest extends TestCase { assertFalse(ssl.getEnableSessionCreation()); ssl.setEnableSessionCreation(true); assertTrue(ssl.getEnableSessionCreation()); + ssl.close(); } /** - * @throws IOException - * @throws UnknownHostException * javax.net.ssl.SSLSocket#setNeedClientAuth(boolean need) * javax.net.ssl.SSLSocket#getNeedClientAuthCreation() */ @@ -394,11 +289,10 @@ public class SSLSocketTest extends TestCase { assertTrue(ssl.getNeedClientAuth()); ssl.setNeedClientAuth(false); assertFalse(ssl.getNeedClientAuth()); + ssl.close(); } /** - * @throws IOException - * @throws UnknownHostException * javax.net.ssl.SSLSocket#setWantClientAuth(boolean want) * javax.net.ssl.SSLSocket#getWantClientAuthCreation() */ @@ -408,20 +302,20 @@ public class SSLSocketTest extends TestCase { assertTrue(ssl.getWantClientAuth()); ssl.setWantClientAuth(false); assertFalse(ssl.getWantClientAuth()); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#getSupportedProtocols() */ public void test_getSupportedProtocols() throws IOException { SSLSocket ssl = getSSLSocket(); String[] res = ssl.getSupportedProtocols(); assertTrue("No supported protocols found", res.length > 0); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#getEnabledProtocols() * javax.net.ssl.SSLSocket#setEnabledProtocols(String[] protocols) */ @@ -429,50 +323,42 @@ public class SSLSocketTest extends TestCase { SSLSocket ssl = getSSLSocket(); try { ssl.setEnabledProtocols(null); - } catch (IllegalArgumentException iae) { - //expected - } - try { - ssl.setEnabledProtocols(new String[] {}); - } catch (IllegalArgumentException iae) { - //expected + fail(); + } catch (IllegalArgumentException expected) { } + ssl.setEnabledProtocols(new String[] {}); try { ssl.setEnabledProtocols(new String[] {"blubb"}); - } catch (IllegalArgumentException iae) { - //expected + fail(); + } catch (IllegalArgumentException expected) { } ssl.setEnabledProtocols(ssl.getEnabledProtocols()); String[] res = ssl.getEnabledProtocols(); assertEquals("no enabled protocols set", - ssl.getEnabledProtocols().length, res.length); + ssl.getEnabledProtocols().length, res.length); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#getSession() */ public void test_getSession() throws IOException { SSLSocket ssl = getSSLSocket(); - try { - assertNotNull(ssl.getSession()); - } catch (Exception e) { - fail("Unexpected exception " + e); - } + assertNotNull(ssl.getSession()); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#getSupportedCipherSuites() */ public void test_getSupportedCipherSuites() throws IOException { SSLSocket ssl = getSSLSocket(); String[] res = ssl.getSupportedCipherSuites(); assertTrue("no supported cipher suites", res.length > 0); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#getEnabledCipherSuites() * javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[] suites) */ @@ -480,18 +366,14 @@ public class SSLSocketTest extends TestCase { SSLSocket ssl = getSSLSocket(); try { ssl.setEnabledCipherSuites(null); - } catch (IllegalArgumentException iae) { - //expected - } - try { - ssl.setEnabledCipherSuites(new String[] {}); - } catch (IllegalArgumentException iae) { - //expected + fail(); + } catch (IllegalArgumentException expected) { } + ssl.setEnabledCipherSuites(new String[] {}); try { ssl.setEnabledCipherSuites(new String[] {"blubb"}); - } catch (IllegalArgumentException iae) { - //expected + fail(); + } catch (IllegalArgumentException expected) { } ssl.setEnabledCipherSuites(ssl.getSupportedCipherSuites()); String[] res = ssl.getEnabledCipherSuites(); @@ -499,10 +381,10 @@ public class SSLSocketTest extends TestCase { assertEquals("not all supported cipher suites were enabled", Arrays.asList(ssl.getSupportedCipherSuites()), Arrays.asList(res)); + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#getUseClientMode() * javax.net.ssl.SSLSocket#setUseClientMode(boolean mode) */ @@ -511,6 +393,7 @@ public class SSLSocketTest extends TestCase { assertTrue(ssl.getUseClientMode()); ssl.setUseClientMode(false); assertFalse(ssl.getUseClientMode()); + ssl.close(); ssl = getSSLSocket("localhost", startServer("UseClientMode")); try { @@ -520,32 +403,26 @@ public class SSLSocketTest extends TestCase { } try { ssl.setUseClientMode(false); - fail("IllegalArgumentException wasn't thrown"); - } catch (IllegalArgumentException iae) { - //expected - } catch (Exception e) { - fail(e + " was thrown instead of IllegalArgumentException"); + fail(); + } catch (IllegalArgumentException expected) { } + ssl.close(); } /** - * @throws IOException * javax.net.ssl.SSLSocket#startHandshake() */ public void test_startHandshake() throws IOException { SSLSocket ssl = getSSLSocket(); try { ssl.startHandshake(); - fail("IOException wasn't thrown"); - } catch (IOException ioe) { - //expected - } catch (Exception e) { - fail(e + " was thrown instead of IOException"); + fail(); + } catch (IOException expected) { } + ssl.close(); } - // Change this to false if on RI - boolean useBKS = true; + boolean useBKS = !StandardNames.IS_RI; private String PASSWORD = "android"; @@ -557,61 +434,61 @@ public class SSLSocketTest extends TestCase { * Defines the keystore contents for the server, BKS version. Holds just a * single self-generated key. The subject name is "Test Server". */ - private static final String SERVER_KEYS_BKS = - "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" + - "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" + - "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" + - "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" + - "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" + - "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" + - "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" + - "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" + - "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" + - "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" + - "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" + - "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" + - "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" + - "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" + - "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" + - "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" + - "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" + - "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" + - "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" + - "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" + - "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" + - "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" + - "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" + - "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw="; + private static final String SERVER_KEYS_BKS = "" + + "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" + + "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" + + "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" + + "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" + + "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" + + "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" + + "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" + + "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" + + "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" + + "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" + + "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" + + "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" + + "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" + + "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" + + "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" + + "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" + + "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" + + "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" + + "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" + + "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" + + "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" + + "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" + + "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" + + "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw="; /** * Defines the keystore contents for the server, JKS version. Holds just a * single self-generated key. The subject name is "Test Server". */ - private static final String SERVER_KEYS_JKS = - "/u3+7QAAAAIAAAABAAAAAQAFbXlrZXkAAAEaWFfBeAAAArowggK2MA4GCisGAQQBKgIRAQEFAASC" + - "AqI2kp5XjnF8YZkhcF92YsJNQkvsmH7zqMM87j23zSoV4DwyE3XeC/gZWq1ToScIhoqZkzlbWcu4" + - "T/Zfc/DrfGk/rKbBL1uWKGZ8fMtlZk8KoAhxZk1JSyJvdkyKxqmzUbxk1OFMlN2VJNu97FPVH+du" + - "dvjTvmpdoM81INWBW/1fZJeQeDvn4mMbbe0IxgpiLnI9WSevlaDP/sm1X3iO9yEyzHLL+M5Erspo" + - "Cwa558fOu5DdsICMXhvDQxjWFKFhPHnKtGe+VvwkG9/bAaDgx3kfhk0w5zvdnkKb+8Ed9ylNRzdk" + - "ocAa/mxlMTOsTvDKXjjsBupNPIIj7OP4GNnZaxkJjSs98pEO67op1GX2qhy6FSOPNuq8k/65HzUc" + - "PYn6voEeh6vm02U/sjEnzRevQ2+2wXoAdp0EwtQ/DlMe+NvcwPGWKuMgX4A4L93DZGb04N2VmAU3" + - "YLOtZwTO0LbuWrcCM/q99G/7LcczkxIVrO2I/rh8RXVczlf9QzcrFObFv4ATuspWJ8xG7DhsMbnk" + - "rT94Pq6TogYeoz8o8ZMykesAqN6mt/9+ToIemmXv+e+KU1hI5oLwWMnUG6dXM6hIvrULY6o+QCPH" + - "172YQJMa+68HAeS+itBTAF4Clm/bLn6reHCGGU6vNdwU0lYldpiOj9cB3t+u2UuLo6tiFWjLf5Zs" + - "EQJETd4g/EK9nHxJn0GAKrWnTw7pEHQJ08elzUuy04C/jEEG+4QXU1InzS4o/kR0Sqz2WTGDoSoq" + - "ewuPRU5bzQs/b9daq3mXrnPtRBL6HfSDAdpTK76iHqLCGdqx3avHjVSBm4zFvEuYBCev+3iKOBmg" + - "yh7eQRTjz4UOWfy85omMBr7lK8PtfVBDzOXpasxS0uBgdUyBDX4tO6k9jZ8a1kmQRQAAAAEABVgu" + - "NTA5AAACSDCCAkQwggGtAgRIR8SKMA0GCSqGSIb3DQEBBAUAMGkxCzAJBgNVBAYTAlVTMRMwEQYD" + - "VQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQHEwNNVFYxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMH" + - "QW5kcm9pZDEUMBIGA1UEAxMLVGVzdCBTZXJ2ZXIwHhcNMDgwNjA1MTA0ODQyWhcNMDgwOTAzMTA0" + - "ODQyWjBpMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8w" + - "DQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMIGf" + - "MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwoC6chqCI84rj1PrXuJgbiit4EV909zR6N0jNlYfg" + - "itwB39bP39wH03rFm8T59b3mbSptnGmCIpLZn25KPPFsYD3JJ+wFlmiUdEP9H05flfwtFQJnw9uT" + - "3rRIdYVMPcQ3RoZzwAMliGr882I2thIDbA6xjGU/1nRIdvk0LtxH3QIDAQABMA0GCSqGSIb3DQEB" + - "BAUAA4GBAJn+6YgUlY18Ie+0+Vt8oEi81DNi/bfPrAUAh63fhhBikx/3R9dl3wh09Z6p7cIdNxjW" + - "n2ll+cRW9eqF7z75F0Omm0C7/KAEPjukVbszmzeU5VqzkpSt0j84YWi+TfcHRrfvhLbrlmGITVpY" + - "ol5pHLDyqGmDs53pgwipWqsn/nEXEBgj3EoqPeqHbDf7YaP8h/5BSt0="; + private static final String SERVER_KEYS_JKS = "" + + "/u3+7QAAAAIAAAABAAAAAQAFbXlrZXkAAAEaWFfBeAAAArowggK2MA4GCisGAQQBKgIRAQEFAASC" + + "AqI2kp5XjnF8YZkhcF92YsJNQkvsmH7zqMM87j23zSoV4DwyE3XeC/gZWq1ToScIhoqZkzlbWcu4" + + "T/Zfc/DrfGk/rKbBL1uWKGZ8fMtlZk8KoAhxZk1JSyJvdkyKxqmzUbxk1OFMlN2VJNu97FPVH+du" + + "dvjTvmpdoM81INWBW/1fZJeQeDvn4mMbbe0IxgpiLnI9WSevlaDP/sm1X3iO9yEyzHLL+M5Erspo" + + "Cwa558fOu5DdsICMXhvDQxjWFKFhPHnKtGe+VvwkG9/bAaDgx3kfhk0w5zvdnkKb+8Ed9ylNRzdk" + + "ocAa/mxlMTOsTvDKXjjsBupNPIIj7OP4GNnZaxkJjSs98pEO67op1GX2qhy6FSOPNuq8k/65HzUc" + + "PYn6voEeh6vm02U/sjEnzRevQ2+2wXoAdp0EwtQ/DlMe+NvcwPGWKuMgX4A4L93DZGb04N2VmAU3" + + "YLOtZwTO0LbuWrcCM/q99G/7LcczkxIVrO2I/rh8RXVczlf9QzcrFObFv4ATuspWJ8xG7DhsMbnk" + + "rT94Pq6TogYeoz8o8ZMykesAqN6mt/9+ToIemmXv+e+KU1hI5oLwWMnUG6dXM6hIvrULY6o+QCPH" + + "172YQJMa+68HAeS+itBTAF4Clm/bLn6reHCGGU6vNdwU0lYldpiOj9cB3t+u2UuLo6tiFWjLf5Zs" + + "EQJETd4g/EK9nHxJn0GAKrWnTw7pEHQJ08elzUuy04C/jEEG+4QXU1InzS4o/kR0Sqz2WTGDoSoq" + + "ewuPRU5bzQs/b9daq3mXrnPtRBL6HfSDAdpTK76iHqLCGdqx3avHjVSBm4zFvEuYBCev+3iKOBmg" + + "yh7eQRTjz4UOWfy85omMBr7lK8PtfVBDzOXpasxS0uBgdUyBDX4tO6k9jZ8a1kmQRQAAAAEABVgu" + + "NTA5AAACSDCCAkQwggGtAgRIR8SKMA0GCSqGSIb3DQEBBAUAMGkxCzAJBgNVBAYTAlVTMRMwEQYD" + + "VQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQHEwNNVFYxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMH" + + "QW5kcm9pZDEUMBIGA1UEAxMLVGVzdCBTZXJ2ZXIwHhcNMDgwNjA1MTA0ODQyWhcNMDgwOTAzMTA0" + + "ODQyWjBpMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8w" + + "DQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMIGf" + + "MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwoC6chqCI84rj1PrXuJgbiit4EV909zR6N0jNlYfg" + + "itwB39bP39wH03rFm8T59b3mbSptnGmCIpLZn25KPPFsYD3JJ+wFlmiUdEP9H05flfwtFQJnw9uT" + + "3rRIdYVMPcQ3RoZzwAMliGr882I2thIDbA6xjGU/1nRIdvk0LtxH3QIDAQABMA0GCSqGSIb3DQEB" + + "BAUAA4GBAJn+6YgUlY18Ie+0+Vt8oEi81DNi/bfPrAUAh63fhhBikx/3R9dl3wh09Z6p7cIdNxjW" + + "n2ll+cRW9eqF7z75F0Omm0C7/KAEPjukVbszmzeU5VqzkpSt0j84YWi+TfcHRrfvhLbrlmGITVpY" + + "ol5pHLDyqGmDs53pgwipWqsn/nEXEBgj3EoqPeqHbDf7YaP8h/5BSt0="; protected int startServer(String name) { String keys = useBKS ? SERVER_KEYS_BKS : SERVER_KEYS_JKS; @@ -620,12 +497,15 @@ public class SSLSocketTest extends TestCase { serverThread.start(); try { while (!serverReady) { + Exception e = server.getException(); + if (e != null) { + throw new AssertionError(e); + } Thread.currentThread().sleep(50); } // give the server 100 millis to accept Thread.currentThread().sleep(100); - } catch (InterruptedException e) { - // ignore + } catch (InterruptedException ignore) { } return server.sport; } @@ -668,27 +548,34 @@ public class SSLSocketTest extends TestCase { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, null); - SSLServerSocket serverSocket = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(); - - serverSocket.bind(new InetSocketAddress(port)); - sport = serverSocket.getLocalPort(); - serverReady = true; - - SSLSocket clientSocket = (SSLSocket)serverSocket.accept(); - - InputStream stream = clientSocket.getInputStream(); - - for (int i = 0; i < 256; i++) { - int j = stream.read(); - if (i != j) { - throw new RuntimeException("Error reading socket, expected " + i + ", got " + j); + SSLServerSocket serverSocket = (SSLServerSocket) + sslContext.getServerSocketFactory().createServerSocket(); + try { + serverSocket.bind(new InetSocketAddress(port)); + sport = serverSocket.getLocalPort(); + serverReady = true; + + SSLSocket clientSocket = (SSLSocket)serverSocket.accept(); + + try { + InputStream stream = clientSocket.getInputStream(); + try { + for (int i = 0; i < 256; i++) { + int j = stream.read(); + if (i != j) { + throw new RuntimeException("Error reading socket, expected " + i + + ", got " + j); + } + } + } finally { + stream.close(); + } + } finally { + clientSocket.close(); } + } finally { + serverSocket.close(); } - - stream.close(); - clientSocket.close(); - serverSocket.close(); - } catch (Exception ex) { exception = ex; } @@ -724,32 +611,30 @@ public class SSLSocketTest extends TestCase { } private SSLSocket getSSLSocket() throws IOException { - SSLSocket ssl = null; - ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(); - return ssl; + return (SSLSocket) SSLSocketFactory.getDefault().createSocket(); } private SSLSocket getSSLSocket(InetAddress host, int port) throws IOException { - SSLSocket ssl = null; - ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port); - return ssl; + return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port); } private SSLSocket getSSLSocket(String host, int port) throws UnknownHostException, IOException { - SSLSocket ssl = null; - ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port); - return ssl; + return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port); } - private SSLSocket getSSLSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { - SSLSocket ssl = null; - ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port, localHost, localPort); - return ssl; + private SSLSocket getSSLSocket(InetAddress host, int port, InetAddress localHost, int localPort) + throws IOException { + return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, + port, + localHost, + localPort); } - private SSLSocket getSSLSocket(String host, int port, InetAddress localHost, int localPort) throws UnknownHostException, IOException { - SSLSocket ssl = null; - ssl = (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, port, localHost, localPort); - return ssl; + private SSLSocket getSSLSocket(String host, int port, InetAddress localHost, int localPort) + throws UnknownHostException, IOException { + return (SSLSocket) SSLSocketFactory.getDefault().createSocket(host, + port, + localHost, + localPort); } } diff --git a/run-libcore-tests b/run-libcore-tests new file mode 100755 index 0000000..a1a66ae --- /dev/null +++ b/run-libcore-tests @@ -0,0 +1,24 @@ +#!/bin/bash + +# make sure there's a vogar on the path, but prefer the user's one. +export PATH=$PATH:~dalvik-prebuild/vogar/bin + +VOGAR="vogar $VOGAR_FLAGS $*" + +# We enumerate the test packages for vogar rather than just giving it the classes.jar +# so hundreds of packages can be tested in parallel, rather than one big jar file serially. +all_test_packages=$(find libcore/*/src/test -name "*.java" | \ + fgrep -v junit | \ + fgrep -v org/w3c/domts | \ + xargs grep -h '^package ' | sed 's/^package //' | sed 's/;$//' | sort | uniq | tr "\n" " ") + +echo "Running tests for following test packages:" +echo $all_test_packages | tr " " "\n" + +$VOGAR \ + --vm-arg -Xmx32M \ + --classpath out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/javalib.jar \ + --classpath out/target/common/obj/JAVA_LIBRARIES/sqlite-jdbc_intermediates/classes.jar \ + $all_test_packages \ + tests.api.org.w3c.dom \ + || true diff --git a/support/src/test/java/libcore/net/http/HttpResponseCache.java b/support/src/test/java/libcore/net/http/HttpResponseCache.java deleted file mode 100644 index a551e27..0000000 --- a/support/src/test/java/libcore/net/http/HttpResponseCache.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package libcore.net.http; - -import java.io.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/tests/http/MockWebServer.java b/support/src/test/java/tests/http/MockWebServer.java index 2d8215c..a130dde 100644 --- a/support/src/test/java/tests/http/MockWebServer.java +++ b/support/src/test/java/tests/http/MockWebServer.java @@ -251,9 +251,7 @@ public final class MockWebServer { Socket socket; if (sslSocketFactory != null) { if (tunnelProxy) { - if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) { - throw new IllegalStateException("Tunnel without any CONNECT!"); - } + createTunnel(); } socket = sslSocketFactory.createSocket( raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); @@ -283,6 +281,22 @@ public final class MockWebServer { } /** + * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response + * is dispatched. + */ + private void createTunnel() throws IOException, InterruptedException { + while (true) { + MockResponse connect = responseQueue.peek(); + if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) { + throw new IllegalStateException("Tunnel without any CONNECT!"); + } + if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) { + return; + } + } + } + + /** * Reads a request and writes its response. Returns true if a request * was processed. */ diff --git a/support/src/test/java/tests/http/SocketPolicy.java b/support/src/test/java/tests/http/SocketPolicy.java index 549c65d..820b400 100644 --- a/support/src/test/java/tests/http/SocketPolicy.java +++ b/support/src/test/java/tests/http/SocketPolicy.java @@ -34,6 +34,12 @@ public enum SocketPolicy { DISCONNECT_AT_END, /** + * Wrap the socket with SSL at the completion of this request/response + * pair. Used for CONNECT messages to tunnel SSL over an HTTP proxy. + */ + UPGRADE_TO_SSL_AT_END, + + /** * Request immediate close of connection without even reading the * request. * diff --git a/support/src/test/java/tests/io/MockOs.java b/support/src/test/java/tests/io/MockOs.java new file mode 100644 index 0000000..0cfe836 --- /dev/null +++ b/support/src/test/java/tests/io/MockOs.java @@ -0,0 +1,120 @@ +/* + * 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 tests.io; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.Os; +import libcore.io.OsConstants; + +/** + * A mocking interceptor that wraps another {@link Os} to add faults. This can + * be useful to test otherwise hard-to-test scenarios such as a full disk. + */ +public final class MockOs { + private final InheritableThreadLocal<Map<String, Deque<InvocationHandler>>> handlers + = new InheritableThreadLocal<Map<String, Deque<InvocationHandler>>>() { + @Override protected Map<String, Deque<InvocationHandler>> initialValue() { + return new HashMap<String, Deque<InvocationHandler>>(); + } + }; + + private Os delegate; + private final InvocationHandler delegateHandler = new InvocationHandler() { + @Override public Object invoke(Object o, Method method, Object[] args) throws Throwable { + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + }; + + private final InvocationHandler invocationHandler = new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + InvocationHandler handler = getHandlers(method.getName()).poll(); + if (handler == null) { + handler = delegateHandler; + } + return handler.invoke(proxy, method, args); + } + }; + + private final Os mockOs = (Os) Proxy.newProxyInstance(MockOs.class.getClassLoader(), + new Class[] { Os.class }, invocationHandler); + + public void install() { + if (delegate != null) { + throw new IllegalStateException("MockOs already installed!"); + } + delegate = Libcore.os; + Libcore.os = mockOs; + } + + public void uninstall() { + if (delegate == null) { + throw new IllegalStateException("MockOs not installed!"); + } + Libcore.os = delegate; + } + + /** + * Returns the invocation handlers to handle upcoming invocations of + * {@code methodName}. If empty, calls will be handled by the delegate. + */ + public Deque<InvocationHandler> getHandlers(String methodName) { + Map<String, Deque<InvocationHandler>> threadFaults = handlers.get(); + Deque<InvocationHandler> result = threadFaults.get(methodName); + if (result == null) { + result = new ArrayDeque<InvocationHandler>(); + threadFaults.put(methodName, result); + } + return result; + } + + /** + * Enqueues the specified number of normal operations. Useful to delay + * faults. + */ + public void enqueueNormal(String methodName, int count) { + Deque<InvocationHandler> handlers = getHandlers(methodName); + for (int i = 0; i < count; i++) { + handlers.add(delegateHandler); + } + } + + public void enqueueFault(String methodName) { + enqueueFault(methodName, OsConstants.EIO); + } + + public void enqueueFault(String methodName, final int errno) { + getHandlers(methodName).add(new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) { + throw new ErrnoException(method.getName(), errno); + } + }); + } +} diff --git a/support/src/test/java/tests/net/StuckServer.java b/support/src/test/java/tests/net/StuckServer.java index 4230f17..eababce 100644 --- a/support/src/test/java/tests/net/StuckServer.java +++ b/support/src/test/java/tests/net/StuckServer.java @@ -40,6 +40,24 @@ public final class StuckServer { } } + public void unblockAfterMs(final int ms) { + Thread t = new Thread(new Runnable() { + @Override public void run() { + try { + Thread.sleep(ms); + for (Socket client : clients) { + client.close(); + } + clients.clear(); + clients.add(serverSocket.accept()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + t.start(); + } + public InetSocketAddress getLocalSocketAddress() { return (InetSocketAddress) serverSocket.getLocalSocketAddress(); } diff --git a/support/src/test/java/tests/support/Support_Configuration.java b/support/src/test/java/tests/support/Support_Configuration.java index fb5b468..fed0bc8 100644 --- a/support/src/test/java/tests/support/Support_Configuration.java +++ b/support/src/test/java/tests/support/Support_Configuration.java @@ -46,8 +46,6 @@ public class Support_Configuration { public static String HomeAddressSoftware = "Jetty(6.0.x)"; - public static String ProxyServerTestHost = "jcltest.apache.org"; - public static String SocksServerTestHost = "jcltest.apache.org"; public static int SocksServerTestPort = 1080; @@ -75,15 +73,7 @@ public class Support_Configuration { public static byte[] InetTestCaddr = { 9, 26, -56, -111 }; - public static final String HomeAddress6 = "jcltest6.apache.org"; - - public static String IPv6GlobalAddressJcl4 = "FE80:0000:0000:0000:020D:60FF:FE0F:A776%4"; // this - - public static String ProxyServerTestHostIPv6 = "jcltest6.apache.org"; - - public static String InetTestIP6 = "fe80::20d:60ff:fe24:7410"; - - public static String InetTestIP6LO = "::1"; + public static String IPv6GlobalAddressJcl4 = "2001:4860:8004::67"; // ipv6.google.com // ip address that resolves to a host that is not present on the local // network @@ -131,12 +121,6 @@ public class Support_Configuration { public static long URLConnectionDate = 929106872000L; - public static boolean RunCommTests = false; - - public static String Port1 = "COM1"; - - public static String Port2 = "COM2"; - static Hashtable<String, String> props = null; static { loadProperties(); @@ -149,9 +133,6 @@ public class Support_Configuration { Hashtable<String, String> props = new Hashtable<String, String>(); String iniName = System.getProperty("test.ini.file", "JCLAuto.ini"); - if (System.getProperty("test.comm") != null) { - RunCommTests = true; - } try { in = new FileInputStream(iniName); @@ -204,11 +185,6 @@ public class Support_Configuration { HomeAddressSoftware = value; } - value = props.get("ProxyServerTestHost"); - if (value != null) { - ProxyServerTestHost = value; - } - value = props.get("SocksServerTestHost"); if (value != null) { SocksServerTestHost = value; @@ -313,31 +289,6 @@ public class Support_Configuration { URLConnectionDate = Long.parseLong(value); } - value = props.get("Port1"); - if (value != null) { - Port1 = value; - } - - value = props.get("Port2"); - if (value != null) { - Port2 = value; - } - - value = props.get("InetTestIP6"); - if (value != null) { - InetTestIP6 = value; - } - - value = props.get("InetTestIP6LO"); - if (value != null) { - InetTestIP6LO = value; - } - - value = props.get("ProxyServerTestHostIPv6"); - if (value != null) { - ProxyServerTestHostIPv6 = value; - } - value = props.get("ResolvedNotExistingHost"); if (value != null) { ResolvedNotExistingHost = value; |