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