0001: /*
0002: * Copyright 1999-2004 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: /*
0017: * $Id: URI.java,v 1.2 2004/12/16 19:22:34 minchau Exp $
0018: */
0019: package org.apache.xml.serializer.utils;
0020:
0021: import java.io.IOException;
0022: import java.io.Serializable;
0023:
0024: /**
0025: * A class to represent a Uniform Resource Identifier (URI). This class
0026: * is designed to handle the parsing of URIs and provide access to
0027: * the various components (scheme, host, port, userinfo, path, query
0028: * string and fragment) that may constitute a URI.
0029: * <p>
0030: * Parsing of a URI specification is done according to the URI
0031: * syntax described in RFC 2396
0032: * <http://www.ietf.org/rfc/rfc2396.txt?number=2396>. Every URI consists
0033: * of a scheme, followed by a colon (':'), followed by a scheme-specific
0034: * part. For URIs that follow the "generic URI" syntax, the scheme-
0035: * specific part begins with two slashes ("//") and may be followed
0036: * by an authority segment (comprised of user information, host, and
0037: * port), path segment, query segment and fragment. Note that RFC 2396
0038: * no longer specifies the use of the parameters segment and excludes
0039: * the "user:password" syntax as part of the authority segment. If
0040: * "user:password" appears in a URI, the entire user/password string
0041: * is stored as userinfo.
0042: * <p>
0043: * For URIs that do not follow the "generic URI" syntax (e.g. mailto),
0044: * the entire scheme-specific part is treated as the "path" portion
0045: * of the URI.
0046: * <p>
0047: * Note that, unlike the java.net.URL class, this class does not provide
0048: * any built-in network access functionality nor does it provide any
0049: * scheme-specific functionality (for example, it does not know a
0050: * default port for a specific scheme). Rather, it only knows the
0051: * grammar and basic set of operations that can be applied to a URI.
0052: *
0053: * This class is a copy of the one in org.apache.xml.utils.
0054: * It exists to cut the serializers dependancy on that package.
0055: *
0056: * A minor change from the original is that this class no longer implements
0057: * Serializable, and the serialVersionUID magic field is dropped, and
0058: * the class is no longer "public".
0059: *
0060: * @xsl.usage internal
0061: */
0062: final class URI {
0063: /**
0064: * MalformedURIExceptions are thrown in the process of building a URI
0065: * or setting fields on a URI when an operation would result in an
0066: * invalid URI specification.
0067: *
0068: */
0069: public static class MalformedURIException extends IOException {
0070:
0071: /**
0072: * Constructs a <code>MalformedURIException</code> with no specified
0073: * detail message.
0074: */
0075: public MalformedURIException() {
0076: super ();
0077: }
0078:
0079: /**
0080: * Constructs a <code>MalformedURIException</code> with the
0081: * specified detail message.
0082: *
0083: * @param p_msg the detail message.
0084: */
0085: public MalformedURIException(String p_msg) {
0086: super (p_msg);
0087: }
0088: }
0089:
0090: /** reserved characters */
0091: private static final String RESERVED_CHARACTERS = ";/?:@&=+$,";
0092:
0093: /**
0094: * URI punctuation mark characters - these, combined with
0095: * alphanumerics, constitute the "unreserved" characters
0096: */
0097: private static final String MARK_CHARACTERS = "-_.!~*'() ";
0098:
0099: /** scheme can be composed of alphanumerics and these characters */
0100: private static final String SCHEME_CHARACTERS = "+-.";
0101:
0102: /**
0103: * userinfo can be composed of unreserved, escaped and these
0104: * characters
0105: */
0106: private static final String USERINFO_CHARACTERS = ";:&=+$,";
0107:
0108: /** Stores the scheme (usually the protocol) for this URI.
0109: * @serial */
0110: private String m_scheme = null;
0111:
0112: /** If specified, stores the userinfo for this URI; otherwise null.
0113: * @serial */
0114: private String m_userinfo = null;
0115:
0116: /** If specified, stores the host for this URI; otherwise null.
0117: * @serial */
0118: private String m_host = null;
0119:
0120: /** If specified, stores the port for this URI; otherwise -1.
0121: * @serial */
0122: private int m_port = -1;
0123:
0124: /** If specified, stores the path for this URI; otherwise null.
0125: * @serial */
0126: private String m_path = null;
0127:
0128: /**
0129: * If specified, stores the query string for this URI; otherwise
0130: * null.
0131: * @serial
0132: */
0133: private String m_queryString = null;
0134:
0135: /** If specified, stores the fragment for this URI; otherwise null.
0136: * @serial */
0137: private String m_fragment = null;
0138:
0139: /** Indicate whether in DEBUG mode */
0140: private static boolean DEBUG = false;
0141:
0142: /**
0143: * Construct a new and uninitialized URI.
0144: */
0145: public URI() {
0146: }
0147:
0148: /**
0149: * Construct a new URI from another URI. All fields for this URI are
0150: * set equal to the fields of the URI passed in.
0151: *
0152: * @param p_other the URI to copy (cannot be null)
0153: */
0154: public URI(URI p_other) {
0155: initialize(p_other);
0156: }
0157:
0158: /**
0159: * Construct a new URI from a URI specification string. If the
0160: * specification follows the "generic URI" syntax, (two slashes
0161: * following the first colon), the specification will be parsed
0162: * accordingly - setting the scheme, userinfo, host,port, path, query
0163: * string and fragment fields as necessary. If the specification does
0164: * not follow the "generic URI" syntax, the specification is parsed
0165: * into a scheme and scheme-specific part (stored as the path) only.
0166: *
0167: * @param p_uriSpec the URI specification string (cannot be null or
0168: * empty)
0169: *
0170: * @throws MalformedURIException if p_uriSpec violates any syntax
0171: * rules
0172: */
0173: public URI(String p_uriSpec) throws MalformedURIException {
0174: this ((URI) null, p_uriSpec);
0175: }
0176:
0177: /**
0178: * Construct a new URI from a base URI and a URI specification string.
0179: * The URI specification string may be a relative URI.
0180: *
0181: * @param p_base the base URI (cannot be null if p_uriSpec is null or
0182: * empty)
0183: * @param p_uriSpec the URI specification string (cannot be null or
0184: * empty if p_base is null)
0185: *
0186: * @throws MalformedURIException if p_uriSpec violates any syntax
0187: * rules
0188: */
0189: public URI(URI p_base, String p_uriSpec)
0190: throws MalformedURIException {
0191: initialize(p_base, p_uriSpec);
0192: }
0193:
0194: /**
0195: * Construct a new URI that does not follow the generic URI syntax.
0196: * Only the scheme and scheme-specific part (stored as the path) are
0197: * initialized.
0198: *
0199: * @param p_scheme the URI scheme (cannot be null or empty)
0200: * @param p_schemeSpecificPart the scheme-specific part (cannot be
0201: * null or empty)
0202: *
0203: * @throws MalformedURIException if p_scheme violates any
0204: * syntax rules
0205: */
0206: public URI(String p_scheme, String p_schemeSpecificPart)
0207: throws MalformedURIException {
0208:
0209: if (p_scheme == null || p_scheme.trim().length() == 0) {
0210: throw new MalformedURIException(
0211: "Cannot construct URI with null/empty scheme!");
0212: }
0213:
0214: if (p_schemeSpecificPart == null
0215: || p_schemeSpecificPart.trim().length() == 0) {
0216: throw new MalformedURIException(
0217: "Cannot construct URI with null/empty scheme-specific part!");
0218: }
0219:
0220: setScheme(p_scheme);
0221: setPath(p_schemeSpecificPart);
0222: }
0223:
0224: /**
0225: * Construct a new URI that follows the generic URI syntax from its
0226: * component parts. Each component is validated for syntax and some
0227: * basic semantic checks are performed as well. See the individual
0228: * setter methods for specifics.
0229: *
0230: * @param p_scheme the URI scheme (cannot be null or empty)
0231: * @param p_host the hostname or IPv4 address for the URI
0232: * @param p_path the URI path - if the path contains '?' or '#',
0233: * then the query string and/or fragment will be
0234: * set from the path; however, if the query and
0235: * fragment are specified both in the path and as
0236: * separate parameters, an exception is thrown
0237: * @param p_queryString the URI query string (cannot be specified
0238: * if path is null)
0239: * @param p_fragment the URI fragment (cannot be specified if path
0240: * is null)
0241: *
0242: * @throws MalformedURIException if any of the parameters violates
0243: * syntax rules or semantic rules
0244: */
0245: public URI(String p_scheme, String p_host, String p_path,
0246: String p_queryString, String p_fragment)
0247: throws MalformedURIException {
0248: this (p_scheme, null, p_host, -1, p_path, p_queryString,
0249: p_fragment);
0250: }
0251:
0252: /**
0253: * Construct a new URI that follows the generic URI syntax from its
0254: * component parts. Each component is validated for syntax and some
0255: * basic semantic checks are performed as well. See the individual
0256: * setter methods for specifics.
0257: *
0258: * @param p_scheme the URI scheme (cannot be null or empty)
0259: * @param p_userinfo the URI userinfo (cannot be specified if host
0260: * is null)
0261: * @param p_host the hostname or IPv4 address for the URI
0262: * @param p_port the URI port (may be -1 for "unspecified"; cannot
0263: * be specified if host is null)
0264: * @param p_path the URI path - if the path contains '?' or '#',
0265: * then the query string and/or fragment will be
0266: * set from the path; however, if the query and
0267: * fragment are specified both in the path and as
0268: * separate parameters, an exception is thrown
0269: * @param p_queryString the URI query string (cannot be specified
0270: * if path is null)
0271: * @param p_fragment the URI fragment (cannot be specified if path
0272: * is null)
0273: *
0274: * @throws MalformedURIException if any of the parameters violates
0275: * syntax rules or semantic rules
0276: */
0277: public URI(String p_scheme, String p_userinfo, String p_host,
0278: int p_port, String p_path, String p_queryString,
0279: String p_fragment) throws MalformedURIException {
0280:
0281: if (p_scheme == null || p_scheme.trim().length() == 0) {
0282: throw new MalformedURIException(Utils.messages
0283: .createMessage(MsgKey.ER_SCHEME_REQUIRED, null)); //"Scheme is required!");
0284: }
0285:
0286: if (p_host == null) {
0287: if (p_userinfo != null) {
0288: throw new MalformedURIException(Utils.messages
0289: .createMessage(
0290: MsgKey.ER_NO_USERINFO_IF_NO_HOST, null)); //"Userinfo may not be specified if host is not specified!");
0291: }
0292:
0293: if (p_port != -1) {
0294: throw new MalformedURIException(Utils.messages
0295: .createMessage(MsgKey.ER_NO_PORT_IF_NO_HOST,
0296: null)); //"Port may not be specified if host is not specified!");
0297: }
0298: }
0299:
0300: if (p_path != null) {
0301: if (p_path.indexOf('?') != -1 && p_queryString != null) {
0302: throw new MalformedURIException(
0303: Utils.messages
0304: .createMessage(
0305: MsgKey.ER_NO_QUERY_STRING_IN_PATH,
0306: null)); //"Query string cannot be specified in path and query string!");
0307: }
0308:
0309: if (p_path.indexOf('#') != -1 && p_fragment != null) {
0310: throw new MalformedURIException(Utils.messages
0311: .createMessage(
0312: MsgKey.ER_NO_FRAGMENT_STRING_IN_PATH,
0313: null)); //"Fragment cannot be specified in both the path and fragment!");
0314: }
0315: }
0316:
0317: setScheme(p_scheme);
0318: setHost(p_host);
0319: setPort(p_port);
0320: setUserinfo(p_userinfo);
0321: setPath(p_path);
0322: setQueryString(p_queryString);
0323: setFragment(p_fragment);
0324: }
0325:
0326: /**
0327: * Initialize all fields of this URI from another URI.
0328: *
0329: * @param p_other the URI to copy (cannot be null)
0330: */
0331: private void initialize(URI p_other) {
0332:
0333: m_scheme = p_other.getScheme();
0334: m_userinfo = p_other.getUserinfo();
0335: m_host = p_other.getHost();
0336: m_port = p_other.getPort();
0337: m_path = p_other.getPath();
0338: m_queryString = p_other.getQueryString();
0339: m_fragment = p_other.getFragment();
0340: }
0341:
0342: /**
0343: * Initializes this URI from a base URI and a URI specification string.
0344: * See RFC 2396 Section 4 and Appendix B for specifications on parsing
0345: * the URI and Section 5 for specifications on resolving relative URIs
0346: * and relative paths.
0347: *
0348: * @param p_base the base URI (may be null if p_uriSpec is an absolute
0349: * URI)
0350: * @param p_uriSpec the URI spec string which may be an absolute or
0351: * relative URI (can only be null/empty if p_base
0352: * is not null)
0353: *
0354: * @throws MalformedURIException if p_base is null and p_uriSpec
0355: * is not an absolute URI or if
0356: * p_uriSpec violates syntax rules
0357: */
0358: private void initialize(URI p_base, String p_uriSpec)
0359: throws MalformedURIException {
0360:
0361: if (p_base == null
0362: && (p_uriSpec == null || p_uriSpec.trim().length() == 0)) {
0363: throw new MalformedURIException(
0364: Utils.messages
0365: .createMessage(
0366: MsgKey.ER_CANNOT_INIT_URI_EMPTY_PARMS,
0367: null)); //"Cannot initialize URI with empty parameters.");
0368: }
0369:
0370: // just make a copy of the base if spec is empty
0371: if (p_uriSpec == null || p_uriSpec.trim().length() == 0) {
0372: initialize(p_base);
0373:
0374: return;
0375: }
0376:
0377: String uriSpec = p_uriSpec.trim();
0378: int uriSpecLen = uriSpec.length();
0379: int index = 0;
0380:
0381: // check for scheme
0382: int colonIndex = uriSpec.indexOf(':');
0383: if (colonIndex < 0) {
0384: if (p_base == null) {
0385: throw new MalformedURIException(Utils.messages
0386: .createMessage(MsgKey.ER_NO_SCHEME_IN_URI,
0387: new Object[] { uriSpec })); //"No scheme found in URI: "+uriSpec);
0388: }
0389: } else {
0390: initializeScheme(uriSpec);
0391: uriSpec = uriSpec.substring(colonIndex + 1);
0392: uriSpecLen = uriSpec.length();
0393: }
0394:
0395: // two slashes means generic URI syntax, so we get the authority
0396: if (((index + 1) < uriSpecLen)
0397: && (uriSpec.substring(index).startsWith("//"))) {
0398: index += 2;
0399:
0400: int startPos = index;
0401:
0402: // get authority - everything up to path, query or fragment
0403: char testChar = '\0';
0404:
0405: while (index < uriSpecLen) {
0406: testChar = uriSpec.charAt(index);
0407:
0408: if (testChar == '/' || testChar == '?'
0409: || testChar == '#') {
0410: break;
0411: }
0412:
0413: index++;
0414: }
0415:
0416: // if we found authority, parse it out, otherwise we set the
0417: // host to empty string
0418: if (index > startPos) {
0419: initializeAuthority(uriSpec.substring(startPos, index));
0420: } else {
0421: m_host = "";
0422: }
0423: }
0424:
0425: initializePath(uriSpec.substring(index));
0426:
0427: // Resolve relative URI to base URI - see RFC 2396 Section 5.2
0428: // In some cases, it might make more sense to throw an exception
0429: // (when scheme is specified is the string spec and the base URI
0430: // is also specified, for example), but we're just following the
0431: // RFC specifications
0432: if (p_base != null) {
0433:
0434: // check to see if this is the current doc - RFC 2396 5.2 #2
0435: // note that this is slightly different from the RFC spec in that
0436: // we don't include the check for query string being null
0437: // - this handles cases where the urispec is just a query
0438: // string or a fragment (e.g. "?y" or "#s") -
0439: // see <http://www.ics.uci.edu/~fielding/url/test1.html> which
0440: // identified this as a bug in the RFC
0441: if (m_path.length() == 0 && m_scheme == null
0442: && m_host == null) {
0443: m_scheme = p_base.getScheme();
0444: m_userinfo = p_base.getUserinfo();
0445: m_host = p_base.getHost();
0446: m_port = p_base.getPort();
0447: m_path = p_base.getPath();
0448:
0449: if (m_queryString == null) {
0450: m_queryString = p_base.getQueryString();
0451: }
0452:
0453: return;
0454: }
0455:
0456: // check for scheme - RFC 2396 5.2 #3
0457: // if we found a scheme, it means absolute URI, so we're done
0458: if (m_scheme == null) {
0459: m_scheme = p_base.getScheme();
0460: }
0461:
0462: // check for authority - RFC 2396 5.2 #4
0463: // if we found a host, then we've got a network path, so we're done
0464: if (m_host == null) {
0465: m_userinfo = p_base.getUserinfo();
0466: m_host = p_base.getHost();
0467: m_port = p_base.getPort();
0468: } else {
0469: return;
0470: }
0471:
0472: // check for absolute path - RFC 2396 5.2 #5
0473: if (m_path.length() > 0 && m_path.startsWith("/")) {
0474: return;
0475: }
0476:
0477: // if we get to this point, we need to resolve relative path
0478: // RFC 2396 5.2 #6
0479: String path = new String();
0480: String basePath = p_base.getPath();
0481:
0482: // 6a - get all but the last segment of the base URI path
0483: if (basePath != null) {
0484: int lastSlash = basePath.lastIndexOf('/');
0485:
0486: if (lastSlash != -1) {
0487: path = basePath.substring(0, lastSlash + 1);
0488: }
0489: }
0490:
0491: // 6b - append the relative URI path
0492: path = path.concat(m_path);
0493:
0494: // 6c - remove all "./" where "." is a complete path segment
0495: index = -1;
0496:
0497: while ((index = path.indexOf("/./")) != -1) {
0498: path = path.substring(0, index + 1).concat(
0499: path.substring(index + 3));
0500: }
0501:
0502: // 6d - remove "." if path ends with "." as a complete path segment
0503: if (path.endsWith("/.")) {
0504: path = path.substring(0, path.length() - 1);
0505: }
0506:
0507: // 6e - remove all "<segment>/../" where "<segment>" is a complete
0508: // path segment not equal to ".."
0509: index = -1;
0510:
0511: int segIndex = -1;
0512: String tempString = null;
0513:
0514: while ((index = path.indexOf("/../")) > 0) {
0515: tempString = path.substring(0, path.indexOf("/../"));
0516: segIndex = tempString.lastIndexOf('/');
0517:
0518: if (segIndex != -1) {
0519: if (!tempString.substring(segIndex++).equals("..")) {
0520: path = path.substring(0, segIndex).concat(
0521: path.substring(index + 4));
0522: }
0523: }
0524: }
0525:
0526: // 6f - remove ending "<segment>/.." where "<segment>" is a
0527: // complete path segment
0528: if (path.endsWith("/..")) {
0529: tempString = path.substring(0, path.length() - 3);
0530: segIndex = tempString.lastIndexOf('/');
0531:
0532: if (segIndex != -1) {
0533: path = path.substring(0, segIndex + 1);
0534: }
0535: }
0536:
0537: m_path = path;
0538: }
0539: }
0540:
0541: /**
0542: * Initialize the scheme for this URI from a URI string spec.
0543: *
0544: * @param p_uriSpec the URI specification (cannot be null)
0545: *
0546: * @throws MalformedURIException if URI does not have a conformant
0547: * scheme
0548: */
0549: private void initializeScheme(String p_uriSpec)
0550: throws MalformedURIException {
0551:
0552: int uriSpecLen = p_uriSpec.length();
0553: int index = 0;
0554: String scheme = null;
0555: char testChar = '\0';
0556:
0557: while (index < uriSpecLen) {
0558: testChar = p_uriSpec.charAt(index);
0559:
0560: if (testChar == ':' || testChar == '/' || testChar == '?'
0561: || testChar == '#') {
0562: break;
0563: }
0564:
0565: index++;
0566: }
0567:
0568: scheme = p_uriSpec.substring(0, index);
0569:
0570: if (scheme.length() == 0) {
0571: throw new MalformedURIException(Utils.messages
0572: .createMessage(MsgKey.ER_NO_SCHEME_INURI, null)); //"No scheme found in URI.");
0573: } else {
0574: setScheme(scheme);
0575: }
0576: }
0577:
0578: /**
0579: * Initialize the authority (userinfo, host and port) for this
0580: * URI from a URI string spec.
0581: *
0582: * @param p_uriSpec the URI specification (cannot be null)
0583: *
0584: * @throws MalformedURIException if p_uriSpec violates syntax rules
0585: */
0586: private void initializeAuthority(String p_uriSpec)
0587: throws MalformedURIException {
0588:
0589: int index = 0;
0590: int start = 0;
0591: int end = p_uriSpec.length();
0592: char testChar = '\0';
0593: String userinfo = null;
0594:
0595: // userinfo is everything up @
0596: if (p_uriSpec.indexOf('@', start) != -1) {
0597: while (index < end) {
0598: testChar = p_uriSpec.charAt(index);
0599:
0600: if (testChar == '@') {
0601: break;
0602: }
0603:
0604: index++;
0605: }
0606:
0607: userinfo = p_uriSpec.substring(start, index);
0608:
0609: index++;
0610: }
0611:
0612: // host is everything up to ':'
0613: String host = null;
0614:
0615: start = index;
0616:
0617: while (index < end) {
0618: testChar = p_uriSpec.charAt(index);
0619:
0620: if (testChar == ':') {
0621: break;
0622: }
0623:
0624: index++;
0625: }
0626:
0627: host = p_uriSpec.substring(start, index);
0628:
0629: int port = -1;
0630:
0631: if (host.length() > 0) {
0632:
0633: // port
0634: if (testChar == ':') {
0635: index++;
0636:
0637: start = index;
0638:
0639: while (index < end) {
0640: index++;
0641: }
0642:
0643: String portStr = p_uriSpec.substring(start, index);
0644:
0645: if (portStr.length() > 0) {
0646: for (int i = 0; i < portStr.length(); i++) {
0647: if (!isDigit(portStr.charAt(i))) {
0648: throw new MalformedURIException(
0649: portStr
0650: + " is invalid. Port should only contain digits!");
0651: }
0652: }
0653:
0654: try {
0655: port = Integer.parseInt(portStr);
0656: } catch (NumberFormatException nfe) {
0657:
0658: // can't happen
0659: }
0660: }
0661: }
0662: }
0663:
0664: setHost(host);
0665: setPort(port);
0666: setUserinfo(userinfo);
0667: }
0668:
0669: /**
0670: * Initialize the path for this URI from a URI string spec.
0671: *
0672: * @param p_uriSpec the URI specification (cannot be null)
0673: *
0674: * @throws MalformedURIException if p_uriSpec violates syntax rules
0675: */
0676: private void initializePath(String p_uriSpec)
0677: throws MalformedURIException {
0678:
0679: if (p_uriSpec == null) {
0680: throw new MalformedURIException(
0681: "Cannot initialize path from null string!");
0682: }
0683:
0684: int index = 0;
0685: int start = 0;
0686: int end = p_uriSpec.length();
0687: char testChar = '\0';
0688:
0689: // path - everything up to query string or fragment
0690: while (index < end) {
0691: testChar = p_uriSpec.charAt(index);
0692:
0693: if (testChar == '?' || testChar == '#') {
0694: break;
0695: }
0696:
0697: // check for valid escape sequence
0698: if (testChar == '%') {
0699: if (index + 2 >= end
0700: || !isHex(p_uriSpec.charAt(index + 1))
0701: || !isHex(p_uriSpec.charAt(index + 2))) {
0702: throw new MalformedURIException(
0703: Utils.messages
0704: .createMessage(
0705: MsgKey.ER_PATH_CONTAINS_INVALID_ESCAPE_SEQUENCE,
0706: null)); //"Path contains invalid escape sequence!");
0707: }
0708: } else if (!isReservedCharacter(testChar)
0709: && !isUnreservedCharacter(testChar)) {
0710: if ('\\' != testChar)
0711: throw new MalformedURIException(Utils.messages
0712: .createMessage(MsgKey.ER_PATH_INVALID_CHAR,
0713: new Object[] { String
0714: .valueOf(testChar) })); //"Path contains invalid character: "
0715: //+ testChar);
0716: }
0717:
0718: index++;
0719: }
0720:
0721: m_path = p_uriSpec.substring(start, index);
0722:
0723: // query - starts with ? and up to fragment or end
0724: if (testChar == '?') {
0725: index++;
0726:
0727: start = index;
0728:
0729: while (index < end) {
0730: testChar = p_uriSpec.charAt(index);
0731:
0732: if (testChar == '#') {
0733: break;
0734: }
0735:
0736: if (testChar == '%') {
0737: if (index + 2 >= end
0738: || !isHex(p_uriSpec.charAt(index + 1))
0739: || !isHex(p_uriSpec.charAt(index + 2))) {
0740: throw new MalformedURIException(
0741: "Query string contains invalid escape sequence!");
0742: }
0743: } else if (!isReservedCharacter(testChar)
0744: && !isUnreservedCharacter(testChar)) {
0745: throw new MalformedURIException(
0746: "Query string contains invalid character:"
0747: + testChar);
0748: }
0749:
0750: index++;
0751: }
0752:
0753: m_queryString = p_uriSpec.substring(start, index);
0754: }
0755:
0756: // fragment - starts with #
0757: if (testChar == '#') {
0758: index++;
0759:
0760: start = index;
0761:
0762: while (index < end) {
0763: testChar = p_uriSpec.charAt(index);
0764:
0765: if (testChar == '%') {
0766: if (index + 2 >= end
0767: || !isHex(p_uriSpec.charAt(index + 1))
0768: || !isHex(p_uriSpec.charAt(index + 2))) {
0769: throw new MalformedURIException(
0770: "Fragment contains invalid escape sequence!");
0771: }
0772: } else if (!isReservedCharacter(testChar)
0773: && !isUnreservedCharacter(testChar)) {
0774: throw new MalformedURIException(
0775: "Fragment contains invalid character:"
0776: + testChar);
0777: }
0778:
0779: index++;
0780: }
0781:
0782: m_fragment = p_uriSpec.substring(start, index);
0783: }
0784: }
0785:
0786: /**
0787: * Get the scheme for this URI.
0788: *
0789: * @return the scheme for this URI
0790: */
0791: public String getScheme() {
0792: return m_scheme;
0793: }
0794:
0795: /**
0796: * Get the scheme-specific part for this URI (everything following the
0797: * scheme and the first colon). See RFC 2396 Section 5.2 for spec.
0798: *
0799: * @return the scheme-specific part for this URI
0800: */
0801: public String getSchemeSpecificPart() {
0802:
0803: StringBuffer schemespec = new StringBuffer();
0804:
0805: if (m_userinfo != null || m_host != null || m_port != -1) {
0806: schemespec.append("//");
0807: }
0808:
0809: if (m_userinfo != null) {
0810: schemespec.append(m_userinfo);
0811: schemespec.append('@');
0812: }
0813:
0814: if (m_host != null) {
0815: schemespec.append(m_host);
0816: }
0817:
0818: if (m_port != -1) {
0819: schemespec.append(':');
0820: schemespec.append(m_port);
0821: }
0822:
0823: if (m_path != null) {
0824: schemespec.append((m_path));
0825: }
0826:
0827: if (m_queryString != null) {
0828: schemespec.append('?');
0829: schemespec.append(m_queryString);
0830: }
0831:
0832: if (m_fragment != null) {
0833: schemespec.append('#');
0834: schemespec.append(m_fragment);
0835: }
0836:
0837: return schemespec.toString();
0838: }
0839:
0840: /**
0841: * Get the userinfo for this URI.
0842: *
0843: * @return the userinfo for this URI (null if not specified).
0844: */
0845: public String getUserinfo() {
0846: return m_userinfo;
0847: }
0848:
0849: /**
0850: * Get the host for this URI.
0851: *
0852: * @return the host for this URI (null if not specified).
0853: */
0854: public String getHost() {
0855: return m_host;
0856: }
0857:
0858: /**
0859: * Get the port for this URI.
0860: *
0861: * @return the port for this URI (-1 if not specified).
0862: */
0863: public int getPort() {
0864: return m_port;
0865: }
0866:
0867: /**
0868: * Get the path for this URI (optionally with the query string and
0869: * fragment).
0870: *
0871: * @param p_includeQueryString if true (and query string is not null),
0872: * then a "?" followed by the query string
0873: * will be appended
0874: * @param p_includeFragment if true (and fragment is not null),
0875: * then a "#" followed by the fragment
0876: * will be appended
0877: *
0878: * @return the path for this URI possibly including the query string
0879: * and fragment
0880: */
0881: public String getPath(boolean p_includeQueryString,
0882: boolean p_includeFragment) {
0883:
0884: StringBuffer pathString = new StringBuffer(m_path);
0885:
0886: if (p_includeQueryString && m_queryString != null) {
0887: pathString.append('?');
0888: pathString.append(m_queryString);
0889: }
0890:
0891: if (p_includeFragment && m_fragment != null) {
0892: pathString.append('#');
0893: pathString.append(m_fragment);
0894: }
0895:
0896: return pathString.toString();
0897: }
0898:
0899: /**
0900: * Get the path for this URI. Note that the value returned is the path
0901: * only and does not include the query string or fragment.
0902: *
0903: * @return the path for this URI.
0904: */
0905: public String getPath() {
0906: return m_path;
0907: }
0908:
0909: /**
0910: * Get the query string for this URI.
0911: *
0912: * @return the query string for this URI. Null is returned if there
0913: * was no "?" in the URI spec, empty string if there was a
0914: * "?" but no query string following it.
0915: */
0916: public String getQueryString() {
0917: return m_queryString;
0918: }
0919:
0920: /**
0921: * Get the fragment for this URI.
0922: *
0923: * @return the fragment for this URI. Null is returned if there
0924: * was no "#" in the URI spec, empty string if there was a
0925: * "#" but no fragment following it.
0926: */
0927: public String getFragment() {
0928: return m_fragment;
0929: }
0930:
0931: /**
0932: * Set the scheme for this URI. The scheme is converted to lowercase
0933: * before it is set.
0934: *
0935: * @param p_scheme the scheme for this URI (cannot be null)
0936: *
0937: * @throws MalformedURIException if p_scheme is not a conformant
0938: * scheme name
0939: */
0940: public void setScheme(String p_scheme) throws MalformedURIException {
0941:
0942: if (p_scheme == null) {
0943: throw new MalformedURIException(Utils.messages
0944: .createMessage(MsgKey.ER_SCHEME_FROM_NULL_STRING,
0945: null)); //"Cannot set scheme from null string!");
0946: }
0947:
0948: if (!isConformantSchemeName(p_scheme)) {
0949: throw new MalformedURIException(Utils.messages
0950: .createMessage(MsgKey.ER_SCHEME_NOT_CONFORMANT,
0951: null)); //"The scheme is not conformant.");
0952: }
0953:
0954: m_scheme = p_scheme.toLowerCase();
0955: }
0956:
0957: /**
0958: * Set the userinfo for this URI. If a non-null value is passed in and
0959: * the host value is null, then an exception is thrown.
0960: *
0961: * @param p_userinfo the userinfo for this URI
0962: *
0963: * @throws MalformedURIException if p_userinfo contains invalid
0964: * characters
0965: */
0966: public void setUserinfo(String p_userinfo)
0967: throws MalformedURIException {
0968:
0969: if (p_userinfo == null) {
0970: m_userinfo = null;
0971: } else {
0972: if (m_host == null) {
0973: throw new MalformedURIException(
0974: "Userinfo cannot be set when host is null!");
0975: }
0976:
0977: // userinfo can contain alphanumerics, mark characters, escaped
0978: // and ';',':','&','=','+','$',','
0979: int index = 0;
0980: int end = p_userinfo.length();
0981: char testChar = '\0';
0982:
0983: while (index < end) {
0984: testChar = p_userinfo.charAt(index);
0985:
0986: if (testChar == '%') {
0987: if (index + 2 >= end
0988: || !isHex(p_userinfo.charAt(index + 1))
0989: || !isHex(p_userinfo.charAt(index + 2))) {
0990: throw new MalformedURIException(
0991: "Userinfo contains invalid escape sequence!");
0992: }
0993: } else if (!isUnreservedCharacter(testChar)
0994: && USERINFO_CHARACTERS.indexOf(testChar) == -1) {
0995: throw new MalformedURIException(
0996: "Userinfo contains invalid character:"
0997: + testChar);
0998: }
0999:
1000: index++;
1001: }
1002: }
1003:
1004: m_userinfo = p_userinfo;
1005: }
1006:
1007: /**
1008: * Set the host for this URI. If null is passed in, the userinfo
1009: * field is also set to null and the port is set to -1.
1010: *
1011: * @param p_host the host for this URI
1012: *
1013: * @throws MalformedURIException if p_host is not a valid IP
1014: * address or DNS hostname.
1015: */
1016: public void setHost(String p_host) throws MalformedURIException {
1017:
1018: if (p_host == null || p_host.trim().length() == 0) {
1019: m_host = p_host;
1020: m_userinfo = null;
1021: m_port = -1;
1022: } else if (!isWellFormedAddress(p_host)) {
1023: throw new MalformedURIException(
1024: Utils.messages
1025: .createMessage(
1026: MsgKey.ER_HOST_ADDRESS_NOT_WELLFORMED,
1027: null)); //"Host is not a well formed address!");
1028: }
1029:
1030: m_host = p_host;
1031: }
1032:
1033: /**
1034: * Set the port for this URI. -1 is used to indicate that the port is
1035: * not specified, otherwise valid port numbers are between 0 and 65535.
1036: * If a valid port number is passed in and the host field is null,
1037: * an exception is thrown.
1038: *
1039: * @param p_port the port number for this URI
1040: *
1041: * @throws MalformedURIException if p_port is not -1 and not a
1042: * valid port number
1043: */
1044: public void setPort(int p_port) throws MalformedURIException {
1045:
1046: if (p_port >= 0 && p_port <= 65535) {
1047: if (m_host == null) {
1048: throw new MalformedURIException(Utils.messages
1049: .createMessage(MsgKey.ER_PORT_WHEN_HOST_NULL,
1050: null)); //"Port cannot be set when host is null!");
1051: }
1052: } else if (p_port != -1) {
1053: throw new MalformedURIException(Utils.messages
1054: .createMessage(MsgKey.ER_INVALID_PORT, null)); //"Invalid port number!");
1055: }
1056:
1057: m_port = p_port;
1058: }
1059:
1060: /**
1061: * Set the path for this URI. If the supplied path is null, then the
1062: * query string and fragment are set to null as well. If the supplied
1063: * path includes a query string and/or fragment, these fields will be
1064: * parsed and set as well. Note that, for URIs following the "generic
1065: * URI" syntax, the path specified should start with a slash.
1066: * For URIs that do not follow the generic URI syntax, this method
1067: * sets the scheme-specific part.
1068: *
1069: * @param p_path the path for this URI (may be null)
1070: *
1071: * @throws MalformedURIException if p_path contains invalid
1072: * characters
1073: */
1074: public void setPath(String p_path) throws MalformedURIException {
1075:
1076: if (p_path == null) {
1077: m_path = null;
1078: m_queryString = null;
1079: m_fragment = null;
1080: } else {
1081: initializePath(p_path);
1082: }
1083: }
1084:
1085: /**
1086: * Append to the end of the path of this URI. If the current path does
1087: * not end in a slash and the path to be appended does not begin with
1088: * a slash, a slash will be appended to the current path before the
1089: * new segment is added. Also, if the current path ends in a slash
1090: * and the new segment begins with a slash, the extra slash will be
1091: * removed before the new segment is appended.
1092: *
1093: * @param p_addToPath the new segment to be added to the current path
1094: *
1095: * @throws MalformedURIException if p_addToPath contains syntax
1096: * errors
1097: */
1098: public void appendPath(String p_addToPath)
1099: throws MalformedURIException {
1100:
1101: if (p_addToPath == null || p_addToPath.trim().length() == 0) {
1102: return;
1103: }
1104:
1105: if (!isURIString(p_addToPath)) {
1106: throw new MalformedURIException(Utils.messages
1107: .createMessage(MsgKey.ER_PATH_INVALID_CHAR,
1108: new Object[] { p_addToPath })); //"Path contains invalid character!");
1109: }
1110:
1111: if (m_path == null || m_path.trim().length() == 0) {
1112: if (p_addToPath.startsWith("/")) {
1113: m_path = p_addToPath;
1114: } else {
1115: m_path = "/" + p_addToPath;
1116: }
1117: } else if (m_path.endsWith("/")) {
1118: if (p_addToPath.startsWith("/")) {
1119: m_path = m_path.concat(p_addToPath.substring(1));
1120: } else {
1121: m_path = m_path.concat(p_addToPath);
1122: }
1123: } else {
1124: if (p_addToPath.startsWith("/")) {
1125: m_path = m_path.concat(p_addToPath);
1126: } else {
1127: m_path = m_path.concat("/" + p_addToPath);
1128: }
1129: }
1130: }
1131:
1132: /**
1133: * Set the query string for this URI. A non-null value is valid only
1134: * if this is an URI conforming to the generic URI syntax and
1135: * the path value is not null.
1136: *
1137: * @param p_queryString the query string for this URI
1138: *
1139: * @throws MalformedURIException if p_queryString is not null and this
1140: * URI does not conform to the generic
1141: * URI syntax or if the path is null
1142: */
1143: public void setQueryString(String p_queryString)
1144: throws MalformedURIException {
1145:
1146: if (p_queryString == null) {
1147: m_queryString = null;
1148: } else if (!isGenericURI()) {
1149: throw new MalformedURIException(
1150: "Query string can only be set for a generic URI!");
1151: } else if (getPath() == null) {
1152: throw new MalformedURIException(
1153: "Query string cannot be set when path is null!");
1154: } else if (!isURIString(p_queryString)) {
1155: throw new MalformedURIException(
1156: "Query string contains invalid character!");
1157: } else {
1158: m_queryString = p_queryString;
1159: }
1160: }
1161:
1162: /**
1163: * Set the fragment for this URI. A non-null value is valid only
1164: * if this is a URI conforming to the generic URI syntax and
1165: * the path value is not null.
1166: *
1167: * @param p_fragment the fragment for this URI
1168: *
1169: * @throws MalformedURIException if p_fragment is not null and this
1170: * URI does not conform to the generic
1171: * URI syntax or if the path is null
1172: */
1173: public void setFragment(String p_fragment)
1174: throws MalformedURIException {
1175:
1176: if (p_fragment == null) {
1177: m_fragment = null;
1178: } else if (!isGenericURI()) {
1179: throw new MalformedURIException(
1180: Utils.messages.createMessage(
1181: MsgKey.ER_FRAG_FOR_GENERIC_URI, null)); //"Fragment can only be set for a generic URI!");
1182: } else if (getPath() == null) {
1183: throw new MalformedURIException(Utils.messages
1184: .createMessage(MsgKey.ER_FRAG_WHEN_PATH_NULL, null)); //"Fragment cannot be set when path is null!");
1185: } else if (!isURIString(p_fragment)) {
1186: throw new MalformedURIException(Utils.messages
1187: .createMessage(MsgKey.ER_FRAG_INVALID_CHAR, null)); //"Fragment contains invalid character!");
1188: } else {
1189: m_fragment = p_fragment;
1190: }
1191: }
1192:
1193: /**
1194: * Determines if the passed-in Object is equivalent to this URI.
1195: *
1196: * @param p_test the Object to test for equality.
1197: *
1198: * @return true if p_test is a URI with all values equal to this
1199: * URI, false otherwise
1200: */
1201: public boolean equals(Object p_test) {
1202:
1203: if (p_test instanceof URI) {
1204: URI testURI = (URI) p_test;
1205:
1206: if (((m_scheme == null && testURI.m_scheme == null) || (m_scheme != null
1207: && testURI.m_scheme != null && m_scheme
1208: .equals(testURI.m_scheme)))
1209: && ((m_userinfo == null && testURI.m_userinfo == null) || (m_userinfo != null
1210: && testURI.m_userinfo != null && m_userinfo
1211: .equals(testURI.m_userinfo)))
1212: && ((m_host == null && testURI.m_host == null) || (m_host != null
1213: && testURI.m_host != null && m_host
1214: .equals(testURI.m_host)))
1215: && m_port == testURI.m_port
1216: && ((m_path == null && testURI.m_path == null) || (m_path != null
1217: && testURI.m_path != null && m_path
1218: .equals(testURI.m_path)))
1219: && ((m_queryString == null && testURI.m_queryString == null) || (m_queryString != null
1220: && testURI.m_queryString != null && m_queryString
1221: .equals(testURI.m_queryString)))
1222: && ((m_fragment == null && testURI.m_fragment == null) || (m_fragment != null
1223: && testURI.m_fragment != null && m_fragment
1224: .equals(testURI.m_fragment)))) {
1225: return true;
1226: }
1227: }
1228:
1229: return false;
1230: }
1231:
1232: /**
1233: * Get the URI as a string specification. See RFC 2396 Section 5.2.
1234: *
1235: * @return the URI string specification
1236: */
1237: public String toString() {
1238:
1239: StringBuffer uriSpecString = new StringBuffer();
1240:
1241: if (m_scheme != null) {
1242: uriSpecString.append(m_scheme);
1243: uriSpecString.append(':');
1244: }
1245:
1246: uriSpecString.append(getSchemeSpecificPart());
1247:
1248: return uriSpecString.toString();
1249: }
1250:
1251: /**
1252: * Get the indicator as to whether this URI uses the "generic URI"
1253: * syntax.
1254: *
1255: * @return true if this URI uses the "generic URI" syntax, false
1256: * otherwise
1257: */
1258: public boolean isGenericURI() {
1259:
1260: // presence of the host (whether valid or empty) means
1261: // double-slashes which means generic uri
1262: return (m_host != null);
1263: }
1264:
1265: /**
1266: * Determine whether a scheme conforms to the rules for a scheme name.
1267: * A scheme is conformant if it starts with an alphanumeric, and
1268: * contains only alphanumerics, '+','-' and '.'.
1269: *
1270: *
1271: * @param p_scheme The sheme name to check
1272: * @return true if the scheme is conformant, false otherwise
1273: */
1274: public static boolean isConformantSchemeName(String p_scheme) {
1275:
1276: if (p_scheme == null || p_scheme.trim().length() == 0) {
1277: return false;
1278: }
1279:
1280: if (!isAlpha(p_scheme.charAt(0))) {
1281: return false;
1282: }
1283:
1284: char testChar;
1285:
1286: for (int i = 1; i < p_scheme.length(); i++) {
1287: testChar = p_scheme.charAt(i);
1288:
1289: if (!isAlphanum(testChar)
1290: && SCHEME_CHARACTERS.indexOf(testChar) == -1) {
1291: return false;
1292: }
1293: }
1294:
1295: return true;
1296: }
1297:
1298: /**
1299: * Determine whether a string is syntactically capable of representing
1300: * a valid IPv4 address or the domain name of a network host. A valid
1301: * IPv4 address consists of four decimal digit groups separated by a
1302: * '.'. A hostname consists of domain labels (each of which must
1303: * begin and end with an alphanumeric but may contain '-') separated
1304: * & by a '.'. See RFC 2396 Section 3.2.2.
1305: *
1306: *
1307: * @param p_address The address string to check
1308: * @return true if the string is a syntactically valid IPv4 address
1309: * or hostname
1310: */
1311: public static boolean isWellFormedAddress(String p_address) {
1312:
1313: if (p_address == null) {
1314: return false;
1315: }
1316:
1317: String address = p_address.trim();
1318: int addrLength = address.length();
1319:
1320: if (addrLength == 0 || addrLength > 255) {
1321: return false;
1322: }
1323:
1324: if (address.startsWith(".") || address.startsWith("-")) {
1325: return false;
1326: }
1327:
1328: // rightmost domain label starting with digit indicates IP address
1329: // since top level domain label can only start with an alpha
1330: // see RFC 2396 Section 3.2.2
1331: int index = address.lastIndexOf('.');
1332:
1333: if (address.endsWith(".")) {
1334: index = address.substring(0, index).lastIndexOf('.');
1335: }
1336:
1337: if (index + 1 < addrLength
1338: && isDigit(p_address.charAt(index + 1))) {
1339: char testChar;
1340: int numDots = 0;
1341:
1342: // make sure that 1) we see only digits and dot separators, 2) that
1343: // any dot separator is preceded and followed by a digit and
1344: // 3) that we find 3 dots
1345: for (int i = 0; i < addrLength; i++) {
1346: testChar = address.charAt(i);
1347:
1348: if (testChar == '.') {
1349: if (!isDigit(address.charAt(i - 1))
1350: || (i + 1 < addrLength && !isDigit(address
1351: .charAt(i + 1)))) {
1352: return false;
1353: }
1354:
1355: numDots++;
1356: } else if (!isDigit(testChar)) {
1357: return false;
1358: }
1359: }
1360:
1361: if (numDots != 3) {
1362: return false;
1363: }
1364: } else {
1365:
1366: // domain labels can contain alphanumerics and '-"
1367: // but must start and end with an alphanumeric
1368: char testChar;
1369:
1370: for (int i = 0; i < addrLength; i++) {
1371: testChar = address.charAt(i);
1372:
1373: if (testChar == '.') {
1374: if (!isAlphanum(address.charAt(i - 1))) {
1375: return false;
1376: }
1377:
1378: if (i + 1 < addrLength
1379: && !isAlphanum(address.charAt(i + 1))) {
1380: return false;
1381: }
1382: } else if (!isAlphanum(testChar) && testChar != '-') {
1383: return false;
1384: }
1385: }
1386: }
1387:
1388: return true;
1389: }
1390:
1391: /**
1392: * Determine whether a char is a digit.
1393: *
1394: *
1395: * @param p_char the character to check
1396: * @return true if the char is betweeen '0' and '9', false otherwise
1397: */
1398: private static boolean isDigit(char p_char) {
1399: return p_char >= '0' && p_char <= '9';
1400: }
1401:
1402: /**
1403: * Determine whether a character is a hexadecimal character.
1404: *
1405: *
1406: * @param p_char the character to check
1407: * @return true if the char is betweeen '0' and '9', 'a' and 'f'
1408: * or 'A' and 'F', false otherwise
1409: */
1410: private static boolean isHex(char p_char) {
1411: return (isDigit(p_char) || (p_char >= 'a' && p_char <= 'f') || (p_char >= 'A' && p_char <= 'F'));
1412: }
1413:
1414: /**
1415: * Determine whether a char is an alphabetic character: a-z or A-Z
1416: *
1417: *
1418: * @param p_char the character to check
1419: * @return true if the char is alphabetic, false otherwise
1420: */
1421: private static boolean isAlpha(char p_char) {
1422: return ((p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z'));
1423: }
1424:
1425: /**
1426: * Determine whether a char is an alphanumeric: 0-9, a-z or A-Z
1427: *
1428: *
1429: * @param p_char the character to check
1430: * @return true if the char is alphanumeric, false otherwise
1431: */
1432: private static boolean isAlphanum(char p_char) {
1433: return (isAlpha(p_char) || isDigit(p_char));
1434: }
1435:
1436: /**
1437: * Determine whether a character is a reserved character:
1438: * ';', '/', '?', ':', '@', '&', '=', '+', '$' or ','
1439: *
1440: *
1441: * @param p_char the character to check
1442: * @return true if the string contains any reserved characters
1443: */
1444: private static boolean isReservedCharacter(char p_char) {
1445: return RESERVED_CHARACTERS.indexOf(p_char) != -1;
1446: }
1447:
1448: /**
1449: * Determine whether a char is an unreserved character.
1450: *
1451: *
1452: * @param p_char the character to check
1453: * @return true if the char is unreserved, false otherwise
1454: */
1455: private static boolean isUnreservedCharacter(char p_char) {
1456: return (isAlphanum(p_char) || MARK_CHARACTERS.indexOf(p_char) != -1);
1457: }
1458:
1459: /**
1460: * Determine whether a given string contains only URI characters (also
1461: * called "uric" in RFC 2396). uric consist of all reserved
1462: * characters, unreserved characters and escaped characters.
1463: *
1464: *
1465: * @param p_uric URI string
1466: * @return true if the string is comprised of uric, false otherwise
1467: */
1468: private static boolean isURIString(String p_uric) {
1469:
1470: if (p_uric == null) {
1471: return false;
1472: }
1473:
1474: int end = p_uric.length();
1475: char testChar = '\0';
1476:
1477: for (int i = 0; i < end; i++) {
1478: testChar = p_uric.charAt(i);
1479:
1480: if (testChar == '%') {
1481: if (i + 2 >= end || !isHex(p_uric.charAt(i + 1))
1482: || !isHex(p_uric.charAt(i + 2))) {
1483: return false;
1484: } else {
1485: i += 2;
1486:
1487: continue;
1488: }
1489: }
1490:
1491: if (isReservedCharacter(testChar)
1492: || isUnreservedCharacter(testChar)) {
1493: continue;
1494: } else {
1495: return false;
1496: }
1497: }
1498:
1499: return true;
1500: }
1501: }
|