File:  [Public] / java / classes / org / w3c / tools / resources / LookupState.java
Revision 1.17: download - view: text, annotated - select for diffs
Sat Jun 16 15:48:48 2012 UTC (13 years, 6 months ago) by ylafon
Branches: MAIN
CVS tags: HEAD
various updates, use of StringBuilder, for each loops, starting some generics, some optims, ipv6 patch for acls

// LookupState.java
// $Id: LookupState.java,v 1.17 2012/06/16 15:48:48 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.tools.resources;

import java.util.ArrayList;

/**
 * This object keeps the state info around while looking up an entity.
 */

public class LookupState {
    private int index;
    private String components[];
    private String componentstype[];
    private RequestInterface request;
    private boolean is_directory = false;
    private boolean is_internal = false;
    private String uri = null;
    private String query = null;
    private String fragment = null;
    private String type = null; // rfc1630 type as defined in ftp

    /**
     * Unescape a escaped string
     *
     * @param s The string to be unescaped
     * @return the unescaped string.
     */

    public static String unescape(String s) {
        int l = s.length();
        boolean work = false;
        // this one is to avoid allocating a string and a char array
        // the cost is 3n tests, need to check that in the long run
        for (int i = 0; i < l; i++) {
            char c = s.charAt(i);
            if ((c == '%') || (c == '+')) {
                work = true;
                break;
            }
        }
        if (work) {
            char cbuf[] = new char[l];
            int pos = 0;
            int ch = -1;
            for (int i = 0; i < l; i++) {
                switch (ch = s.charAt(i)) {
                    case '%':
                        ch = s.charAt(++i);
                        int hb = (Character.isDigit((char) ch)
                                ? ch - '0'
                                : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
                        ch = s.charAt(++i);
                        int lb = (Character.isDigit((char) ch)
                                ? ch - '0'
                                : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
                        // remove if equal to 0 FIXME for utf8
                        if (((hb << 4) | lb) > 0) {
                            cbuf[pos++] = (char) ((hb << 4) | lb);
                        }
                        break;
                    case '+':
                        cbuf[pos++] = ' ';
                        break;
                    default:
                        cbuf[pos++] = (char) ch;
                }
            }
            return new String(cbuf, 0, pos);
        }
        return s;
    }


    /**
     * Parse the given URI into an array of hierarchical components.
     * The optional query string and an optional fragment are recorded into
     * the request as new fields.
     * <p>The query string and the fragment are recorded into the request
     * as the <strong>query</strong> and <strong>frag</strong> attributes.
     *
     * @throws ProtocolException if unable to parse
     */
    protected void parseURI()
            throws ProtocolException {
        int urilen = uri.length();
        int start = 0;
        int slash = -1;
        int t = -1;
        ArrayList<String> comps = new ArrayList<String>();
        int q = uri.indexOf('?', start);
        int f = uri.indexOf('#', start);
        int stop = -1;

        if ((q >= 0) && (f >= 0)) {
            stop = Math.min(q, f);
        } else if (q >= 0) {
            stop = q;
        } else if (f >= 0) {
            stop = f;
        } else {
            stop = urilen;
        }
        if (stop < 0)
            stop = urilen;
        this.uri = uri;
        loop:
        while (true) {
            slash = uri.indexOf('/', start);
            if ((slash >= stop) || (slash < 0)) {
                break loop;
            } else if (slash == start) {
                start = slash + 1;
                continue loop;
            } else if (slash > 0) {
                String part = unescape(uri.substring(start, slash));
                // detect / and
                t = part.indexOf(';');
                if (t == -1) {
                    if (part.indexOf('/') != -1) {
                        // FIXME currently using unescaped string
                        String spa = uri.substring(start, slash);
                        comps.add(spa);
                    } else {
                        comps.add(part);
                    }
                } else {
                    int sl = part.indexOf('/');
                    if (sl >= 0) {
                        if (t < sl) {
                            comps.add(part);
                        } else {
                            // FIXME currently using unescaped string
                            comps.add((uri.substring(start, slash)));
                        }
                    }
                }
                start = slash + 1;
                continue loop;
            }
        }
        // Deal with any ? or # fragments:
        if ((q >= 0) || (f >= 0)) {
            if ((q >= 0) && (f > q)) {
                // ?q#f
                if (q + 1 < f)
                    this.query = uri.substring(q + 1, f);
                if (f + 1 < urilen)
                    this.fragment = uri.substring(f + 1, urilen);
            } else if ((f >= 0) && (q > f)) {
                // #f?q
                if (f + 1 < q)
                    this.fragment = uri.substring(f + 1, q);
                if (q + 1 < urilen)
                    this.query = uri.substring(q + 1, urilen);
            } else if (f >= 0) {
                // #f
                if (f + 1 < urilen)
                    this.fragment = uri.substring(f + 1, urilen);
            } else if (q >= 0) {
                // ?q
                if (q + 1 < urilen)
                    this.query = uri.substring(q + 1, urilen);
            }
        }
        // Update query states:
        if (request != null) {
            if (query != null)
                request.setState("query", query);
            if (fragment != null)
                request.setState("frag", fragment);
        }
        // Keep track of last frament, and wrap up the result:
        if (start < stop) {
            comps.add(unescape(uri.substring(start, stop)));
        }
        if (--stop >= 0)
            is_directory = (uri.charAt(stop) == '/');
        components = comps.toArray(new String[comps.size()]);
        componentstype = new String[components.length];
        // now cut possible comments inside the URL, per rfc 1630
        for (int i = 0; i < components.length; i++) {
            t = components[i].indexOf(';');
            if (t >= 0) {
                componentstype[i] = components[i].substring(t + 1);
                components[i] = components[i].substring(0, t);
            }
            if (components[i].indexOf('/') >= 0) {
                throw new ProtocolException("encoded % in URI are forbidden" +
                        " on this server");
            }
        }
        index = 0;
    }

    /**
     * Get the fragment part of the URL, if any.
     * The fragment is anything beyond the # character in a URL.
     *
     * @return A String instance, or <strong>null</strong>.
     */

    public String getFragment() {
        return fragment;
    }

    /**
     * Get the query part of the URL, if any.
     * The query is anything beyond a ? character in a URL.
     *
     * @return A String instance, or <strong>null</strong>.
     */

    public String getQuery() {
        return query;
    }

    /**
     * Get the type part of the URL, if any.
     * The type is anything beyond a ; character in a URL.
     *
     * @return A String instance, or <strong>null</strong>.
     */

    public String getType() {
        return componentstype[index];
    }

    /**
     * Is the requested URI a directory URI ?
     *
     * @return A boolean <strong>true</strong> if the requested URI ends with
     *         a slash, <strong>false</strong> otherwise.
     */

    public boolean isDirectory() {
        return is_directory;
    }

    /**
     * Get this lookpu state full URI.
     */

    public String getURI() {
        return uri;
    }

    /**
     * Get next part of the URL to be look for.
     *
     * @return A String giving the next component.
     */

    public final String getNextComponent() {
        if (request != null) {
            request.setState("type", componentstype[index]);
        }
        return components[index++];
    }

    /**
     * Get the next component, without consuming it.
     *
     * @return A String giving the next component, or <strong>null</strong>
     *         if none is available.
     */

    public final String peekNextComponent() {
        if (index < components.length)
            return components[index];
        return null;
    }

    /**
     * Get the remaining path.
     *
     * @param consume If <strong>true</strong>, consume the components,
     *                otherwise, just peek them.
     * @return A String giving the remaining URL.
     */

    public final String getRemainingPath(boolean consume) {
        StringBuilder sb = new StringBuilder();
        for (int i = index; i < components.length; i++) {
            sb.append('/').append(components[i]);
        }
        if (consume)
            index = components.length;
        return sb.toString();
    }

    /**
     * Get the remaiing path, without consuming it.
     *
     * @return The remaining path.
     */

    public final String getRemainingPath() {
        return getRemainingPath(false);
    }

    /**
     * Does this look up state has more components to be looked for.
     *
     * @return <strong>true</strong> if more components are to be looked for.
     */

    public boolean hasMoreComponents() {
        return index < components.length;
    }

    /**
     * How much components have not yet been looked up in this state.
     */

    public int countRemainingComponents() {
        return components.length - index;
    }

    /**
     * Get this lookup state request.
     *
     * @return An instance of RequestInterface, or <strong>null</strong>
     *         if this is an internal request.
     */

    public final RequestInterface getRequest() {
        return request;
    }

    /**
     * Is this lookup state object associated with a request ?
     *
     * @return A boolean <strong>true</strong> if a request is associated.
     */

    public boolean hasRequest() {
        return (request != null);
    }

    /**
     * Mark this lookup state as being done internally.
     * This allows lookup methods to be more kind (for example, not throwing
     * redirections error, etc).
     */

    public void markInternal() {
        is_internal = true;
    }

    /**
     * Is this lookup state internal to the server.
     * Internal lookup state may not have an associated request.
     *
     * @return A boolean <strong>true</strong> if this is an internal request.
     */

    public boolean isInternal() {
        return is_internal;
    }

    /**
     * Create a lookup state to handle the given request on behalf of client.
     *
     * @param request The request whose URI is to bee looked up.
     * @throws ProtocolException if an error relative to the protocol occurs
     */

    public LookupState(RequestInterface request)
            throws ProtocolException {
        this.request = request;
        this.uri = request.getURLPath();
        this.is_internal = request.isInternal();
        if (uri == null) {
            ReplyInterface reply = request.makeBadRequestReply();
            reply.setContent("Invalid URI (unparsable)");
            throw new ProtocolException(reply);
        }
        parseURI();
    }

    /**
     * Construct a lookup state to be resolved internnaly by the server.
     * This method allows for internal lookup of object, even if there is no
     * real client making the request.
     *
     * @param uri The URI to be looked up.
     * @throws ProtocolException if an error relative to the protocol occurs
     */

    public LookupState(String uri)
            throws ProtocolException {
        this.request = null;
        this.is_internal = true;
        this.uri = uri;
        parseURI();
    }

}

Webmaster