# HG changeset patch # User michaelm # Date 1381784955 -3600 # Node ID ab071ce903684ecf8b6cbb19cd6876491d9b6485 # Parent c142f19c71813667a91da8b6687a2907cfc83be3 8014719: HttpClient/ProxyTest.java failing with IAE HttpURLPermission.parseURI Reviewed-by: alanb, chegar diff -r c142f19c7181 -r ab071ce90368 jdk/src/share/classes/java/net/HostPortrange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/net/HostPortrange.java Mon Oct 14 22:09:15 2013 +0100 @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 in the LICENSE file that + * accompanied this code). + * + * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +import java.net.*; +import java.util.Formatter; +import java.util.Locale; +import sun.net.util.IPAddressUtil; + +/** + * Parses a string containing a host/domain name and port range + */ +class HostPortrange { + + String hostname; + String scheme; + int[] portrange; + + boolean wildcard; + boolean literal; + boolean ipv6, ipv4; + static final int PORT_MIN = 0; + static final int PORT_MAX = (1 << 16) -1; + + boolean equals(HostPortrange that) { + return this.hostname.equals(that.hostname) + && this.portrange[0] == that.portrange[0] + && this.portrange[1] == that.portrange[1] + && this.wildcard == that.wildcard + && this.literal == that.literal; + } + + public int hashCode() { + return hostname.hashCode() + portrange[0] + portrange[1]; + } + + HostPortrange(String scheme, String str) { + // Parse the host name. A name has up to three components, the + // hostname, a port number, or two numbers representing a port + // range. "www.sun.com:8080-9090" is a valid host name. + + // With IPv6 an address can be 2010:836B:4179::836B:4179 + // An IPv6 address needs to be enclose in [] + // For ex: [2010:836B:4179::836B:4179]:8080-9090 + // Refer to RFC 2732 for more information. + + // first separate string into two fields: hoststr, portstr + String hoststr, portstr = null; + this.scheme = scheme; + + // check for IPv6 address + if (str.charAt(0) == '[') { + ipv6 = literal = true; + int rb = str.indexOf(']'); + if (rb != -1) { + hoststr = str.substring(1, rb); + } else { + throw new IllegalArgumentException("invalid IPv6 address: " + str); + } + int sep = str.indexOf(':', rb + 1); + if (sep != -1 && str.length() > sep) { + portstr = str.substring(sep + 1); + } + // need to normalize hoststr now + byte[] ip = IPAddressUtil.textToNumericFormatV6(hoststr); + if (ip == null) { + throw new IllegalArgumentException("illegal IPv6 address"); + } + StringBuilder sb = new StringBuilder(); + Formatter formatter = new Formatter(sb, Locale.US); + formatter.format("%02x%02x:%02x%02x:%02x%02x:%02x" + + "%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], + ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]); + hostname = sb.toString(); + } else { + // not IPv6 therefore ':' is the port separator + + int sep = str.indexOf(':'); + if (sep != -1 && str.length() > sep) { + hoststr = str.substring(0, sep); + portstr = str.substring(sep + 1); + } else { + hoststr = sep == -1 ? str : str.substring(0, sep); + } + // is this a domain wildcard specification? + if (hoststr.lastIndexOf('*') > 0) { + throw new IllegalArgumentException("invalid host wildcard specification"); + } else if (hoststr.startsWith("*")) { + wildcard = true; + if (hoststr.equals("*")) { + hoststr = ""; + } else if (hoststr.startsWith("*.")) { + hoststr = hoststr.substring(1).toLowerCase(); // leave the '.' ? + } else { + throw new IllegalArgumentException("invalid host wildcard specification"); + } + } else { + // check if ipv4 (if rightmost label a number) + // The normal way to specify ipv4 is 4 decimal labels + // but actually three, two or single label formats valid also + // So, we recognise ipv4 by just testing the rightmost label + // being a number. + int lastdot = hoststr.lastIndexOf('.'); + if (lastdot != -1 && (hoststr.length() > 1)) { + boolean ipv4 = true; + + for (int i = lastdot + 1, len = hoststr.length(); i < len; i++) { + char c = hoststr.charAt(i); + if (c < '0' || c > '9') { + ipv4 = false; + break; + } + } + this.ipv4 = this.literal = ipv4; + if (ipv4) { + byte[] ip = IPAddressUtil.textToNumericFormatV4(hoststr); + if (ip == null) { + throw new IllegalArgumentException("illegal IPv4 address"); + } + StringBuilder sb = new StringBuilder(); + Formatter formatter = new Formatter(sb, Locale.US); + formatter.format("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + hoststr = sb.toString(); + } else { + // regular domain name + hoststr = hoststr.toLowerCase(); + } + } + } + hostname = hoststr; + } + + try { + portrange = parsePort(portstr); + } catch (Exception e) { + throw new IllegalArgumentException("invalid port range: " + portstr); + } + } + + public boolean literal() { + return literal; + } + + public boolean ipv4Literal() { + return ipv4; + } + + public boolean ipv6Literal() { + return ipv6; + } + + public String hostname() { + return hostname; + } + + public int[] portrange() { + return portrange; + } + + /** + * returns true if the hostname part started with * + * hostname returns the remaining part of the host component + * eg "*.foo.com" -> ".foo.com" or "*" -> "" + * + * @return + */ + public boolean wildcard() { + return wildcard; + } + + // these shouldn't leak outside the implementation + final static int[] HTTP_PORT = {80, 80}; + final static int[] HTTPS_PORT = {443, 443}; + final static int[] NO_PORT = {-1, -1}; + + int[] defaultPort() { + if (scheme.equals("http")) { + return HTTP_PORT; + } else if (scheme.equals("https")) { + return HTTPS_PORT; + } + return NO_PORT; + } + + int[] parsePort(String port) + { + + if (port == null || port.equals("")) { + return defaultPort(); + } + + if (port.equals("*")) { + return new int[] {PORT_MIN, PORT_MAX}; + } + + try { + int dash = port.indexOf('-'); + + if (dash == -1) { + int p = Integer.parseInt(port); + return new int[] {p, p}; + } else { + String low = port.substring(0, dash); + String high = port.substring(dash+1); + int l,h; + + if (low.equals("")) { + l = PORT_MIN; + } else { + l = Integer.parseInt(low); + } + + if (high.equals("")) { + h = PORT_MAX; + } else { + h = Integer.parseInt(high); + } + if (l < 0 || h < 0 || h
  • a "connect" {@link SocketPermission} to the host/port combination of the * destination URL or
  • - *
  • a {@link HttpURLPermission} that permits this request.
  • + *
  • a {@link URLPermission} that permits this request.
  • *

    * If automatic redirection is enabled, and this request is redirected to another * destination, then the caller must also have permission to connect to the diff -r c142f19c7181 -r ab071ce90368 jdk/src/share/classes/java/net/HttpURLPermission.java --- a/jdk/src/share/classes/java/net/HttpURLPermission.java Mon Oct 14 09:52:36 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,408 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 in the LICENSE file that - * accompanied this code). - * - * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.net; - -import java.io.ObjectInputStream; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; -import java.util.Collections; -import java.security.Permission; - -/** - * Represents permission to access a resource or set of resources defined by a - * given http or https url, and for a given set of user-settable request methods - * and request headers. The name of the permission is the url string. - * The actions string is a concatenation of the request methods and headers. - * The range of method and header names is not restricted by this class. - *

    The url

    - * The url string is also used to instantiate a {@link URI} object which is - * used for comparison with other HttpURLPermission instances. Therefore, any - * references in this specification to url, mean this URI object. - * The path component of the url comprises a sequence of path segments, separated - * by '/' characters. The path is specified in a similar way to the path - * in {@link java.io.FilePermission}. There are three different ways - * as the following examples show: - * - * - * - * - * - * - * - * - * - * - * - * - *
    URL Examples
    Example urlDescription
    http://www.oracle.com/a/b/c.htmlA url which identifies a specific (single) resource
    http://www.oracle.com/a/b/*The '*' character refers to all resources in the same "directory" - in - * other words all resources with the same number of path components, and - * which only differ in the final path component, represented by the '*'. - *
    http://www.oracle.com/a/b/-The '-' character refers to all resources recursively below the - * preceding path (eg. http://www.oracle.com/a/b/c/d/e.html matches this - * example). - *
    - *

    - * The '*' and '-' may only be specified in the final segment of a path and must be - * the only character in that segment. Any query or fragment components of the - * url are ignored when constructing HttpURLPermissions. - *

    - * As a special case, urls of the form, "http:*" or "https:*" are accepted to - * mean any url of the given scheme. - *

    The actions string

    - * The actions string of a HttpURLPermission is a concatenation of the method list - * and the request headers list. These are lists of the permitted HTTP request - * methods and permitted request headers of the permission (respectively). The two lists - * are separated by a colon ':' character and elements of each list are comma separated. - * Some examples are: - *

    - *         "POST,GET,DELETE"
    - *         "GET:X-Foo-Request,X-Bar-Request"
    - *         "POST,GET:Header1,Header2"
    - * 
    - * The first example specifies the methods: POST, GET and DELETE, but no request headers. - * The second example specifies one request method and two headers. The third - * example specifies two request methods, and two headers. - *

    - * The colon separator need not be present if the request headers list is empty. - * No white-space is permitted in the actions string. The action strings supplied to - * the HttpURLPermission constructors are case-insensitive and are normalized by converting - * method names to upper-case and header names to the form defines in RFC2616 (lower case - * with initial letter of each word capitalized). Either list can contain a wild-card '*' - * character which signifies all request methods or headers respectively. - *

    - * Note. Depending on the context of use, some request methods and headers may be permitted - * at all times, and others may not be permitted at any time. For example, the - * HTTP protocol handler might disallow certain headers such as Content-Length - * from being set by application code, regardless of whether the security policy - * in force, permits it. - * - * @since 1.8 - */ -public final class HttpURLPermission extends Permission { - - private static final long serialVersionUID = -2702463814894478682L; - - private transient URI uri; - private transient List methods; - private transient List requestHeaders; - - // serialized field - private String actions; - - /** - * Creates a new HttpURLPermission from a url string and which permits the given - * request methods and user-settable request headers. - * The name of the permission is its url string. Only the scheme, authority - * and path components of the url are used. Any fragment or query - * components are ignored. The permissions action string is as specified above. - * - * @param url the url string - * - * @param actions the actions string - * - * @throws IllegalArgumentException if url does not result in a valid {@link URI}, - * its scheme is not http or https, or if actions contains white-space. - */ - public HttpURLPermission(String url, String actions) { - super(url); - init(actions); - } - - private void init(String actions) { - URI uri = parseURI(getName()); - int colon = actions.indexOf(':'); - if (actions.lastIndexOf(':') != colon) { - throw new IllegalArgumentException("invalid actions string"); - } - - String methods, headers; - if (colon == -1) { - methods = actions; - headers = ""; - } else { - methods = actions.substring(0, colon); - headers = actions.substring(colon+1); - } - - List l = normalizeMethods(methods); - Collections.sort(l); - this.methods = Collections.unmodifiableList(l); - - l = normalizeHeaders(headers); - Collections.sort(l); - this.requestHeaders = Collections.unmodifiableList(l); - - this.actions = actions(); - this.uri = uri; - } - - /** - * Creates a HttpURLPermission with the given url string and unrestricted - * methods and request headers by invoking the two argument - * constructor as follows: HttpURLPermission(url, "*:*") - * - * @param url the url string - * - * @throws IllegalArgumentException if url does not result in a valid {@link URI} - */ - public HttpURLPermission(String url) { - this(url, "*:*"); - } - - /** - * Returns the normalized method list and request - * header list, in the form: - *

    -     *      "method-names : header-names"
    -     * 
    - *

    - * where method-names is the list of methods separated by commas - * and header-names is the list of permitted headers separated by commas. - * There is no white space in the returned String. If header-names is empty - * then the colon separator will not be present. - */ - public String getActions() { - return actions; - } - - /** - * Checks if this HttpURLPermission implies the given permission. - * Specifically, the following checks are done as if in the - * following sequence: - *

    - *

    Some examples of how paths are matched are shown below: - *

    - * - * - * - * - * - * - * - * - * - *
    Examples of Path Matching
    this's pathp's pathmatch
    /a/b/a/byes
    /a/b/*/a/b/cyes
    /a/b/*/a/b/c/dno
    /a/b/-/a/b/c/dyes
    /a/b/-/a/b/c/d/eyes
    /a/b/-/a/b/c/*yes
    /a/b/*/a/b/c/-no
    - */ - public boolean implies(Permission p) { - if (! (p instanceof HttpURLPermission)) { - return false; - } - - HttpURLPermission that = (HttpURLPermission)p; - - if (!this.methods.get(0).equals("*") && - Collections.indexOfSubList(this.methods, that.methods) == -1) { - return false; - } - - if (this.requestHeaders.isEmpty() && !that.requestHeaders.isEmpty()) { - return false; - } - - if (!this.requestHeaders.isEmpty() && - !this.requestHeaders.get(0).equals("*") && - Collections.indexOfSubList(this.requestHeaders, - that.requestHeaders) == -1) { - return false; - } - - if (this.uri.equals(that.uri)) { - return true; - } - - if (!this.uri.getScheme().equals(that.uri.getScheme())) { - return false; - } - - if (this.uri.getSchemeSpecificPart().equals("*")) { - return true; - } - - String thisAuthority = this.uri.getAuthority(); - - if (thisAuthority != null && - !thisAuthority.equals(that.uri.getAuthority())) { - return false; - } - - String thispath = this.uri.getPath(); - String thatpath = that.uri.getPath(); - - if (thispath.endsWith("/-")) { - String thisprefix = thispath.substring(0, thispath.length() - 1); - return thatpath.startsWith(thisprefix); - } - - if (thispath.endsWith("/*")) { - String thisprefix = thispath.substring(0, thispath.length() - 1); - if (!thatpath.startsWith(thisprefix)) { - return false; - } - String thatsuffix = thatpath.substring(thisprefix.length()); - // suffix must not contain '/' chars - if (thatsuffix.indexOf('/') != -1) { - return false; - } - if (thatsuffix.equals("-")) { - return false; - } - return true; - } - return false; - } - - - /** - * Returns true if, this.getActions().equals(p.getActions()) - * and p's url equals this's url. Returns false otherwise. - */ - public boolean equals(Object p) { - if (!(p instanceof HttpURLPermission)) { - return false; - } - HttpURLPermission that = (HttpURLPermission)p; - return this.getActions().equals(that.getActions()) && - this.uri.equals(that.uri); - } - - /** - * Returns a hashcode calculated from the hashcode of the - * actions String and the url - */ - public int hashCode() { - return getActions().hashCode() + uri.hashCode(); - } - - - private List normalizeMethods(String methods) { - List l = new ArrayList<>(); - StringBuilder b = new StringBuilder(); - for (int i=0; i 0) - l.add(s); - b = new StringBuilder(); - } else if (c == ' ' || c == '\t') { - throw new IllegalArgumentException("white space not allowed"); - } else { - if (c >= 'a' && c <= 'z') { - c += 'A' - 'a'; - } - b.append(c); - } - } - String s = b.toString(); - if (s.length() > 0) - l.add(s); - return l; - } - - private List normalizeHeaders(String headers) { - List l = new ArrayList<>(); - StringBuilder b = new StringBuilder(); - boolean capitalizeNext = true; - for (int i=0; i= 'a' && c <= 'z') { - if (capitalizeNext) { - c += 'A' - 'a'; - capitalizeNext = false; - } - b.append(c); - } else if (c == ' ' || c == '\t') { - throw new IllegalArgumentException("white space not allowed"); - } else if (c == '-') { - capitalizeNext = true; - b.append(c); - } else if (c == ',') { - String s = b.toString(); - if (s.length() > 0) - l.add(s); - b = new StringBuilder(); - capitalizeNext = true; - } else { - capitalizeNext = false; - b.append(c); - } - } - String s = b.toString(); - if (s.length() > 0) - l.add(s); - return l; - } - - private URI parseURI(String url) { - URI u = URI.create(url); - String scheme = u.getScheme(); - if (!(scheme.equalsIgnoreCase("http") || - scheme.equalsIgnoreCase("https"))) { - throw new IllegalArgumentException ("unexpected URL scheme"); - } - if (!u.getSchemeSpecificPart().equals("*")) { - u = URI.create(scheme + "://" + u.getRawAuthority() + u.getRawPath()); - } - return u; - } - - private String actions() { - StringBuilder b = new StringBuilder(); - for (String s : methods) { - b.append(s); - } - b.append(":"); - for (String s : requestHeaders) { - b.append(s); - } - return b.toString(); - } - /** - * restore the state of this object from stream - */ - private void readObject(ObjectInputStream s) - throws IOException, ClassNotFoundException { - ObjectInputStream.GetField fields = s.readFields(); - String actions = (String)fields.get("actions", null); - - init(actions); - } -} diff -r c142f19c7181 -r ab071ce90368 jdk/src/share/classes/java/net/URLPermission.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/net/URLPermission.java Mon Oct 14 22:09:15 2013 +0100 @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 in the LICENSE file that + * accompanied this code). + * + * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +import java.io.ObjectInputStream; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.security.Permission; + +/** + * Represents permission to access a resource or set of resources defined by a + * given url, and for a given set of user-settable request methods + * and request headers. The name of the permission is the url string. + * The actions string is a concatenation of the request methods and headers. + * The range of method and header names is not restricted by this class. + *

    The url

    + * The url string has the following expected structure. + *

    + *     scheme : // authority [ / path ]
    + * 
    + * scheme will typically be http or https, but is not restricted by this + * class. + * authority is specified as:

    + *

    + *     authority = hostrange [ : portrange ]
    + *     portrange = portnumber | -portnumber | portnumber-[portnumber] | *
    + *     hostrange = ([*.] dnsname) | IPv4address | IPv6address
    + * 
    + * dnsname is a standard DNS host or domain name, ie. one or more labels + * separated by ".". IPv4address is a standard literal IPv4 address and + * IPv6address is as defined in + * RFC 2732. Literal IPv6 addresses must however, be enclosed in '[]' characters. + * The dnsname specification can be preceded by "*." which means + * the name will match any hostname whose right-most domain labels are the same as + * this name. For example, "*.oracle.com" matches "foo.bar.oracle.com" + *

    + * portrange is used to specify a port number, or a bounded or unbounded range of ports + * that this permission applies to. If portrange is absent or invalid, then a default + * port number is assumed if the scheme is {@code http} (default 80) or {@code https} + * (default 443). No default is assumed for other schemes. A wildcard may be specified + * which means all ports. + *

    + * The path component comprises a sequence of path segments, + * separated by '/' characters. path may also be empty. The path is specified + * in a similar way to the path in {@link java.io.FilePermission}. There are + * three different ways as the following examples show: + * + * + * + * + * + * + * + * + * + * + * + * + *
    URL Examples
    Example urlDescription
    http://www.oracle.com/a/b/c.htmlA url which identifies a specific (single) resource
    http://www.oracle.com/a/b/*The '*' character refers to all resources in the same "directory" - in + * other words all resources with the same number of path components, and + * which only differ in the final path component, represented by the '*'. + *
    http://www.oracle.com/a/b/-The '-' character refers to all resources recursively below the + * preceding path (eg. http://www.oracle.com/a/b/c/d/e.html matches this + * example). + *
    + *

    + * The '*' and '-' may only be specified in the final segment of a path and must be + * the only character in that segment. Any query or fragment components of the + * url are ignored when constructing URLPermissions. + *

    + * As a special case, urls of the form, "scheme:*" are accepted to + * mean any url of the given scheme. + *

    + * The scheme and authority components of the url string are handled + * without regard to case. This means {@link #equals(Object)}, + * {@link #hashCode()} and {@link #implies(Permission)} are case insensitive with respect + * to these components. If the authority contains a literal IP address, + * then the address is normalized for comparison. The path component is case sensitive. + *

    The actions string

    + * The actions string of a URLPermission is a concatenation of the method list + * and the request headers list. These are lists of the permitted request + * methods and permitted request headers of the permission (respectively). The two lists + * are separated by a colon ':' character and elements of each list are comma separated. + * Some examples are: + *

    + *         "POST,GET,DELETE"
    + *         "GET:X-Foo-Request,X-Bar-Request"
    + *         "POST,GET:Header1,Header2"
    + * 
    + * The first example specifies the methods: POST, GET and DELETE, but no request headers. + * The second example specifies one request method and two headers. The third + * example specifies two request methods, and two headers. + *

    + * The colon separator need not be present if the request headers list is empty. + * No white-space is permitted in the actions string. The action strings supplied to + * the URLPermission constructors are case-insensitive and are normalized by converting + * method names to upper-case and header names to the form defines in RFC2616 (lower case + * with initial letter of each word capitalized). Either list can contain a wild-card '*' + * character which signifies all request methods or headers respectively. + *

    + * Note. Depending on the context of use, some request methods and headers may be permitted + * at all times, and others may not be permitted at any time. For example, the + * HTTP protocol handler might disallow certain headers such as Content-Length + * from being set by application code, regardless of whether the security policy + * in force, permits it. + * + * @since 1.8 + */ +public final class URLPermission extends Permission { + + private static final long serialVersionUID = -2702463814894478682L; + + private transient String scheme; + private transient String ssp; // scheme specific part + private transient String path; + private transient List methods; + private transient List requestHeaders; + private transient Authority authority; + + // serialized field + private String actions; + + /** + * Creates a new URLPermission from a url string and which permits the given + * request methods and user-settable request headers. + * The name of the permission is the url string it was created with. Only the scheme, + * authority and path components of the url are used internally. Any fragment or query + * components are ignored. The permissions action string is as specified above. + * + * @param url the url string + * + * @param actions the actions string + * + * @exception IllegalArgumentException if url is invalid or if actions contains white-space. + */ + public URLPermission(String url, String actions) { + super(url); + init(actions); + } + + private void init(String actions) { + parseURI(getName()); + int colon = actions.indexOf(':'); + if (actions.lastIndexOf(':') != colon) { + throw new IllegalArgumentException("invalid actions string"); + } + + String methods, headers; + if (colon == -1) { + methods = actions; + headers = ""; + } else { + methods = actions.substring(0, colon); + headers = actions.substring(colon+1); + } + + List l = normalizeMethods(methods); + Collections.sort(l); + this.methods = Collections.unmodifiableList(l); + + l = normalizeHeaders(headers); + Collections.sort(l); + this.requestHeaders = Collections.unmodifiableList(l); + + this.actions = actions(); + } + + /** + * Creates a URLPermission with the given url string and unrestricted + * methods and request headers by invoking the two argument + * constructor as follows: URLPermission(url, "*:*") + * + * @param url the url string + * + * @throws IllegalArgumentException if url does not result in a valid {@link URI} + */ + public URLPermission(String url) { + this(url, "*:*"); + } + + /** + * Returns the normalized method list and request + * header list, in the form: + *

    +     *      "method-names : header-names"
    +     * 
    + *

    + * where method-names is the list of methods separated by commas + * and header-names is the list of permitted headers separated by commas. + * There is no white space in the returned String. If header-names is empty + * then the colon separator will not be present. + */ + public String getActions() { + return actions; + } + + /** + * Checks if this URLPermission implies the given permission. + * Specifically, the following checks are done as if in the + * following sequence: + *

    + *

    Some examples of how paths are matched are shown below: + *

    + * + * + * + * + * + * + * + * + * + *
    Examples of Path Matching
    this's pathp's pathmatch
    /a/b/a/byes
    /a/b/*/a/b/cyes
    /a/b/*/a/b/c/dno
    /a/b/-/a/b/c/dyes
    /a/b/-/a/b/c/d/eyes
    /a/b/-/a/b/c/*yes
    /a/b/*/a/b/c/-no
    + */ + public boolean implies(Permission p) { + if (! (p instanceof URLPermission)) { + return false; + } + + URLPermission that = (URLPermission)p; + + if (!this.methods.get(0).equals("*") && + Collections.indexOfSubList(this.methods, that.methods) == -1) { + return false; + } + + if (this.requestHeaders.isEmpty() && !that.requestHeaders.isEmpty()) { + return false; + } + + if (!this.requestHeaders.isEmpty() && + !this.requestHeaders.get(0).equals("*") && + Collections.indexOfSubList(this.requestHeaders, + that.requestHeaders) == -1) { + return false; + } + + if (!this.scheme.equals(that.scheme)) { + return false; + } + + if (this.ssp.equals("*")) { + return true; + } + + if (!this.authority.implies(that.authority)) { + return false; + } + + if (this.path == null) { + return that.path == null; + } + if (that.path == null) { + return false; + } + + if (this.path.endsWith("/-")) { + String thisprefix = this.path.substring(0, this.path.length() - 1); + return that.path.startsWith(thisprefix); + } + + if (this.path.endsWith("/*")) { + String thisprefix = this.path.substring(0, this.path.length() - 1); + if (!that.path.startsWith(thisprefix)) { + return false; + } + String thatsuffix = that.path.substring(thisprefix.length()); + // suffix must not contain '/' chars + if (thatsuffix.indexOf('/') != -1) { + return false; + } + if (thatsuffix.equals("-")) { + return false; + } + return true; + } + return this.path.equals(that.path); + } + + + /** + * Returns true if, this.getActions().equals(p.getActions()) + * and p's url equals this's url. Returns false otherwise. + */ + public boolean equals(Object p) { + if (!(p instanceof URLPermission)) { + return false; + } + URLPermission that = (URLPermission)p; + if (!this.scheme.equals(that.scheme)) { + return false; + } + if (!this.getActions().equals(that.getActions())) { + return false; + } + if (!this.authority.equals(that.authority)) { + return false; + } + if (this.path != null) { + return this.path.equals(that.path); + } else { + return that.path == null; + } + } + + /** + * Returns a hashcode calculated from the hashcode of the + * actions String and the url string. + */ + public int hashCode() { + return getActions().hashCode() + + scheme.hashCode() + + authority.hashCode() + + path == null ? 0 : path.hashCode(); + } + + + private List normalizeMethods(String methods) { + List l = new ArrayList<>(); + StringBuilder b = new StringBuilder(); + for (int i=0; i 0) + l.add(s); + b = new StringBuilder(); + } else if (c == ' ' || c == '\t') { + throw new IllegalArgumentException("white space not allowed"); + } else { + if (c >= 'a' && c <= 'z') { + c += 'A' - 'a'; + } + b.append(c); + } + } + String s = b.toString(); + if (s.length() > 0) + l.add(s); + return l; + } + + private List normalizeHeaders(String headers) { + List l = new ArrayList<>(); + StringBuilder b = new StringBuilder(); + boolean capitalizeNext = true; + for (int i=0; i= 'a' && c <= 'z') { + if (capitalizeNext) { + c += 'A' - 'a'; + capitalizeNext = false; + } + b.append(c); + } else if (c == ' ' || c == '\t') { + throw new IllegalArgumentException("white space not allowed"); + } else if (c == '-') { + capitalizeNext = true; + b.append(c); + } else if (c == ',') { + String s = b.toString(); + if (s.length() > 0) + l.add(s); + b = new StringBuilder(); + capitalizeNext = true; + } else { + capitalizeNext = false; + b.append(c); + } + } + String s = b.toString(); + if (s.length() > 0) + l.add(s); + return l; + } + + private void parseURI(String url) { + int len = url.length(); + int delim = url.indexOf(':'); + if (delim == -1 || delim + 1 == len) { + throw new IllegalArgumentException("invalid URL string"); + } + scheme = url.substring(0, delim).toLowerCase(); + this.ssp = url.substring(delim + 1); + + if (!ssp.startsWith("//")) { + this.authority = new Authority(scheme, ssp.toLowerCase()); + return; + } + String authpath = ssp.substring(2); + + delim = authpath.indexOf('/'); + String auth; + if (delim == -1) { + this.path = ""; + auth = authpath; + } else { + auth = authpath.substring(0, delim); + this.path = authpath.substring(delim); + } + this.authority = new Authority(scheme, auth.toLowerCase()); + } + + private String actions() { + StringBuilder b = new StringBuilder(); + for (String s : methods) { + b.append(s); + } + b.append(":"); + for (String s : requestHeaders) { + b.append(s); + } + return b.toString(); + } + + /** + * restore the state of this object from stream + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = s.readFields(); + String actions = (String)fields.get("actions", null); + + init(actions); + } + + static class Authority { + HostPortrange p; + + Authority(String scheme, String authority) { + p = new HostPortrange(scheme, authority); + } + + boolean implies(Authority other) { + return impliesHostrange(other) && impliesPortrange(other); + } + + private boolean impliesHostrange(Authority that) { + String thishost = this.p.hostname(); + String thathost = that.p.hostname(); + + if (p.wildcard() && thishost.equals("")) { + // this "*" implies all others + return true; + } + if (that.p.wildcard() && thathost.equals("")) { + // that "*" can only be implied by this "*" + return false; + } + if (thishost.equals(thathost)) { + // covers all cases of literal IP addresses and fixed + // domain names. + return true; + } + if (this.p.wildcard()) { + // this "*.foo.com" implies "bub.bar.foo.com" + return thathost.endsWith(thishost); + } + return false; + } + + private boolean impliesPortrange(Authority that) { + int[] thisrange = this.p.portrange(); + int[] thatrange = that.p.portrange(); + if (thisrange[0] == -1) { + /* port not specified non http/s URL */ + return true; + } + return thisrange[0] <= thatrange[0] && + thisrange[1] >= thatrange[1]; + } + + boolean equals(Authority that) { + return this.p.equals(that.p); + } + + public int hashCode() { + return p.hashCode(); + } + } +} diff -r c142f19c7181 -r ab071ce90368 jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java --- a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Mon Oct 14 09:52:36 2013 -0700 +++ b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Mon Oct 14 22:09:15 2013 +0100 @@ -46,7 +46,7 @@ import java.net.CacheResponse; import java.net.SecureCacheResponse; import java.net.CacheRequest; -import java.net.HttpURLPermission; +import java.net.URLPermission; import java.net.Authenticator.RequestorType; import java.security.AccessController; import java.security.PrivilegedExceptionAction; @@ -389,7 +389,7 @@ private int connectTimeout = NetworkClient.DEFAULT_CONNECT_TIMEOUT; private int readTimeout = NetworkClient.DEFAULT_READ_TIMEOUT; - /* A permission converted from a HttpURLPermission */ + /* A permission converted from a URLPermission */ private SocketPermission socketPermission; /* Logging support */ @@ -930,8 +930,7 @@ plainConnect0(); return null; } - } -// }, null, p -- replace line above, when limited doPriv ready + }, null, p ); } catch (PrivilegedActionException e) { throw (IOException) e.getException(); @@ -943,7 +942,7 @@ } /** - * if the caller has a HttpURLPermission for connecting to the + * if the caller has a URLPermission for connecting to the * given URL, then return a SocketPermission which permits * access to that destination. Return null otherwise. The permission * is cached in a field (which can only be changed by redirects) @@ -969,7 +968,10 @@ String actions = getRequestMethod()+":" + getUserSetHeaders().getHeaderNamesInList(); - HttpURLPermission p = new HttpURLPermission(url.toString(), actions); + String urlstring = url.getProtocol() + "://" + url.getAuthority() + + url.getPath(); + + URLPermission p = new URLPermission(urlstring, actions); try { sm.checkPermission(p); socketPermission = newPerm; @@ -1188,8 +1190,7 @@ public OutputStream run() throws IOException { return getOutputStream0(); } - } -// }, null, p -- replace line above, when limited doPriv ready + }, null, p ); } catch (PrivilegedActionException e) { throw (IOException) e.getException(); @@ -1372,8 +1373,7 @@ public InputStream run() throws IOException { return getInputStream0(); } - } -// }, null, p -- replace line above, when limited doPriv ready + }, null, p ); } catch (PrivilegedActionException e) { throw (IOException) e.getException(); @@ -2507,8 +2507,7 @@ public Boolean run() throws IOException { return followRedirect0(loc, stat, locUrl0); } - } -// }, null, p -- replace line above, when limited doPriv ready + }, null, p ); } catch (PrivilegedActionException e) { throw (IOException) e.getException(); diff -r c142f19c7181 -r ab071ce90368 jdk/src/share/classes/sun/security/tools/policytool/PolicyTool.java --- a/jdk/src/share/classes/sun/security/tools/policytool/PolicyTool.java Mon Oct 14 09:52:36 2013 -0700 +++ b/jdk/src/share/classes/sun/security/tools/policytool/PolicyTool.java Mon Oct 14 22:09:15 2013 +0100 @@ -1447,7 +1447,7 @@ PERM_ARRAY.add(new AWTPerm()); PERM_ARRAY.add(new DelegationPerm()); PERM_ARRAY.add(new FilePerm()); - PERM_ARRAY.add(new HttpURLPerm()); + PERM_ARRAY.add(new URLPerm()); PERM_ARRAY.add(new InqSecContextPerm()); PERM_ARRAY.add(new LogPerm()); PERM_ARRAY.add(new MgmtPerm()); @@ -3843,10 +3843,10 @@ } } -class HttpURLPerm extends Perm { - public HttpURLPerm() { - super("HttpURLPermission", - "java.net.HttpURLPermission", +class URLPerm extends Perm { + public URLPerm() { + super("URLPermission", + "java.net.URLPermission", new String[] { "<"+ PolicyTool.rb.getString("url") + ">", }, diff -r c142f19c7181 -r ab071ce90368 jdk/test/java/net/HttpURLPermission/HttpURLPermissionTest.java --- a/jdk/test/java/net/HttpURLPermission/HttpURLPermissionTest.java Mon Oct 14 09:52:36 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code 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 code 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 in the LICENSE file that - * accompanied this code). - * - * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -import java.net.HttpURLPermission; -import java.io.*; - -/** - * @test - * @bug 8010464 - */ - -public class HttpURLPermissionTest { - - // super class for all test types - abstract static class Test { - boolean expected; - abstract boolean execute(); - }; - - // Tests URL part of implies() method. This is the main test. - static class URLImpliesTest extends Test { - String arg1, arg2; - - URLImpliesTest(String arg1, String arg2, boolean expected) { - this.arg1 = arg1; - this.arg2 = arg2; - this.expected = expected; - } - - boolean execute() { - HttpURLPermission p1 = new HttpURLPermission (arg1, "GET:*"); - HttpURLPermission p2 = new HttpURLPermission (arg2, "GET:*"); - boolean result = p1.implies(p2); - return result == expected; - } - }; - - static URLImpliesTest imtest(String arg1, String arg2, boolean expected) { - return new URLImpliesTest(arg1, arg2, expected); - } - - static class ActionImpliesTest extends Test { - String arg1, arg2; - - ActionImpliesTest(String arg1, String arg2, boolean expected) { - this.arg1 = arg1; - this.arg2 = arg2; - this.expected = expected; - } - - boolean execute() { - String url1 = "http://www.foo.com/-"; - String url2 = "http://www.foo.com/a/b"; - HttpURLPermission p1 = new HttpURLPermission(url1, arg1); - HttpURLPermission p2 = new HttpURLPermission(url2, arg2); - boolean result = p1.implies(p2); - return result == expected; - } - } - - static ActionImpliesTest actest(String arg1, String arg2, boolean expected) { - return new ActionImpliesTest(arg1, arg2, expected); - } - - static Test[] pathImplies = { - // single - imtest("http://www.foo.com/", "http://www.foo.com/", true), - imtest("http://www.bar.com/", "http://www.foo.com/", false), - imtest("http://www.foo.com/a/b", "http://www.foo.com/", false), - imtest("http://www.foo.com/a/b", "http://www.foo.com/a/b/c", false), - // wildcard - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/a/b/c", true), - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/a/b/*", true), - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/a/b/c#frag", true), - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/a/b/c#frag?foo=foo", true), - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/b/b/c", false), - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/a/b/c.html", true), - imtest("http://www.foo.com/a/b/*", "http://www.foo.com/a/b/c.html", true), - imtest("http://www.foo.com/a/b/*", "https://www.foo.com/a/b/c", false), - // recursive - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/-", true), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c", true), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c#frag", true), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c#frag?foo=foo", true), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/b/b/c", false), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c.html", true), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c.html", true), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c/d/e.html", true), - imtest("https://www.foo.com/a/b/-", "http://www.foo.com/a/b/c/d/e.html", false), - imtest("http://www.foo.com/a/b/-", "http://www.foo.com/a/b/c/d/e#frag", true), - imtest("http://www.foo.com/a/b/-", "https://www.foo.com/a/b/c", false), - // special cases - imtest("http:*", "https://www.foo.com/a/b/c", false), - imtest("http:*", "http://www.foo.com/a/b/c", true), - imtest("http:*", "http://foo/bar", true), - imtest("http://foo/bar", "https://foo/bar", false) - }; - - static Test[] actionImplies = { - actest("GET", "GET", true), - actest("GET", "POST", false), - actest("GET:", "PUT", false), - actest("GET:", "GET", true), - actest("GET,POST", "GET", true), - actest("GET,POST:", "GET", true), - actest("GET:X-Foo", "GET:x-foo", true), - actest("GET:X-Foo,X-bar", "GET:x-foo", true), - actest("GET:X-Foo", "GET:x-boo", false), - actest("GET:X-Foo,X-Bar", "GET:x-bar,x-foo", true), - actest("GET:X-Bar,X-Foo,X-Bar,Y-Foo", "GET:x-bar,x-foo", true), - actest("GET:*", "GET:x-bar,x-foo", true), - actest("*:*", "GET:x-bar,x-foo", true) - }; - - static boolean failed = false; - - public static void main(String args[]) throws Exception { - for (int i=0; i