| /* |
| * 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 java.net; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.security.Permission; |
| import java.security.PermissionCollection; |
| |
| import org.apache.harmony.luni.util.Inet6Util; |
| import org.apache.harmony.luni.util.Msg; |
| |
| /** |
| * Regulates the access to network operations available through sockets through |
| * permissions. A permission consists of a target (a host), and an associated |
| * action list. The target should identify the host by either indicating the |
| * (possibly wildcarded (eg. {@code .company.com})) DNS style name of the host |
| * or its IP address in standard {@code nn.nn.nn.nn} ("dot") notation. The |
| * action list can be made up of one or more of the following actions separated |
| * by a comma: |
| * <dl> |
| * <dt>connect</dt> |
| * <dd>requests permission to connect to the host</dd> |
| * <dt>listen</dt> |
| * <dd>requests permission to listen for connections from the host</dd> |
| * <dt>accept</dt> |
| * <dd>requests permission to accept connections from the host</dd> |
| * <dt>resolve</dt> |
| * <dd>requests permission to resolve the hostname</dd> |
| * </dl> |
| * Note that {@code resolve} is implied when any (or none) of the others are |
| * present. |
| * <p> |
| * Access to a particular port can be requested by appending a colon and a |
| * single digit to the name (eg. {@code .company.com:7000}). A range of port |
| * numbers can also be specified, by appending a pattern of the form |
| * <i>LOW-HIGH</i> where <i>LOW</i> and <i>HIGH</i> are valid port numbers. If |
| * either <i>LOW</i> or <i>HIGH</i> is omitted it is equivalent to entering the |
| * lowest or highest possible value respectively. For example: |
| * |
| * <pre> |
| * {@code SocketPermission("www.company.com:7000-", "connect,accept")} |
| * </pre> |
| * |
| * represents the permission to connect to and accept connections from {@code |
| * www.company.com} on ports in the range {@code 7000} to {@code 65535}. |
| */ |
| public final class SocketPermission extends Permission implements Serializable { |
| |
| private static final long serialVersionUID = -7204263841984476862L; |
| |
| // Bit masks for each of the possible actions |
| static final int SP_CONNECT = 1; |
| |
| static final int SP_LISTEN = 2; |
| |
| static final int SP_ACCEPT = 4; |
| |
| static final int SP_RESOLVE = 8; |
| |
| // list of actions permitted for socket permission in order, indexed by mask |
| // value |
| @SuppressWarnings("nls") |
| private static final String[] actionNames = { "", "connect", "listen", "", |
| "accept", "", "", "", "resolve" }; |
| |
| // If a wildcard is present store the information |
| private transient boolean isPartialWild; |
| |
| private transient boolean isWild; |
| |
| // The highest port number |
| private static final int HIGHEST_PORT = 65535; |
| |
| // The lowest port number |
| private static final int LOWEST_PORT = 0; |
| |
| transient String hostName; // Host name as returned by InetAddress |
| |
| transient String ipString; // IP address as returned by InetAddress |
| |
| transient boolean resolved; // IP address has been resolved |
| |
| // the port range; |
| transient int portMin = LOWEST_PORT; |
| |
| transient int portMax = HIGHEST_PORT; |
| |
| private String actions; // List of all actions allowed by this permission |
| |
| transient int actionsMask = SP_RESOLVE; |
| |
| /** |
| * Constructs a new {@code SocketPermission} instance. The hostname can be a |
| * DNS name, an individual hostname, an IP address or the empty string which |
| * implies {@code localhost}. The port or port range is optional. |
| * <p> |
| * The action list is a comma-separated list which can consists of the |
| * possible operations {@code "connect"}, {@code "listen"}, {@code "accept"} |
| * , and {@code "resolve"}. They are case-insensitive and can be put |
| * together in any order. {@code "resolve"} is implied per default. |
| * |
| * @param host |
| * the hostname this permission is valid for. |
| * @param action |
| * the action string of this permission. |
| */ |
| public SocketPermission(String host, String action) { |
| super(host.equals("") ? "localhost" : host); //$NON-NLS-1$ //$NON-NLS-2$ |
| hostName = getHostString(host); |
| if (action == null) { |
| throw new NullPointerException(); |
| } |
| if (action.equals("")) { //$NON-NLS-1$ |
| throw new IllegalArgumentException(); |
| } |
| |
| setActions(action); |
| actions = toCanonicalActionString(action); |
| // Use host since we are only checking for port presence |
| parsePort(host, hostName); |
| } |
| |
| /** |
| * Compares the argument {@code o} to this instance and returns {@code true} |
| * if they represent the same permission using a class specific comparison. |
| * |
| * @param other |
| * the object to compare with this {@code SocketPermission} |
| * instance. |
| * @return {@code true} if they represent the same permission, {@code false} |
| * otherwise. |
| * @see #hashCode |
| */ |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (other == null || this.getClass() != other.getClass()) { |
| return false; |
| } |
| SocketPermission sp = (SocketPermission) other; |
| if (!hostName.equalsIgnoreCase(sp.hostName)) { |
| if (getIPString(true) == null || !ipString.equalsIgnoreCase(sp.getIPString(true))) { |
| return false; |
| } |
| } |
| if (this.actionsMask != SP_RESOLVE) { |
| if (this.portMin != sp.portMin) { |
| return false; |
| } |
| if (this.portMax != sp.portMax) { |
| return false; |
| } |
| } |
| return this.actionsMask == sp.actionsMask; |
| } |
| |
| /** |
| * Returns the hash value for this {@code SocketPermission} instance. Any |
| * two objects which returns {@code true} when passed to {@code equals()} |
| * must return the same value as a result of this method. |
| * |
| * @return the hashcode value for this instance. |
| * @see #equals |
| */ |
| @Override |
| public int hashCode() { |
| return hostName.hashCode() ^ actionsMask ^ portMin ^ portMax; |
| } |
| |
| /** |
| * Gets a comma-separated list of all actions allowed by this permission. If |
| * more than one action is returned they follow this order: {@code connect}, |
| * {@code listen}, {@code accept}, {@code resolve}. |
| * |
| * @return the comma-separated action list. |
| */ |
| @Override |
| public String getActions() { |
| return actions; |
| } |
| |
| /** |
| * Stores the actions for this permission as a bit field. |
| * |
| * @param actions |
| * java.lang.String the action list |
| */ |
| private void setActions(String actions) throws IllegalArgumentException { |
| if (actions.equals("")) { //$NON-NLS-1$ |
| return; |
| } |
| boolean parsing = true; |
| String action; |
| StringBuilder sb = new StringBuilder(); |
| int pos = 0, length = actions.length(); |
| while (parsing) { |
| char c; |
| sb.setLength(0); |
| while (pos < length && (c = actions.charAt(pos++)) != ',') { |
| sb.append(c); |
| } |
| if (pos == length) { |
| parsing = false; |
| } |
| action = sb.toString().trim().toLowerCase(); |
| if (action.equals(actionNames[SP_CONNECT])) { |
| actionsMask |= SP_CONNECT; |
| } else if (action.equals(actionNames[SP_LISTEN])) { |
| actionsMask |= SP_LISTEN; |
| } else if (action.equals(actionNames[SP_ACCEPT])) { |
| actionsMask |= SP_ACCEPT; |
| } else if (action.equals(actionNames[SP_RESOLVE])) { |
| // do nothing |
| } else { |
| throw new IllegalArgumentException(Msg.getString("K0048", //$NON-NLS-1$ |
| action)); |
| } |
| } |
| } |
| |
| /** |
| * Checks whether this {@code SocketPermission} instance allows all actions |
| * which are allowed by the given permission object {@code p}. All argument |
| * permission actions, hosts and ports must be implied by this permission |
| * instance in order to return {@code true}. This permission may imply |
| * additional actions not present in the argument permission. |
| * |
| * @param p |
| * the socket permission which has to be implied by this |
| * instance. |
| * @return {@code true} if this permission instance implies all permissions |
| * represented by {@code p}, {@code false} otherwise. |
| */ |
| @Override |
| public boolean implies(Permission p) { |
| SocketPermission sp; |
| try { |
| sp = (SocketPermission) p; |
| } catch (ClassCastException e) { |
| return false; |
| } |
| |
| // tests if the action list of p is the subset of the one of the |
| // receiver |
| if (sp == null || (actionsMask & sp.actionsMask) != sp.actionsMask) { |
| return false; |
| } |
| |
| // only check the port range if the action string of the current object |
| // is not "resolve" |
| if (!p.getActions().equals("resolve")) { //$NON-NLS-1$ |
| if ((sp.portMin < this.portMin) || (sp.portMax > this.portMax)) { |
| return false; |
| } |
| } |
| |
| // Verify the host is valid |
| return checkHost(sp); |
| } |
| |
| /** |
| * Creates a new {@code PermissionCollection} to store {@code |
| * SocketPermission} objects. |
| * |
| * @return the new permission collection. |
| */ |
| @Override |
| public PermissionCollection newPermissionCollection() { |
| return new SocketPermissionCollection(); |
| } |
| |
| /** |
| * Parse the port, including the minPort, maxPort |
| * @param hostPort the host[:port] one |
| * @param host the host name we just get |
| * @throws IllegalArgumentException If the port is not a positive number or minPort |
| * is not less than or equal maxPort |
| */ |
| private void parsePort(String hostPort, String host) throws IllegalArgumentException { |
| String port = hostPort.substring(host.length()); |
| String emptyString = ""; //$NON-NLS-1$ |
| |
| if (emptyString.equals(port)) { |
| // Not specified |
| portMin = 80; |
| portMax = 80; |
| return; |
| } |
| |
| if (":*".equals(port)) { |
| // The port range should be 0-65535 |
| portMin = 0; |
| portMax = 65535; |
| return; |
| } |
| |
| // Omit ':' |
| port = port.substring(1); |
| int negIdx = port.indexOf('-'); |
| String strPortMin = emptyString; |
| String strPortMax = emptyString; |
| if (-1 == negIdx) { |
| // No neg mark, only one number |
| strPortMin = port; |
| strPortMax = port; |
| } else { |
| strPortMin = port.substring(0, negIdx); |
| strPortMax = port.substring(negIdx + 1); |
| if (emptyString.equals(strPortMin)) { |
| strPortMin = "0"; |
| } |
| if (emptyString.equals(strPortMax)) { |
| strPortMax = "65535"; |
| } |
| } |
| try { |
| portMin = Integer.valueOf(strPortMin).intValue(); |
| portMax = Integer.valueOf(strPortMax).intValue(); |
| |
| if (portMin > portMax) { |
| // K0049=MinPort is greater than MaxPort\: {0} |
| throw new IllegalArgumentException(Msg.getString("K0049", port)); //$NON-NLS-1$ |
| } |
| } catch (NumberFormatException e) { |
| // K004a=Invalid port number specified\: {0} |
| throw new IllegalArgumentException(Msg.getString("K004a", port)); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Creates a canonical action list. |
| * |
| * @param action |
| * java.lang.String |
| * |
| * @return java.lang.String |
| */ |
| private String toCanonicalActionString(String action) { |
| if (action == null || action.equals("") || actionsMask == SP_RESOLVE) { //$NON-NLS-1$ |
| return actionNames[SP_RESOLVE]; // If none specified return the |
| } |
| // implied action resolve |
| StringBuilder sb = new StringBuilder(); |
| if ((actionsMask & SP_CONNECT) == SP_CONNECT) { |
| sb.append(','); |
| sb.append(actionNames[SP_CONNECT]); |
| } |
| if ((actionsMask & SP_LISTEN) == SP_LISTEN) { |
| sb.append(','); |
| sb.append(actionNames[SP_LISTEN]); |
| } |
| if ((actionsMask & SP_ACCEPT) == SP_ACCEPT) { |
| sb.append(','); |
| sb.append(actionNames[SP_ACCEPT]); |
| } |
| sb.append(','); |
| sb.append(actionNames[SP_RESOLVE]);// Resolve is always implied |
| // Don't copy the first ','. |
| return actions = sb.substring(1, sb.length()); |
| } |
| |
| private String getIPString(boolean isCheck) { |
| if (!resolved) { |
| try { |
| ipString = InetAddress.getHostNameInternal(hostName, isCheck); |
| } catch (UnknownHostException e) { |
| // ignore |
| } |
| resolved = true; |
| } |
| return ipString; |
| } |
| |
| /** |
| * Get the host part from the host[:port] one. The host should be |
| * |
| * <pre> |
| * host = (hostname | IPv4address | IPv6reference | IPv6 in full uncompressed form) |
| * </pre> |
| * |
| * The wildcard "*" may be included once in a DNS name host specification. |
| * If it is included, it must be in the leftmost position |
| * |
| * @param host |
| * the {@code host[:port]} string. |
| * @return the host name. |
| * @throws IllegalArgumentException |
| * if the host is invalid. |
| */ |
| private String getHostString(String host) throws IllegalArgumentException { |
| host = host.trim(); |
| int idx = -1; |
| idx = host.indexOf(':'); |
| isPartialWild = (host.length() > 0 && host.charAt(0) == '*'); |
| if (isPartialWild) { |
| resolved = true; |
| isWild = (host.length() == 1); |
| if (isWild) { |
| return host; |
| } |
| if (idx > -1) { |
| host = host.substring(0, idx); |
| } |
| return host.toLowerCase(); |
| } |
| |
| int lastIdx = host.lastIndexOf(':'); |
| |
| if (idx == lastIdx) { |
| if (-1 != idx) { |
| // only one colon, should be port |
| host = host.substring(0, idx); |
| } |
| return host.toLowerCase(); |
| } |
| // maybe ipv6 |
| boolean isFirstBracket = (host.charAt(0) == '['); |
| if (!isFirstBracket) { |
| // No bracket, should be in full form |
| int colonNum = 0; |
| for (int i = 0; i < host.length(); ++i) { |
| if (host.charAt(i) == ':') { |
| colonNum++; |
| } |
| } |
| // Get rid of the colon before port |
| if (8 == colonNum) { |
| host = host.substring(0, lastIdx); |
| } |
| if (Inet6Util.isIP6AddressInFullForm(host)) { |
| return host.toLowerCase(); |
| } |
| // K004a=Invalid port number specified\: {0} |
| throw new IllegalArgumentException(Msg.getString("K004a", host)); |
| } |
| // forward bracket found |
| int bbracketIdx = host.indexOf(']'); |
| if (-1 == bbracketIdx) { |
| // no back bracket found, wrong |
| // K004a=Invalid port number specified\: {0} |
| throw new IllegalArgumentException(Msg.getString("K004a", host)); |
| } |
| host = host.substring(0, bbracketIdx + 1); |
| if (Inet6Util.isValidIP6Address(host)) { |
| return host.toLowerCase(); |
| } |
| // K004a=Invalid port number specified\: {0} |
| throw new IllegalArgumentException(Msg.getString("K004a", host)); |
| } |
| |
| /** |
| * Determines whether or not this permission could refer to the same host as |
| * sp. |
| */ |
| boolean checkHost(SocketPermission sp) { |
| if (isPartialWild) { |
| if (isWild) { |
| return true; // Match on any host |
| } |
| int length = hostName.length() - 1; |
| return sp.hostName.regionMatches(sp.hostName.length() - length, |
| hostName, 1, length); |
| } |
| // The ipString may not be the same, some hosts resolve to |
| // multiple ips |
| return (getIPString(false) != null && ipString.equals(sp.getIPString(false))) |
| || hostName.equals(sp.hostName); |
| } |
| |
| private void writeObject(ObjectOutputStream stream) throws IOException { |
| stream.defaultWriteObject(); |
| } |
| |
| private void readObject(ObjectInputStream stream) throws IOException, |
| ClassNotFoundException { |
| stream.defaultReadObject(); |
| // Initialize locals |
| isPartialWild = false; |
| isWild = false; |
| portMin = LOWEST_PORT; |
| portMax = HIGHEST_PORT; |
| actionsMask = SP_RESOLVE; |
| hostName = getHostString(getName()); |
| parsePort(getName(), hostName); |
| setActions(actions); |
| } |
| } |