001: // Request.java
002: // $Id: Request.java,v 1.37 2003/01/08 12:40:36 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.jigsaw.http;
007:
008: import java.io.IOException;
009: import java.io.InputStream;
010:
011: import java.net.URL;
012:
013: import org.w3c.www.mime.MimeParser;
014:
015: import org.w3c.www.http.ChunkedInputStream;
016: import org.w3c.www.http.ContentLengthInputStream;
017: import org.w3c.www.http.HTTP;
018: import org.w3c.www.http.HttpCredential;
019: import org.w3c.www.http.HttpEntityMessage;
020: import org.w3c.www.http.HttpFactory;
021: import org.w3c.www.http.HttpMessage;
022: import org.w3c.www.http.HttpParserException;
023: import org.w3c.www.http.HttpRequestMessage;
024:
025: import org.w3c.tools.resources.ReplyInterface;
026: import org.w3c.tools.resources.RequestInterface;
027: import org.w3c.tools.resources.ResourceFilter;
028: import org.w3c.tools.resources.ResourceReference;
029:
030: import org.w3c.tools.codec.Base64Encoder;
031:
032: /**
033: * this class extends HttpRequestMessage to cope with HTTP request.
034: * One subtely here: note how each field acessor <em>never</em> throws an
035: * exception, but rather is provided with a default value: this is in the hope
036: * that sometime, HTTP will not require all the parsing it requires right now.
037: */
038:
039: public class Request extends HttpRequestMessage implements
040: RequestInterface {
041: /**
042: * The URL that means <strong>*</strong> for an <code>OPTIONS</code>
043: * method.
044: */
045: public static URL THE_SERVER = null;
046:
047: /**
048: * the state of original URL
049: */
050: public static final String ORIG_URL_STATE = "org.w3c.jigsaw.http.Request.origurl";
051:
052: static {
053: try {
054: THE_SERVER = new URL("http://your.url.unknown");
055: } catch (Exception ex) {
056: ex.printStackTrace();
057: }
058: }
059:
060: protected Client client = null;
061: protected MimeParser parser = null;
062: protected InputStream in = null;
063: protected boolean keepcon = true;
064: boolean is_proxy = false;
065:
066: public void setState(String name, String state) {
067: super .setState(name, state);
068: }
069:
070: /**
071: * Fix the target URL of the request, this is the only good time to do so.
072: * @param parser The MimeParser
073: * @exception HttpParserException if parsing failed.
074: * @exception IOException if an IO error occurs.
075: */
076:
077: public void notifyEndParsing(MimeParser parser)
078: throws HttpParserException, IOException {
079: super .notifyEndParsing(parser);
080: String target = getTarget();
081: String url = null;
082: // Get rid of the nasty cases(there is a place in hell for someone)
083: if (target.equals("*")) {
084: setURL(THE_SERVER);
085: return;
086: }
087: // Is this a full http URL:
088: int at = -1;
089: int colon = target.indexOf(':');
090: String proto = (colon != -1) ? target.substring(0, colon)
091: : null;
092: if ((proto != null)
093: && (proto.equals("http") || proto.equals("ftp"))) {
094: // Good we have a full URL:
095: try {
096: // hugly hack, bug in URL for urls like:
097: // http://user:passwd@host:port/file
098: if ((at = target.indexOf('@', 6)) != -1) {
099: String auth = target.substring(colon + 3, at);
100: int sep = -1;
101: if ((auth.indexOf('/') == -1)
102: && ((sep = auth.indexOf(':')) != -1)) {
103: if (!hasAuthorization()) {
104: String username = auth.substring(0, sep);
105: String password = auth.substring(sep + 1);
106: HttpCredential credential = HttpFactory
107: .makeCredential("Basic");
108: Base64Encoder encoder = new Base64Encoder(
109: username + ":" + password);
110: credential.setAuthParameter("cookie",
111: encoder.processString());
112: setAuthorization(credential);
113: }
114: setURL(new URL(proto + "://"
115: + target.substring(at + 1)));
116: } else {
117: setURL(new URL(target));
118: }
119: } else {
120: setURL(new URL(target));
121: }
122: } catch (Exception ex) {
123: throw new HttpParserException(
124: "Bogus URL [" + url + "]", this );
125: }
126: } else {
127: try {
128: // Do we have a valid host header ?
129: String host = getHost();
130: if (host == null) {
131: // If this claims to be 1.1, tell him he's wrong:
132: if ((major == 1) && (minor >= 1))
133: throw new HttpParserException("No Host Header");
134: httpd server = getClient().getServer();
135: setURL(new URL("http", server.getHost(), server
136: .getPort(), target));
137: } else {
138: int ic = host.indexOf(':');
139: if (ic < 0) {
140: setURL(new URL("http", host, target));
141: } else {
142: setURL(new URL("http", host.substring(0, ic),
143: Integer
144: .parseInt(host
145: .substring(ic + 1)),
146: target));
147: }
148: }
149: } catch (Exception ex) {
150: throw new HttpParserException(
151: "Bogus URL [" + url + "]", this );
152: }
153: }
154: }
155:
156: // FIXME
157: // This guy should also check that the (optional) request stream has been
158: // exhausted.
159:
160: public boolean canKeepConnection() {
161: // HTTP/0.9 doesn't know about keeping connections alive:
162: if ((!keepcon) || (major < 1))
163: return false;
164: if (minor >= 1)
165: // HTTP/1.1 keeps connections alive by default
166: return hasConnection("close") ? false : true;
167: // For HTTP/1.0 check the [proxy] connection header:
168: if (is_proxy)
169: return hasProxyConnection("keep-alive");
170: else
171: return hasConnection("keep-alive");
172: }
173:
174: private ResourceReference target_resource = null;
175:
176: protected void setTargetResource(ResourceReference resource) {
177: target_resource = resource;
178: }
179:
180: /**
181: * Get this request target resource.
182: * @return An instance of HTTPResource, or <strong>null</strong> if
183: * not found.
184: */
185:
186: public ResourceReference getTargetResource() {
187: return target_resource;
188: }
189:
190: public void setProxy(boolean onoff) {
191: is_proxy = onoff;
192: }
193:
194: public boolean isProxy() {
195: return is_proxy;
196: }
197:
198: public String getURLPath() {
199: return url.getFile();
200: }
201:
202: public void setURLPath(String path) {
203: try {
204: url = new URL(url, path);
205: } catch (Exception ex) {
206: ex.printStackTrace();
207: }
208: }
209:
210: public boolean hasContentLength() {
211: return hasHeader(H_CONTENT_LENGTH);
212: }
213:
214: public boolean hasContentType() {
215: return hasHeader(H_CONTENT_TYPE);
216: }
217:
218: public boolean hasAccept() {
219: return hasHeader(H_ACCEPT);
220: }
221:
222: public boolean hasAcceptCharset() {
223: return hasHeader(H_ACCEPT_CHARSET);
224: }
225:
226: public boolean hasAcceptEncoding() {
227: return hasHeader(H_ACCEPT_ENCODING);
228: }
229:
230: public boolean hasAcceptLanguage() {
231: return hasHeader(H_ACCEPT_LANGUAGE);
232: }
233:
234: public boolean hasAuthorization() {
235: return hasHeader(H_AUTHORIZATION);
236: }
237:
238: public boolean hasProxyAuthorization() {
239: return hasHeader(H_PROXY_AUTHORIZATION);
240: }
241:
242: public String getQueryString() {
243: return (String) getState("query");
244: }
245:
246: public boolean hasQueryString() {
247: return hasState("query");
248: }
249:
250: protected boolean internal = false;
251:
252: public boolean isInternal() {
253: return internal;
254: }
255:
256: public void setInternal(boolean onoff) {
257: this .internal = onoff;
258: }
259:
260: protected Request original = null;
261:
262: public Request getOriginal() {
263: return original == null ? this : original;
264: }
265:
266: protected ResourceFilter filters[] = null;
267: protected int infilters = -1;
268:
269: protected void setFilters(ResourceFilter filters[], int infilters) {
270: this .filters = filters;
271: this .infilters = infilters;
272: }
273:
274: /**
275: * Clone this request, in order to launch an internal request.
276: * This method can be used to run a request in some given context, defined
277: * by an original request. It will preserve all the original information
278: * (such as authentication, etc), and will provide a <em>clone</em> of
279: * the original request.
280: * <p>The original request and its clone differ in the following way:
281: * <ul>
282: * <li>The clone is marked as <em>internal</em>, which can be tested
283: * by the <code>isInternal</code> method.
284: * <li>The clone will keep a pointer to the first request that was
285: * cloned. This original request can be accessed by the <code>getOriginal
286: * </code> method.
287: * </ul>
288: * <p>To run an internal request, the caller can then use the <code>
289: * org.w3c.jigsaw.http.httpd</code> <code>perform</code> method.
290: * @return A fresh Request instance, marked as internal.
291: */
292:
293: public HttpMessage getClone() {
294: Request cl = (Request) super .getClone();
295: cl.internal = true;
296: if (cl.original == null)
297: cl.original = this ;
298: return cl;
299: }
300:
301: /**
302: * Get this reply entity body.
303: * The reply entity body is returned as an InputStream, that the caller
304: * has to read to actually get the bytes of the content.
305: * @return An InputStream instance. If the reply has no body, the returned
306: * input stream will just return <strong>-1</strong> on first read.
307: */
308:
309: public InputStream getInputStream() throws IOException {
310: if (in != null)
311: return in;
312: // Find out which method is used to the length:
313: // first, chunked
314: String te[] = getTransferEncoding();
315: if (te != null) {
316: for (int i = 0; i < te.length; i++) {
317: if (te[i].equals("chunked"))
318: in = new ChunkedInputStream(parser.getInputStream());
319: }
320: }
321: // if not, content-length
322: int len = getContentLength();
323: if ((in == null) && (len >= 0)) {
324: in = new ContentLengthInputStream(parser.getInputStream(),
325: len);
326: }
327: // Handle broken HTTP/1.0 request
328: // It is mandatory for 1.1 requests to have been handled above.
329: if ((major == 1) && (minor == 0) && (in == null)) {
330: String m = getMethod();
331: if (m.equals("POST") || m.equals("PUT")) {
332: keepcon = false;
333: in = parser.getInputStream();
334: }
335: }
336: return in;
337: }
338:
339: /**
340: * Unescape a HTTP escaped string
341: * @param s The string to be unescaped
342: * @return the unescaped string.
343: */
344:
345: public static String unescape(String s) {
346: StringBuffer sbuf = new StringBuffer();
347: int l = s.length();
348: int ch = -1;
349: for (int i = 0; i < l; i++) {
350: switch (ch = s.charAt(i)) {
351: case '%':
352: ch = s.charAt(++i);
353: int hb = (Character.isDigit((char) ch) ? ch - '0'
354: : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
355: ch = s.charAt(++i);
356: int lb = (Character.isDigit((char) ch) ? ch - '0'
357: : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF;
358: sbuf.append((char) ((hb << 4) | lb));
359: break;
360: case '+':
361: sbuf.append(' ');
362: break;
363: default:
364: sbuf.append((char) ch);
365: }
366: }
367: return sbuf.toString();
368: }
369:
370: public ReplyInterface makeBadRequestReply() {
371: return makeReply(HTTP.BAD_REQUEST);
372: }
373:
374: /**
375: * Make an empty Reply object matching this request version.
376: * @param status The status of the reply.
377: */
378:
379: public Reply makeReply(int status) {
380: Reply reply = new Reply(client, this , getMajorVersion(),
381: getMinorVersion(), status);
382: if ((filters != null) && (infilters > 0))
383: reply.setFilters(filters, infilters);
384: return reply;
385: }
386:
387: /**
388: * skip the body
389: */
390: public void skipBody() {
391: // don't skip when there is a 100-Continue
392: if (getExpect() != null)
393: return;
394: try {
395: InputStream is = getInputStream();
396: int avail = is.available();
397:
398: while (avail > 0) {
399: is.skip(avail);
400: avail = is.available();
401: }
402: } catch (Exception ex) {// nothing to skip
403: }
404: }
405:
406: /**
407: * Get the client of this request.
408: */
409:
410: public Client getClient() {
411: return client;
412: }
413:
414: public Request(Client client, MimeParser parser) {
415: super (parser);
416: this .parser = parser;
417: this .client = client;
418: }
419:
420: /**
421: * Set this reply entity body.
422: * @param is the InputStream instance.
423: * USE CAREFULLY : need to be thread-safe
424: */
425: public void setStream(InputStream is) {
426: if (is != null)
427: this.in = is;
428: }
429: }
|