OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / libcore / luni / src / main / java / org / apache / harmony / luni / net / PlainSocketImpl.java
diff --git a/libcore/luni/src/main/java/org/apache/harmony/luni/net/PlainSocketImpl.java b/libcore/luni/src/main/java/org/apache/harmony/luni/net/PlainSocketImpl.java
new file mode 100644 (file)
index 0000000..2a36fee
--- /dev/null
@@ -0,0 +1,477 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT 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.net;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImpl;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import org.apache.harmony.luni.platform.INetworkSystem;
+import org.apache.harmony.luni.platform.Platform;
+
+/**
+ * A concrete connected-socket implementation.
+ */
+public class PlainSocketImpl extends SocketImpl {
+
+    // For SOCKS support. A SOCKS bind() uses the last
+    // host connected to in its request.
+    static private InetAddress lastConnectedAddress;
+
+    static private int lastConnectedPort;
+
+    private static Field fdField;
+
+    protected INetworkSystem netImpl = Platform.getNetworkSystem();
+
+    private boolean streaming = true;
+
+    private boolean shutdownInput;
+
+    private Proxy proxy;
+
+    public PlainSocketImpl(FileDescriptor fd) {
+        this.fd = fd;
+    }
+
+    public PlainSocketImpl(Proxy proxy) {
+        this(new FileDescriptor());
+        this.proxy = proxy;
+    }
+
+    public PlainSocketImpl() {
+        this(new FileDescriptor());
+    }
+
+    public PlainSocketImpl(FileDescriptor fd, int localport, InetAddress addr, int port) {
+        super();
+        this.fd = fd;
+        this.localport = localport;
+        this.address = addr;
+        this.port = port;
+    }
+
+    @Override
+    protected void accept(SocketImpl newImpl) throws IOException {
+        if (usingSocks()) {
+            ((PlainSocketImpl) newImpl).socksBind();
+            ((PlainSocketImpl) newImpl).socksAccept();
+            return;
+        }
+
+        try {
+            if (newImpl instanceof PlainSocketImpl) {
+                PlainSocketImpl newPlainSocketImpl = (PlainSocketImpl) newImpl;
+                netImpl.accept(fd, newImpl, newPlainSocketImpl.getFileDescriptor());
+            } else {
+                // if newImpl is not an instance of PlainSocketImpl, use
+                // reflection to get/set protected fields.
+                if (fdField == null) {
+                    fdField = getSocketImplField("fd");
+                }
+                FileDescriptor newFd = (FileDescriptor) fdField.get(newImpl);
+                netImpl.accept(fd, newImpl, newFd);
+            }
+        } catch (IllegalAccessException e) {
+            // empty
+        }
+    }
+
+    private boolean usingSocks() {
+        return proxy != null && proxy.type() == Proxy.Type.SOCKS;
+    }
+
+    /**
+     * gets SocketImpl field by reflection.
+     */
+    private Field getSocketImplField(final String fieldName) {
+        return AccessController.doPrivileged(new PrivilegedAction<Field>() {
+            public Field run() {
+                Field field = null;
+                try {
+                    field = SocketImpl.class.getDeclaredField(fieldName);
+                    field.setAccessible(true);
+                } catch (NoSuchFieldException e) {
+                    throw new Error(e);
+                }
+                return field;
+            }
+        });
+    }
+
+    public void initLocalPort(int localPort) {
+        this.localport = localPort;
+    }
+
+    public void initRemoteAddressAndPort(InetAddress remoteAddress, int remotePort) {
+        this.address = remoteAddress;
+        this.port = remotePort;
+    }
+
+    private void checkNotClosed() throws IOException {
+        if (!fd.valid()) {
+            throw new SocketException("Socket is closed");
+        }
+    }
+
+    @Override
+    protected synchronized int available() throws IOException {
+        checkNotClosed();
+        // we need to check if the input has been shutdown. If so
+        // we should return that there is no data to be read
+        if (shutdownInput) {
+            return 0;
+        }
+        return Platform.getFileSystem().ioctlAvailable(fd);
+    }
+
+    @Override
+    protected void bind(InetAddress address, int port) throws IOException {
+        netImpl.bind(fd, address, port);
+        this.address = address;
+        if (port != 0) {
+            this.localport = port;
+        } else {
+            this.localport = netImpl.getSocketLocalPort(fd);
+        }
+    }
+
+    @Override
+    protected void close() throws IOException {
+        synchronized (fd) {
+            if (fd.valid()) {
+                netImpl.close(fd);
+                fd = new FileDescriptor();
+            }
+        }
+    }
+
+    @Override
+    protected void connect(String aHost, int aPort) throws IOException {
+        connect(InetAddress.getByName(aHost), aPort);
+    }
+
+    @Override
+    protected void connect(InetAddress anAddr, int aPort) throws IOException {
+        connect(anAddr, aPort, 0);
+    }
+
+    /**
+     * Connects this socket to the specified remote host address/port.
+     *
+     * @param anAddr
+     *            the remote host address to connect to
+     * @param aPort
+     *            the remote port to connect to
+     * @param timeout
+     *            a timeout where supported. 0 means no timeout
+     * @throws IOException
+     *             if an error occurs while connecting
+     */
+    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 {
+                netImpl.connect(fd, normalAddr, aPort, timeout);
+            }
+        } catch (ConnectException e) {
+            throw new ConnectException(anAddr + ":" + aPort + " - " + e.getMessage());
+        }
+        super.address = normalAddr;
+        super.port = aPort;
+    }
+
+    @Override
+    protected void create(boolean streaming) throws IOException {
+        this.streaming = streaming;
+        netImpl.socket(fd, streaming);
+    }
+
+    @Override protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    protected synchronized InputStream getInputStream() throws IOException {
+        checkNotClosed();
+        return new SocketInputStream(this);
+    }
+
+    @Override
+    public Object getOption(int optID) throws SocketException {
+        return netImpl.getSocketOption(fd, optID);
+    }
+
+    @Override
+    protected synchronized OutputStream getOutputStream() throws IOException {
+        checkNotClosed();
+        return new SocketOutputStream(this);
+    }
+
+    @Override
+    protected void listen(int backlog) throws IOException {
+        if (usingSocks()) {
+            // Do nothing for a SOCKS connection. The listen occurs on the
+            // server during the bind.
+            return;
+        }
+        netImpl.listen(fd, backlog);
+    }
+
+    @Override
+    public void setOption(int optID, Object val) throws SocketException {
+        netImpl.setSocketOption(fd, optID, val);
+    }
+
+    /**
+     * Gets the SOCKS proxy server port.
+     */
+    private int socksGetServerPort() {
+        // get socks server port from proxy. It is unnecessary to check
+        // "socksProxyPort" property, since proxy setting should only be
+        // determined by ProxySelector.
+        InetSocketAddress addr = (InetSocketAddress) proxy.address();
+        return addr.getPort();
+
+    }
+
+    /**
+     * Gets the InetAddress of the SOCKS proxy server.
+     */
+    private InetAddress socksGetServerAddress() throws UnknownHostException {
+        String proxyName;
+        // get socks server address from proxy. It is unnecessary to check
+        // "socksProxyHost" property, since all proxy setting should be
+        // determined by ProxySelector.
+        InetSocketAddress addr = (InetSocketAddress) proxy.address();
+        proxyName = addr.getHostName();
+        if (null == proxyName) {
+            proxyName = addr.getAddress().getHostAddress();
+        }
+        return InetAddress.getByName(proxyName);
+    }
+
+    /**
+     * Connect using a SOCKS server.
+     */
+    private void socksConnect(InetAddress applicationServerAddress,
+            int applicationServerPort, int timeout) throws IOException {
+        try {
+            netImpl.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout);
+        } catch (Exception e) {
+            throw new SocketException("SOCKS connection failed: " + e);
+        }
+
+        socksRequestConnection(applicationServerAddress, applicationServerPort);
+
+        lastConnectedAddress = applicationServerAddress;
+        lastConnectedPort = applicationServerPort;
+    }
+
+    /**
+     * Request a SOCKS connection to the application server given. If the
+     * request fails to complete successfully, an exception is thrown.
+     */
+    private void socksRequestConnection(InetAddress applicationServerAddress,
+            int applicationServerPort) throws IOException {
+        socksSendRequest(Socks4Message.COMMAND_CONNECT,
+                applicationServerAddress, applicationServerPort);
+        Socks4Message reply = socksReadReply();
+        if (reply.getCommandOrResult() != Socks4Message.RETURN_SUCCESS) {
+            throw new IOException(reply.getErrorString(reply
+                    .getCommandOrResult()));
+        }
+    }
+
+    /**
+     * Perform an accept for a SOCKS bind.
+     */
+    public void socksAccept() throws IOException {
+        Socks4Message reply = socksReadReply();
+        if (reply.getCommandOrResult() != Socks4Message.RETURN_SUCCESS) {
+            throw new IOException(reply.getErrorString(reply
+                    .getCommandOrResult()));
+        }
+    }
+
+    /**
+     * Shutdown the input portion of the socket.
+     */
+    @Override
+    protected void shutdownInput() throws IOException {
+        shutdownInput = true;
+        netImpl.shutdownInput(fd);
+    }
+
+    /**
+     * Shutdown the output portion of the socket.
+     */
+    @Override
+    protected void shutdownOutput() throws IOException {
+        netImpl.shutdownOutput(fd);
+    }
+
+    /**
+     * Bind using a SOCKS server.
+     */
+    private void socksBind() throws IOException {
+        try {
+            netImpl.connect(fd, socksGetServerAddress(), socksGetServerPort(), 0);
+        } catch (Exception e) {
+            throw new IOException("Unable to connect to SOCKS server: " + e);
+        }
+
+        // There must be a connection to an application host for the bind to
+        // work.
+        if (lastConnectedAddress == null) {
+            throw new SocketException("Invalid SOCKS client");
+        }
+
+        // Use the last connected address and port in the bind request.
+        socksSendRequest(Socks4Message.COMMAND_BIND, lastConnectedAddress,
+                lastConnectedPort);
+        Socks4Message reply = socksReadReply();
+
+        if (reply.getCommandOrResult() != Socks4Message.RETURN_SUCCESS) {
+            throw new IOException(reply.getErrorString(reply
+                    .getCommandOrResult()));
+        }
+
+        // A peculiarity of socks 4 - if the address returned is 0, use the
+        // original socks server address.
+        if (reply.getIP() == 0) {
+            address = socksGetServerAddress();
+        } else {
+            // IPv6 support not yet required as
+            // currently the Socks4Message.getIP() only returns int,
+            // so only works with IPv4 4byte addresses
+            byte[] replyBytes = new byte[4];
+            intToBytes(reply.getIP(), replyBytes, 0);
+            address = InetAddress.getByAddress(replyBytes);
+        }
+        localport = reply.getPort();
+    }
+
+    private static void intToBytes(int value, byte[] bytes, int start) {
+        /*
+         * Shift the int so the current byte is right-most Use a byte mask of
+         * 255 to single out the last byte.
+         */
+        bytes[start] = (byte) ((value >> 24) & 255);
+        bytes[start + 1] = (byte) ((value >> 16) & 255);
+        bytes[start + 2] = (byte) ((value >> 8) & 255);
+        bytes[start + 3] = (byte) (value & 255);
+    }
+
+    /**
+     * Send a SOCKS V4 request.
+     */
+    private void socksSendRequest(int command, InetAddress address, int port)
+            throws IOException {
+        Socks4Message request = new Socks4Message();
+        request.setCommandOrResult(command);
+        request.setPort(port);
+        request.setIP(address.getAddress());
+        request.setUserId("default");
+
+        getOutputStream().write(request.getBytes(), 0, request.getLength());
+    }
+
+    /**
+     * Read a SOCKS V4 reply.
+     */
+    private Socks4Message socksReadReply() throws IOException {
+        Socks4Message reply = new Socks4Message();
+        int bytesRead = 0;
+        while (bytesRead < Socks4Message.REPLY_LENGTH) {
+            int count = getInputStream().read(reply.getBytes(), bytesRead,
+                    Socks4Message.REPLY_LENGTH - bytesRead);
+            if (-1 == count) {
+                break;
+            }
+            bytesRead += count;
+        }
+        if (Socks4Message.REPLY_LENGTH != bytesRead) {
+            throw new SocketException("Malformed reply from SOCKS server");
+        }
+        return reply;
+    }
+
+    @Override
+    protected void connect(SocketAddress remoteAddr, int timeout)
+            throws IOException {
+        InetSocketAddress inetAddr = (InetSocketAddress) remoteAddr;
+        connect(inetAddr.getAddress(), inetAddr.getPort(), timeout);
+    }
+
+    @Override
+    protected boolean supportsUrgentData() {
+        return true;
+    }
+
+    @Override
+    protected void sendUrgentData(int value) throws IOException {
+        netImpl.sendUrgentData(fd, (byte) value);
+    }
+
+    FileDescriptor getFD() {
+        return fd;
+    }
+
+    int read(byte[] buffer, int offset, int count) throws IOException {
+        if (shutdownInput) {
+            return -1;
+        }
+        int read = netImpl.read(fd, buffer, offset, count);
+        // Return of zero bytes for a blocking socket means a timeout occurred
+        if (read == 0) {
+            throw new SocketTimeoutException();
+        }
+        // Return of -1 indicates the peer was closed
+        if (read == -1) {
+            shutdownInput = true;
+        }
+        return read;
+    }
+
+    int write(byte[] buffer, int offset, int count) throws IOException {
+        if (streaming) {
+            return netImpl.write(fd, buffer, offset, count);
+        } else {
+            return netImpl.send(fd, buffer, offset, count, port, address);
+        }
+    }
+}