// 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 <em>never</em> 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 <strong>*</strong> for an <code>OPTIONS</code>
* 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 <strong>null</strong> 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 <em>clone</em> of
* the original request.
* <p>The original request and its clone differ in the following way:
* <ul>
* <li>The clone is marked as <em>internal</em>, which can be tested
* by the <code>isInternal</code> method.
* <li>The clone will keep a pointer to the first request that was
* cloned. This original request can be accessed by the <code>getOriginal
* </code> method.
* </ul>
* <p>To run an internal request, the caller can then use the <code>
* org.w3c.jigsaw.http.httpd</code> <code>perform</code> 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 <strong>-1</strong> 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 << 4) | lb)) ;
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 ;
}
}