001: // LookupState.java
002: // $Id: LookupState.java,v 1.16 2004/02/02 11:58:55 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.tools.resources;
007:
008: import java.util.Vector;
009:
010: /**
011: * This object keeps the state info around while looking up an entity.
012: */
013:
014: public class LookupState {
015: private int index;
016: private String components[];
017: private String componentstype[];
018: private RequestInterface request;
019: private boolean is_directory = false;
020: private boolean is_internal = false;
021: private String uri = null;
022: private String query = null;
023: private String fragment = null;
024: private String type = null; // rfc1630 type as defined in ftp
025:
026: /**
027: * Unescape a escaped string
028: * @param s The string to be unescaped
029: * @return the unescaped string.
030: */
031:
032: public static String unescape(String s) {
033: int l = s.length();
034: boolean work = false;
035: // this one is to avoid allocating a string and a char array
036: // the cost is 3n tests, need to check that in the long run
037: for (int i = 0; i < l; i++) {
038: char c = s.charAt(i);
039: if ((c == '%') || (c == '+')) {
040: work = true;
041: break;
042: }
043: }
044: if (work) {
045: char cbuf[] = new char[l];
046: int pos = 0;
047: int ch = -1;
048: for (int i = 0; i < l; i++) {
049: switch (ch = s.charAt(i)) {
050: case '%':
051: ch = s.charAt(++i);
052: int hb = (Character.isDigit((char) ch) ? ch - '0'
053: : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
054: ch = s.charAt(++i);
055: int lb = (Character.isDigit((char) ch) ? ch - '0'
056: : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
057: // remove if equal to 0 FIXME for utf8
058: if (((hb << 4) | lb) > 0) {
059: cbuf[pos++] = (char) ((hb << 4) | lb);
060: }
061: break;
062: case '+':
063: cbuf[pos++] = ' ';
064: break;
065: default:
066: cbuf[pos++] = (char) ch;
067: }
068: }
069: return new String(cbuf, 0, pos);
070: }
071: return s;
072: }
073:
074: /**
075: * Parse the given URI into an array of hierarchical components.
076: * The optional query string and an optional fragment are recorded into
077: * the request as new fields.
078: * <p>The query string and the fragment are recorded into the request
079: * as the <strong>query</strong> and <strong>frag</strong> attributes.
080: * @exception ProtocolException if unable to parse
081: */
082: protected void parseURI() throws ProtocolException {
083: int urilen = uri.length();
084: int start = 0;
085: int slash = -1;
086: int t = -1;
087: Vector comps = new Vector(8);
088: int q = uri.indexOf('?', start);
089: int f = uri.indexOf('#', start);
090: int stop = -1;
091:
092: if ((q >= 0) && (f >= 0)) {
093: stop = Math.min(q, f);
094: } else if (q >= 0) {
095: stop = q;
096: } else if (f >= 0) {
097: stop = f;
098: } else {
099: stop = urilen;
100: }
101: if (stop < 0)
102: stop = urilen;
103: this .uri = uri;
104: loop: while (true) {
105: slash = uri.indexOf('/', start);
106: if ((slash >= stop) || (slash < 0)) {
107: break loop;
108: } else if (slash == start) {
109: start = slash + 1;
110: continue loop;
111: } else if (slash > 0) {
112: String part = unescape(uri.substring(start, slash));
113: // detect / and
114: t = part.indexOf(';');
115: if (t == -1) {
116: if (part.indexOf('/') != -1) {
117: // FIXME currently using unescaped string
118: String spa = uri.substring(start, slash);
119: comps.addElement(spa);
120: } else {
121: comps.addElement(part);
122: }
123: } else {
124: int sl = part.indexOf('/');
125: if (sl >= 0) {
126: if (t < sl) {
127: comps.addElement(part);
128: } else {
129: // FIXME currently using unescaped string
130: comps.addElement((uri.substring(start,
131: slash)));
132: }
133: }
134: }
135: start = slash + 1;
136: continue loop;
137: }
138: }
139: // Deal with any ? or # fragments:
140: if ((q >= 0) || (f >= 0)) {
141: if ((q >= 0) && (f > q)) {
142: // ?q#f
143: if (q + 1 < f)
144: this .query = uri.substring(q + 1, f);
145: if (f + 1 < urilen)
146: this .fragment = uri.substring(f + 1, urilen);
147: } else if ((f >= 0) && (q > f)) {
148: // #f?q
149: if (f + 1 < q)
150: this .fragment = uri.substring(f + 1, q);
151: if (q + 1 < urilen)
152: this .query = uri.substring(q + 1, urilen);
153: } else if (f >= 0) {
154: // #f
155: if (f + 1 < urilen)
156: this .fragment = uri.substring(f + 1, urilen);
157: } else if (q >= 0) {
158: // ?q
159: if (q + 1 < urilen)
160: this .query = uri.substring(q + 1, urilen);
161: }
162: }
163: // Update query states:
164: if (request != null) {
165: if (query != null)
166: request.setState("query", query);
167: if (fragment != null)
168: request.setState("frag", fragment);
169: }
170: // Keep track of last frament, and wrap up the result:
171: if (start < stop) {
172: comps.addElement(unescape(uri.substring(start, stop)));
173: }
174: if (--stop >= 0)
175: is_directory = (uri.charAt(stop) == '/');
176: components = new String[comps.size()];
177: componentstype = new String[comps.size()];
178: comps.copyInto(components);
179: // now cut possible comments inside the URL, per rfc 1630
180: for (int i = 0; i < components.length; i++) {
181: t = components[i].indexOf(';');
182: if (t >= 0) {
183: componentstype[i] = components[i].substring(t + 1);
184: components[i] = components[i].substring(0, t);
185: }
186: if (components[i].indexOf('/') >= 0) {
187: throw new ProtocolException(
188: "encoded % in URI are forbidden"
189: + " on this server");
190: }
191: }
192: index = 0;
193: }
194:
195: /**
196: * Get the fragment part of the URL, if any.
197: * The fragment is anything beyond the # character in a URL.
198: * @return A String instance, or <strong>null</strong>.
199: */
200:
201: public String getFragment() {
202: return fragment;
203: }
204:
205: /**
206: * Get the query part of the URL, if any.
207: * The query is anything beyond a ? character in a URL.
208: * @return A String instance, or <strong>null</strong>.
209: */
210:
211: public String getQuery() {
212: return query;
213: }
214:
215: /**
216: * Get the type part of the URL, if any.
217: * The type is anything beyond a ; character in a URL.
218: * @return A String instance, or <strong>null</strong>.
219: */
220:
221: public String getType() {
222: return componentstype[index];
223: }
224:
225: /**
226: * Is the requested URI a directory URI ?
227: * @return A boolean <strong>true</strong> if the requested URI ends with
228: * a slash, <strong>false</strong> otherwise.
229: */
230:
231: public boolean isDirectory() {
232: return is_directory;
233: }
234:
235: /**
236: * Get this lookpu state full URI.
237: */
238:
239: public String getURI() {
240: return uri;
241: }
242:
243: /**
244: * Get next part of the URL to be look for.
245: * @return A String giving the next component.
246: */
247:
248: public final String getNextComponent() {
249: if (request != null) {
250: request.setState("type", componentstype[index]);
251: }
252: return components[index++];
253: }
254:
255: /**
256: * Get the next component, without consuming it.
257: * @return A String giving the next component, or <strong>null</strong>
258: * if none is available.
259: */
260:
261: public final String peekNextComponent() {
262: if (index < components.length)
263: return components[index];
264: return null;
265: }
266:
267: /**
268: * Get the remaining path.
269: * @param consume If <strong>true</strong>, consume the components,
270: * otherwise, just peek them.
271: * @return A String giving the remaining URL.
272: */
273:
274: public final String getRemainingPath(boolean consume) {
275: StringBuffer sb = new StringBuffer();
276: for (int i = index; i < components.length; i++) {
277: sb.append("/" + components[i]);
278: }
279: if (consume)
280: index = components.length;
281: return sb.toString();
282: }
283:
284: /**
285: * Get the remaiing path, without consuming it.
286: * @return The remaining path.
287: */
288:
289: public final String getRemainingPath() {
290: return getRemainingPath(false);
291: }
292:
293: /**
294: * Does this look up state has more components to be looked for.
295: * @return <strong>true</strong> if more components are to be looked for.
296: */
297:
298: public boolean hasMoreComponents() {
299: return index < components.length;
300: }
301:
302: /**
303: * How much components have not yet been looked up in this state.
304: */
305:
306: public int countRemainingComponents() {
307: return components.length - index;
308: }
309:
310: /**
311: * Get this lookup state request.
312: * @return An instance of RequestInterface, or <strong>null</strong>
313: * if this is an internal request.
314: */
315:
316: public final RequestInterface getRequest() {
317: return request;
318: }
319:
320: /**
321: * Is this lookup state object associated with a request ?
322: * @return A boolean <strong>true</strong> if a request is associated.
323: */
324:
325: public boolean hasRequest() {
326: return (request != null);
327: }
328:
329: /**
330: * Mark this lookup state as being done internally.
331: * This allows lookup methods to be more kind (for example, not throwing
332: * redirections error, etc).
333: */
334:
335: public void markInternal() {
336: is_internal = true;
337: }
338:
339: /**
340: * Is this lookup state internal to the server.
341: * Internal lookup state may not have an associated request.
342: * @return A boolean <strong>true</strong> if this is an internal request.
343: */
344:
345: public boolean isInternal() {
346: return is_internal;
347: }
348:
349: /**
350: * Create a lookup state to handle the given request on behalf of client.
351: * @param client The client that issued the request.
352: * @param request The request whose URI is to bee looked up.
353: * @exception ProtocolException if an error relative to the protocol occurs
354: */
355:
356: public LookupState(RequestInterface request)
357: throws ProtocolException {
358: this .request = request;
359: this .uri = request.getURLPath();
360: this .is_internal = request.isInternal();
361: if (uri == null) {
362: ReplyInterface reply = request.makeBadRequestReply();
363: reply.setContent("Invalid URI (unparsable)");
364: throw new ProtocolException(reply);
365: }
366: parseURI();
367: }
368:
369: /**
370: * Construct a lookup state to be resolved internnaly by the server.
371: * This method allows for internal lookup of object, even if there is no
372: * real client making the request.
373: * @param uri The URI to be looked up.
374: * @exception ProtocolException if an error relative to the protocol occurs
375: */
376:
377: public LookupState(String uri) throws ProtocolException {
378: this .request = null;
379: this .is_internal = true;
380: this.uri = uri;
381: parseURI();
382: }
383:
384: }
|