// Request.java // $Id: Request.java,v 1.37 2003/01/08 12:40:36 ylafon Exp $ // (c) COPYRIGHT MIT and INRIA, 1996. // Please first read the full copyright statement in file COPYRIGHT.html package org.w3c.jigsaw.http ; import java.io.IOException; import java.io.InputStream; import java.net.URL; import org.w3c.www.mime.MimeParser; import org.w3c.www.http.ChunkedInputStream; import org.w3c.www.http.ContentLengthInputStream; import org.w3c.www.http.HTTP; import org.w3c.www.http.HttpCredential; import org.w3c.www.http.HttpEntityMessage; import org.w3c.www.http.HttpFactory; import org.w3c.www.http.HttpMessage; import org.w3c.www.http.HttpParserException; import org.w3c.www.http.HttpRequestMessage; import org.w3c.tools.resources.ReplyInterface; import org.w3c.tools.resources.RequestInterface; import org.w3c.tools.resources.ResourceFilter; import org.w3c.tools.resources.ResourceReference; import org.w3c.tools.codec.Base64Encoder; /** * this class extends HttpRequestMessage to cope with HTTP request. * One subtely here: note how each field acessor never throws an * exception, but rather is provided with a default value: this is in the hope * that sometime, HTTP will not require all the parsing it requires right now. */ public class Request extends HttpRequestMessage implements RequestInterface { /** * The URL that means * for an OPTIONS * method. */ public static URL THE_SERVER = null; /** * the state of original URL */ public static final String ORIG_URL_STATE = "org.w3c.jigsaw.http.Request.origurl"; static { try { THE_SERVER = new URL("http://your.url.unknown"); } catch (Exception ex) { ex.printStackTrace(); } } protected Client client = null; protected MimeParser parser = null; protected InputStream in = null; protected boolean keepcon = true; boolean is_proxy = false; public void setState(String name, String state) { super.setState(name, state); } /** * Fix the target URL of the request, this is the only good time to do so. * @param parser The MimeParser * @exception HttpParserException if parsing failed. * @exception IOException if an IO error occurs. */ public void notifyEndParsing(MimeParser parser) throws HttpParserException, IOException { super.notifyEndParsing(parser); String target = getTarget(); String url = null; // Get rid of the nasty cases(there is a place in hell for someone) if ( target.equals("*") ) { setURL(THE_SERVER); return; } // Is this a full http URL: int at = -1; int colon = target.indexOf(':'); String proto = (colon != -1) ? target.substring(0, colon) : null; if ((proto != null) && (proto.equals("http") || proto.equals("ftp"))) { // Good we have a full URL: try { // hugly hack, bug in URL for urls like: // http://user:passwd@host:port/file if ((at = target.indexOf('@',6)) != -1) { String auth = target.substring(colon+3,at); int sep = -1; if ((auth.indexOf('/') == -1) && ((sep = auth.indexOf(':')) != -1)) { if (! hasAuthorization()) { String username = auth.substring(0,sep); String password = auth.substring(sep+1); HttpCredential credential = HttpFactory.makeCredential("Basic"); Base64Encoder encoder = new Base64Encoder(username+":"+password); credential.setAuthParameter("cookie", encoder.processString()); setAuthorization(credential); } setURL(new URL(proto+ "://"+target.substring(at+1))); } else { setURL(new URL(target)); } } else { setURL(new URL(target)); } } catch (Exception ex) { throw new HttpParserException("Bogus URL ["+url+"]", this); } } else { try { // Do we have a valid host header ? String host = getHost(); if ( host == null ) { // If this claims to be 1.1, tell him he's wrong: if ((major == 1) && (minor >= 1)) throw new HttpParserException("No Host Header"); httpd server = getClient().getServer(); setURL(new URL("http" , server.getHost(), server.getPort() , target)); } else { int ic = host.indexOf(':'); if ( ic < 0 ) { setURL(new URL("http", host, target)); } else { setURL(new URL("http" , host.substring(0, ic) , Integer.parseInt( host.substring(ic+1)) , target)); } } } catch (Exception ex) { throw new HttpParserException("Bogus URL ["+url+"]", this); } } } // FIXME // This guy should also check that the (optional) request stream has been // exhausted. public boolean canKeepConnection() { // HTTP/0.9 doesn't know about keeping connections alive: if (( ! keepcon) || (major < 1)) return false; if ( minor >= 1 ) // HTTP/1.1 keeps connections alive by default return hasConnection("close") ? false : true; // For HTTP/1.0 check the [proxy] connection header: if ( is_proxy ) return hasProxyConnection("keep-alive"); else return hasConnection("keep-alive"); } private ResourceReference target_resource = null; protected void setTargetResource(ResourceReference resource) { target_resource = resource; } /** * Get this request target resource. * @return An instance of HTTPResource, or null if * not found. */ public ResourceReference getTargetResource() { return target_resource; } public void setProxy(boolean onoff) { is_proxy = onoff; } public boolean isProxy() { return is_proxy; } public String getURLPath() { return url.getFile(); } public void setURLPath(String path) { try { url = new URL(url, path); } catch (Exception ex) { ex.printStackTrace(); } } public boolean hasContentLength() { return hasHeader(H_CONTENT_LENGTH); } public boolean hasContentType() { return hasHeader(H_CONTENT_TYPE); } public boolean hasAccept() { return hasHeader(H_ACCEPT); } public boolean hasAcceptCharset() { return hasHeader(H_ACCEPT_CHARSET); } public boolean hasAcceptEncoding() { return hasHeader(H_ACCEPT_ENCODING); } public boolean hasAcceptLanguage() { return hasHeader(H_ACCEPT_LANGUAGE); } public boolean hasAuthorization() { return hasHeader(H_AUTHORIZATION); } public boolean hasProxyAuthorization() { return hasHeader(H_PROXY_AUTHORIZATION); } public String getQueryString() { return (String) getState("query"); } public boolean hasQueryString() { return hasState("query"); } protected boolean internal = false; public boolean isInternal() { return internal; } public void setInternal(boolean onoff) { this.internal = onoff; } protected Request original = null; public Request getOriginal() { return original == null ? this : original ; } protected ResourceFilter filters[] = null; protected int infilters = -1; protected void setFilters(ResourceFilter filters[], int infilters) { this.filters = filters; this.infilters = infilters; } /** * Clone this request, in order to launch an internal request. * This method can be used to run a request in some given context, defined * by an original request. It will preserve all the original information * (such as authentication, etc), and will provide a clone of * the original request. * The original request and its clone differ in the following way: * * The clone is marked as internal, which can be tested * by the isInternal method. * The clone will keep a pointer to the first request that was * cloned. This original request can be accessed by the getOriginal * method. * * To run an internal request, the caller can then use the * org.w3c.jigsaw.http.httpd perform method. * @return A fresh Request instance, marked as internal. */ public HttpMessage getClone() { Request cl = (Request) super.getClone(); cl.internal = true; if ( cl.original == null ) cl.original = this; return cl; } /** * Get this reply entity body. * The reply entity body is returned as an InputStream, that the caller * has to read to actually get the bytes of the content. * @return An InputStream instance. If the reply has no body, the returned * input stream will just return -1 on first read. */ public InputStream getInputStream() throws IOException { if ( in != null ) return in; // Find out which method is used to the length: // first, chunked String te[] = getTransferEncoding() ; if ( te != null ) { for (int i = 0 ; i < te.length ; i++) { if (te[i].equals("chunked")) in = new ChunkedInputStream( parser.getInputStream()); } } // if not, content-length int len = getContentLength(); if ( (in == null) && (len >= 0) ) { in = new ContentLengthInputStream(parser.getInputStream(),len); } // Handle broken HTTP/1.0 request // It is mandatory for 1.1 requests to have been handled above. if ((major == 1) && (minor == 0) && (in == null)) { String m = getMethod(); if (m.equals("POST") || m.equals("PUT")) { keepcon = false; in = parser.getInputStream(); } } return in; } /** * Unescape a HTTP escaped string * @param s The string to be unescaped * @return the unescaped string. */ public static String unescape (String s) { StringBuffer sbuf = new StringBuffer () ; int l = s.length() ; 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 ; sbuf.append ((char) ((hb break ; case '+': sbuf.append (' ') ; break ; default: sbuf.append ((char) ch) ; } } return sbuf.toString() ; } public ReplyInterface makeBadRequestReply() { return makeReply(HTTP.BAD_REQUEST); } /** * Make an empty Reply object matching this request version. * @param status The status of the reply. */ public Reply makeReply(int status) { Reply reply = new Reply(client , this , getMajorVersion() , getMinorVersion() , status); if ((filters != null) && (infilters > 0)) reply.setFilters(filters, infilters); return reply; } /** * skip the body */ public void skipBody() { // don't skip when there is a 100-Continue if (getExpect() != null) return; try { InputStream is = getInputStream(); int avail = is.available(); while (avail > 0) { is.skip(avail); avail = is.available(); } } catch (Exception ex) {// nothing to skip } } /** * Get the client of this request. */ public Client getClient() { return client ; } public Request (Client client, MimeParser parser) { super (parser); this.parser = parser; this.client = client ; } /** * Set this reply entity body. * @param is the InputStream instance. * USE CAREFULLY : need to be thread-safe */ public void setStream(InputStream is){ if (is != null) this.in = is ; } }