/* * %W% %E% * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ package com.sun.cdc.io.j2me.socket; import java.io.*; import java.net.*; import javax.microedition.io.*; import com.sun.cdc.io.j2me.*; import com.sun.cdc.io.*; /** * GenericStreamConnection to the J2SE socket API. * * @author Nik Shaylor * @version 1.0 10/08/99 */ public class Protocol extends ConnectionBase implements StreamConnection, SocketConnection { /** Socket object */ Socket socket; /** Open count */ int opens = 0; /** IPv6 address */ boolean ipv6 = false; /** * Open the connection */ public void open(String name, int mode, boolean timeouts) throws IOException { throw new RuntimeException("Should not be called"); } /* * Check permission to connect to the indicated host. * This should be overriden by the MIDP protocol handler * to check the proper MIDP permission. */ protected void checkPermission(String host, int port) { // Check for SecurityManager.checkConnect() java.lang.SecurityManager sm = System.getSecurityManager(); if (sm != null){ if (host != null) { sm.checkConnect(host, port); } else { sm.checkConnect("localhost", port); } } return; } /* * Check permission when opening an OutputStream. MIDP * versions of the protocol handler should override this * with an empty method. Throw a SecurityException if * the connection is not allowed. Currently the socket * protocol handler does not make a permission check at * this point so this method is empty. */ protected void outputStreamPermissionCheck() { return; } /* * Check permission when opening an InputStream. MIDP * versions of the protocol handler should override this * with an empty method. A SecurityException will be * raised if the connection is not allowed. Currently the * socket protocol handler does not make a permission * check at this point so this method is empty. */ protected void inputStreamPermissionCheck() { return; } /* * Creates a server socket. MIDP * versions of the protocol handler should override this * method. */ protected com.sun.cdc.io.j2me.serversocket.Protocol createServerSocket() { return new com.sun.cdc.io.j2me.serversocket.Protocol(); } /** * Open the connection * @param name the target for the connection * @param writeable a flag that is true if the caller expects to write to the * connection. * @param timeouts A flag to indicate that the called wants timeout exceptions * <p> * The name string for this protocol should be: * "<name or IP number>:<port number> */ public Connection openPrim(String name, int mode, boolean timeouts) throws IOException { if(name.charAt(0) != '/' || name.charAt(1) != '/') { throw new IllegalArgumentException("Protocol must start with \"//\" "+name); } name = name.substring(2); try { /* Host name or IP number */ String nameOrIP = ""; /* Port number */ final int port; /* Look for the : */ int colon = name.indexOf(':'); if(colon != -1) { /* Strip off the protocol name */ nameOrIP = parseHostName(name, colon); /* Host name should not contain reserved characters specified in RFC 2396 */ if ((nameOrIP.indexOf('/') != -1) || (nameOrIP.indexOf('@') != -1) || (nameOrIP.indexOf('?') != -1) || (nameOrIP.indexOf(';') != -1)) { throw new IllegalArgumentException("hostname " + nameOrIP + " cannot contain \"?\" , \"@\" , \";\", \":\", or \"/\" character."); } } if(nameOrIP.length() == 0) { /* * If the open string is "socket://:nnnn" then we regard this as * "serversocket://:nnnn" */ /* socket:// and socket://: are also valid serversocket urls */ com.sun.cdc.io.j2me.serversocket.Protocol con = createServerSocket(); con.open("//"+name, mode, timeouts); return con; } /* Get the port number */ if (ipv6) { int lastColon = name.lastIndexOf(":"); port = Integer.parseInt(name.substring(lastColon+1)); } else { port = Integer.parseInt(name.substring(colon+1)); } /* If the proper security check has not been made, make * it now. */ checkPermission(nameOrIP, port); /* Open the socket. Use a doPrivileged block * to avoid excessive prompting. */ final String hostName = nameOrIP; socket = (Socket)java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction() { public Object run() throws java.security.PrivilegedActionException, IOException { return new Socket(hostName, port); } }); opens++; return this; } catch(NumberFormatException x) { throw new IllegalArgumentException("Invalid port number in "+name); } catch(java.security.PrivilegedActionException pae) { IOException ioe = (IOException)pae.getException(); throw ioe; } } /** * Open the connection * @param socket an already formed socket * <p> * This function is only used by com.sun.kjava.system.palm.protocol.socketserver; */ public void open(Socket socket) throws IOException { this.socket = socket; } private String parseHostName(String connection, int colon) { /* IPv6 addresses are enclosed within [] */ int beginIndex = connection.indexOf("["); int endIndex = connection.indexOf("]"); if (beginIndex > endIndex) { throw new IllegalArgumentException("invalid host name " + connection); } if ((beginIndex ==0) && (endIndex >0)) { return parseIPv6Address(connection, endIndex); } else { return parseIPv4Address(connection, colon); } } private String parseIPv4Address(String name, int colon) { return name.substring(0, colon); } private String parseIPv6Address(String address, int closing) { ipv6 = true; /* beginning '[' and closing ']' should be included in the hostname*/ return address.substring(0, closing+1); } /** * Returns an input stream for this socket. * * @return an input stream for reading bytes from this socket. * @exception IOException if an I/O error occurs when creating the * input stream. */ public InputStream openInputStream() throws IOException { inputStreamPermissionCheck(); InputStream is = new UniversalFilterInputStream(this, socket.getInputStream()); opens++; return is; } /** * Returns an output stream for this socket. * * @return an output stream for writing bytes to this socket. * @exception IOException if an I/O error occurs when creating the * output stream. */ public OutputStream openOutputStream() throws IOException { outputStreamPermissionCheck(); OutputStream os = new UniversalFilterOutputStream(this, socket.getOutputStream()); opens++; return os; } /** * Close the connection. * * @exception IOException if an I/O error occurs when closing the * connection. */ public void close() throws IOException { if(--opens == 0) { socket.close(); } } /** * Set a socket option for the connection. * <P> * Options inform the low level networking code about intended * usage patterns that the application will use in dealing with * the socket connection. * </P> * <P> * Calling <code>setSocketOption</code> to assign buffer sizes * is a hint to the platform of the sizes to set the underlying * network I/O buffers. * Calling <code>getSocketOption</code> can be used to see what * sizes the system is using. * The system MAY adjust the buffer sizes to account for * better throughput available from Maximum Transmission Unit * (MTU) and Maximum Segment Size (MSS) data available * from current network information. * </P> * * @param option socket option identifier (KEEPALIVE, LINGER, * SNDBUF, RCVBUF, or DELAY) * @param value numeric value for specified option * @exception IllegalArgumentException if the value is not * valid (e.g. negative value) or if the option * identifier is not valid * @exception IOException if the connection was closed * * @see #getSocketOption */ public void setSocketOption(byte option, int value) throws IllegalArgumentException, IOException { if (socket.isClosed()) { throw new IOException("Socket is closed"); } /* value can never be negative */ if (value < 0) { throw new IllegalArgumentException("Value cannot be negative"); } switch (option) { case SocketConnection.DELAY: socket.setTcpNoDelay(value != 0); break; case SocketConnection.KEEPALIVE: socket.setKeepAlive(value != 0); break; case SocketConnection.LINGER: socket.setSoLinger(value != 0, value); break; case SocketConnection.RCVBUF: if (value > 0) { socket.setReceiveBufferSize(value); } break; case SocketConnection.SNDBUF: if (value > 0) { socket.setSendBufferSize(value); } break; default: throw new IllegalArgumentException("Option identifier is out of range"); } } /** * Get a socket option for the connection. * * @param option socket option identifier (KEEPALIVE, LINGER, * SNDBUF, RCVBUF, or DELAY) * @return numeric value for specified option or -1 if the * value is not available. * @exception IllegalArgumentException if the option identifier is * not valid * @exception IOException if the connection was closed * @see #setSocketOption */ public int getSocketOption(byte option) throws IllegalArgumentException, IOException { if (socket.isClosed()) { throw new IOException("Socket is closed"); } switch (option) { case SocketConnection.DELAY: return (socket.getTcpNoDelay()) ? 1 : 0 ; case SocketConnection.KEEPALIVE: return (socket.getKeepAlive()) ? 1 : 0 ; case SocketConnection.LINGER: return socket.getSoLinger(); case SocketConnection.RCVBUF: return socket.getReceiveBufferSize(); case SocketConnection.SNDBUF: return socket.getSendBufferSize(); default: throw new IllegalArgumentException("Option identifier is out of range"); } } /** * Gets the local address to which the socket is bound. * * <P>The host address(IP number) that can be used to connect to this * end of the socket connection from an external system. * Since IP addresses may be dynamically assigned, a remote application * will need to be robust in the face of IP number reassignment.</P> * <P> The local hostname (if available) can be accessed from * <code> System.getProperty("microedition.hostname")</code> * </P> * * @return the local address to which the socket is bound. * @exception IOException if the connection was closed. * @see ServerSocketConnection */ public String getLocalAddress() throws IOException { if (socket.isClosed()) { throw new IOException("Socket is closed"); } return socket.getLocalAddress().getHostName(); } /** * Returns the local port to which this socket is bound. * * @return the local port number to which this socket is connected. * @exception IOException if the connection was closed. * @see ServerSocketConnection */ public int getLocalPort() throws IOException { if (socket.isClosed()) { throw new IOException("Socket is closed"); } return socket.getLocalPort(); } /** * Gets the remote address to which the socket is bound. * The address can be either the remote host name or the IP * address(if available). * * @return the remote address to which the socket is bound. * @exception IOException if the connection was closed. */ public String getAddress() throws IOException { if (socket.isClosed()) { throw new IOException("Socket is closed"); } return socket.getInetAddress().getHostName(); } /** * Returns the remote port to which this socket is bound. * * @return the remote port number to which this socket is connected. * @exception IOException if the connection was closed. */ public int getPort() throws IOException { if (socket.isClosed()) { throw new IOException("Socket is closed"); } return socket.getPort(); } }