root/usr/src/lib/libslp/javalib/com/sun/slp/ServiceURL.java
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 */

//  ServiceURL.java :  The service URL.
//  Author:           James Kempf, Erik Guttman
//

package com.sun.slp;

import java.util.*;
import java.io.*;
import java.net.*;

/**
 * The ServiceURL object models the SLP service URL. Both service: URLs
 * and regular URLs are handled by this class.
 *
 * @author James Kempf, Erik Guttman
 */

public class ServiceURL extends Object implements Serializable   {

    // Recognized transports.

    private final static String IPX = "ipx";
    private final static String AT = "at";

    /**
     * Indicates that no port information is required or was returned
     * for this service URL.
     */

    public static final int NO_PORT = 0;

    /**
     * No life time parameter is given.
     */

    public static final int LIFETIME_NONE    =  0;

    /**
     * Default lifetime, 3 hours.
     */

    public static final int LIFETIME_DEFAULT = 10800;

    /**
     * Maximum lifetime, approximately 18 hours.
     */

    public static final int LIFETIME_MAXIMUM = 0xFFFF;

    /**
     * Reregister periodically.
     */

    public static final int LIFETIME_PERMANENT = -1;

    // Maximum port size.

    static final int PORT_MAXIMUM = 0xFFFF;


    //
    // data fields
    //

    private ServiceType serviceType = null;
    private ServiceType originalServiceType = null;
    private String transport = "";
    private String host = "";
    private int port = NO_PORT;
    private String URLPath = "";
    private int lifetime = LIFETIME_DEFAULT;
    private boolean isPermanent = false;
    private boolean noDoubleSlash = false;

    /**
     * Construct a service URL object.
     *
     * @param URL               The service URL as a string.
     * @param iLifetime         The service advertisement lifetime.
     * @exception IllegalArgumentException Thrown if parse
     *                                    errors occur in the
     *                                    parameter.
     */

    public ServiceURL(String URL, int iLifetime)
        throws IllegalArgumentException {

        Assert.nonNullParameter(URL, "URL");

        if ((iLifetime > LIFETIME_MAXIMUM) ||
           (iLifetime < LIFETIME_PERMANENT)) {
            throw
                new IllegalArgumentException(
                SLPConfig.getSLPConfig().formatMessage("lifetime_error",
                                                       new Object[0]));
        }

        checkURLString(URL);
        parseURL(URL);

        if (iLifetime == LIFETIME_PERMANENT) {
            isPermanent = true;
            iLifetime = LIFETIME_MAXIMUM;

        }

        lifetime = iLifetime;
    }

    //
    // ------------------------------------------------------------------
    // Accessors
    // ------------------------------------------------------------------
    //

    /**
     * @return The service type name.
     */

    public ServiceType getServiceType() {
        return serviceType;

    }

    /**
     * Set service type and naming authority if this is not a service: URL.
     *
     * @param type The new ServiceType object.
     * @exception IllegalArgumentException If the service type name or
     *                                   naming authority name is invalid.
     */

    public void setServiceType(ServiceType type) {
        if (!serviceType.isServiceURL()) {
            serviceType = type;

        }

    }

    /**
     * @return The machine name or IP address.
     */

    public String getHost() {
        return host;

    }

    /**
     * @return The port number, if any.
     */

    public int getPort() {
        return port;

    }

    /**
     * @return The URL path description, if any.
     */

    public String getURLPath() {
        return URLPath;

    }

    /**
     * @return The service advertisement lifetime.
     */

    public int getLifetime() {
        return lifetime;

    }

    /**
     * Formats the service URL into standard URL form.
     *
     * @return Formatted string with the service URL.
     */

    public String toString() {  // Overrides Object.toString();

        return
            originalServiceType.toString() +
            ":/" + transport + (noDoubleSlash == false ? "/":"") +
            host + (port != NO_PORT ? (":" + port) : "") +
            URLPath;

    }

    public int hashCode() {
        return
            serviceType.hashCode() +
            transport.hashCode() +
            host.hashCode() +
            port +
            URLPath.hashCode();
    }

    public boolean equals(Object obj) {

        if (obj == this) {
            return true;

        }

        if (!(obj instanceof ServiceURL)) {
            return false;
        }

        ServiceURL surl = (ServiceURL)obj;

        return
            serviceType.equals(surl.serviceType) &&
            transport.equals(surl.transport) &&
            host.equals(surl.host) &&
            (port == surl.port) &&
            (noDoubleSlash == surl.noDoubleSlash) &&
            URLPath.equals(surl.URLPath);

    }

    // Return permanent status.

    boolean getIsPermanent() {
        return isPermanent;

    }

    // Check URL characters for correctness.

    private void checkURLString(String s)
        throws IllegalArgumentException {
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // allowed by RFC1738
            if (c == '/' || c == ':' || c == '-' || c == ':' ||
                c == '.' || c == '%' || c == '_' || c == '\'' ||
                c == '*' || c == '(' || c == ')' || c == '$' ||
                c == '!' || c == ',' || c == '+' || c == '\\') {
                                                        // defer to Windows
                continue;

            }

            // reserved by RFC1738, and thus allowed, pg. 20
            if (c == ';' || c == '@' || c == '?' || c == '&' || c == '=') {
                continue;

            }

            if (Character.isLetterOrDigit(c)) {
                continue;
            }

            SLPConfig conf = SLPConfig.getSLPConfig();

            throw
                new IllegalArgumentException(
                                conf.formatMessage("url_char_error",
                                                   new Object[] {
                                    Character.valueOf(c)}));
        }
    }

    // Parse the incoming service URL specification.

    private void parseURL(String sURL)
        throws IllegalArgumentException {

        StringTokenizer st = new StringTokenizer(sURL, "/", true);

        try {

            // This loop is a kludgy way to break out of the parse so
            //  we only throw at one location in the code.

            do {
                String typeName = st.nextToken();

                // First token must be service type name.

                if (typeName.equals("/")) {
                    break; // error!

                }

                // Check for colon terminator, not part of service
                // type name.

                if (!typeName.endsWith(":")) {
                    break; // error!

                }

                // Create service type, remove trailing colon.

                serviceType =
                    new ServiceType(typeName.substring(0,
                                                       typeName.length() - 1));
                originalServiceType = serviceType;

                // Separator between service type name and transport.

                String slash1 = st.nextToken();

                if (!slash1.equals("/")) {
                    break; // error!

                }

                String slash2 = st.nextToken();

                String sAddr = "";  // address...

                // Check for abstract type or alternate transport.

                if (!slash2.equals("/")) {

                    // If this is an abstract type, then we could have
                    //  something like: service:file-printer:file:/foo/bar.
                    //  This is OK. Also, if this is a non-service: URL,
                    //  something like file:/foo/bar is OK.

                    if (!serviceType.isServiceURL()) {
                        sAddr = slash2;

                        noDoubleSlash = true;

                    } else {

                        // We only recognize IPX and Appletalk at this point.

                        if (!slash2.equalsIgnoreCase(IPX) &&
                            !slash2.equalsIgnoreCase(AT)) {

                            // Abstract type is OK. We must check here because
                            //  something like
                            //  service:printing:lpr:/ipx/foo/bar
                            //  is allowed.

                            if (serviceType.isAbstractType()) {
                                sAddr = slash2;

                                noDoubleSlash = true;

                            } else {

                                break;  // error!

                            }
                        } else {

                            transport = slash2.toLowerCase();

                            // Check for separator between transport and host.

                            if (!st.nextToken().equals("/")) {
                                break; // error!

                            }

                            sAddr = st.nextToken();
                        }
                    }
                } else {

                    // Not abstract type, no alternate transport. Get host.

                    sAddr = st.nextToken();

                }

                if (sAddr.equals("/")) {// no host part
                    URLPath = "/" + st.nextToken("");
                    return; // we're done!

                }

                host = sAddr;

                // Need to check for port number if this is an IP transport.

                if (transport.equals("")) {
                    StringTokenizer tk = new StringTokenizer(host, ":");

                    host = tk.nextToken();

                    // Get port if any.

                    if (tk.hasMoreTokens()) {
                        String p = tk.nextToken();

                        if (tk.hasMoreTokens()) {
                            break; // error!

                        }

                        try {

                            port = Integer.parseInt(p);

                        } catch (NumberFormatException ex) {
                            break; // error!

                        }

                        if (port <= 0 || port > PORT_MAXIMUM) {
                            break; // error!

                        }
                    }
                }

                //
                // after this point we have to check if there is a token
                // remaining before we read it: It is legal to stop at any
                // point now.  Before all the tokens were required, so
                // missing any was an error.
                //
                if (st.hasMoreTokens() == false) {
                                        //  minimal url service:t:// a
                    return; // we're done!

                }

                String sSep  = st.nextToken();

                if (!sSep.equals("/")) {
                    break; // error!

                }

                // there is a URL path
                // URLPath is all remaining tokens
                URLPath = sSep;

                if (st.hasMoreTokens()) {
                    URLPath += st.nextToken("");

                }

                URLPath = URLPath.trim();

                return; // done!

            } while (false); // done with parse.

        } catch (NoSuchElementException ex) {
            throw
                new IllegalArgumentException(
                SLPConfig.getSLPConfig().formatMessage("url_syntax_error",
                                                       new Object[] {sURL}));

        }

        // The only way to get here is if there was an error in the
        //  parse.

        throw
            new IllegalArgumentException(
                SLPConfig.getSLPConfig().formatMessage("url_syntax_error",
                                                       new Object[] {sURL}));

    }

}