--- /dev/null
+// Ssiframe.java\r
+// $Id: SSIFrame.java,v 1.2 2010/06/15 17:53:09 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.ssi ;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.PrintStream;\r
+import java.io.RandomAccessFile;\r
+\r
+import java.util.Dictionary;\r
+import java.util.Vector;\r
+\r
+import org.w3c.www.http.HTTP;\r
+import org.w3c.www.http.HeaderValue;\r
+import org.w3c.www.http.HttpDate;\r
+import org.w3c.www.http.HttpEntityMessage;\r
+import org.w3c.www.http.HttpInteger;\r
+import org.w3c.www.http.HttpMessage;\r
+import org.w3c.www.http.HttpReplyMessage;\r
+import org.w3c.www.http.HttpRequestMessage;\r
+\r
+import org.w3c.tools.resources.Attribute;\r
+import org.w3c.tools.resources.AttributeHolder;\r
+import org.w3c.tools.resources.AttributeRegistry;\r
+import org.w3c.tools.resources.BooleanAttribute;\r
+import org.w3c.tools.resources.ClassAttribute;\r
+import org.w3c.tools.resources.FileResource;\r
+import org.w3c.tools.resources.IntegerAttribute;\r
+import org.w3c.tools.resources.ProtocolException;\r
+import org.w3c.tools.resources.ReplyInterface;\r
+import org.w3c.tools.resources.RequestInterface;\r
+import org.w3c.tools.resources.Resource;\r
+import org.w3c.tools.resources.ResourceException;\r
+\r
+import org.w3c.tools.resources.event.AttributeChangedEvent;\r
+\r
+import org.w3c.jigsaw.http.HTTPException;\r
+import org.w3c.jigsaw.http.Reply;\r
+import org.w3c.jigsaw.http.Request;\r
+\r
+import org.w3c.jigsaw.frames.HTTPFrame;\r
+\r
+import org.w3c.util.ArrayDictionary;\r
+\r
+import org.w3c.jigsaw.ssi.commands.CommandRegistry;\r
+\r
+import org.w3c.tools.resources.ProtocolException;\r
+import org.w3c.tools.resources.ResourceException;\r
+\r
+/**\r
+ * This resource implements server-side parsing of HTML documents.\r
+ * Any comment of the form <code><!--#commandName param1=val1\r
+ * ... paramn=valn --></code> will be interpreted as an include\r
+ * directive.\r
+ * <p> Commands are looked up in an instance of the class\r
+ * supplied in the registryClass attribute, which must be a subclass\r
+ * of <code>org.w3c.jigsaw.ssi.CommandRegistry</code>.\r
+ *\r
+ * @author Antonio Ramirez <anto@mit.edu>\r
+ * @author Benoit Mahe <bmahe@sophia.inria.fr>\r
+ * @see org.w3c.jigsaw.ssi.commands.CommandRegistry\r
+ * @see org.w3c.jigsaw.ssi.commands.Command\r
+ */\r
+\r
+public class SSIFrame extends HTTPFrame {\r
+\r
+ public static final boolean debug = false ;\r
+\r
+ /** Attributes index - The segments */\r
+ private static int ATTR_SEGMENTS = -1 ;\r
+\r
+ /** Attributes index - The total unparsed size */\r
+ private static int ATTR_UNPARSED_SIZE = -1 ;\r
+\r
+ /** Attribute index - The class to use for making the CommandRegistry */\r
+ private static int ATTR_REGISTRY_CLASS = -1 ;\r
+\r
+ /** Attributes index - The maximum recursive parsing depth */\r
+ private static int ATTR_MAX_DEPTH = -1 ;\r
+\r
+ /** Attribute index - Whether or not to deny insecure commands */\r
+ private static int ATTR_SECURE = -1 ;\r
+\r
+ /**\r
+ * The command registry used by the resource.\r
+ */\r
+ private CommandRegistry commReg = null ;\r
+\r
+ /**\r
+ * The most specific class of the current command registry.\r
+ */\r
+ private Class regClass = null ;\r
+\r
+ /**\r
+ * Here we keep track of created registries to avoid\r
+ * making more than one per registry class.\r
+ */\r
+ private static Dictionary regs = new ArrayDictionary(5) ;\r
+\r
+ /** Will hold the increments for finding "<!--#" */\r
+ private static byte startIncrements[] = new byte[128] ;\r
+\r
+ /** Same thing for "-->" */\r
+ private static byte endIncrements[] = new byte[128] ;\r
+\r
+ /** The start pattern */\r
+ private static byte startPat[] =\r
+ {\r
+ (byte)'<',(byte)'!',(byte)'-',(byte)'-',(byte)'#'\r
+ };\r
+\r
+ /** The end pattern */\r
+ private static byte endPat[] =\r
+ {\r
+ (byte)'-',(byte)'-',(byte)'>'\r
+ };\r
+\r
+ // For value-less parameters \r
+ private static final String emptyString = "" ;\r
+\r
+ /**\r
+ * Our "very global" variables\r
+ */\r
+ protected Dictionary vars = null;\r
+\r
+ /**\r
+ * Message state - the current recursion depth\r
+ */\r
+ public static final String STATE_DEPTH =\r
+ "org.w3c.jigsaw.ssi.SSIResource.depth" ;\r
+\r
+ /**\r
+ * Message state - the current variables\r
+ */\r
+ public static final String STATE_VARIABLES =\r
+ "org.w3c.jigsaw.ssi.SSIResource.variables" ;\r
+\r
+ private boolean cacheReplies = true;\r
+\r
+ protected void doNotCacheReply() {\r
+ cacheReplies = false;\r
+ }\r
+\r
+ protected boolean cacheReplies() {\r
+ return cacheReplies;\r
+ }\r
+\r
+ /**\r
+ * Listen its resource.\r
+ */\r
+ public void attributeChanged(AttributeChangedEvent evt) {\r
+ super.attributeChanged(evt);\r
+ String name = evt.getAttribute().getName();\r
+ if ((name.equals("file-stamp")) || (name.equals("file-length"))) {\r
+ setValue(ATTR_SEGMENTS, (Segment[]) null);\r
+ }\r
+ }\r
+\r
+ private final int getUnparsedSize()\r
+ {\r
+ return getInt(ATTR_UNPARSED_SIZE,-1) ;\r
+ }\r
+\r
+ private final void setUnparsedSize(int unparsedSize)\r
+ {\r
+ setValue(ATTR_UNPARSED_SIZE,new Integer(unparsedSize)) ;\r
+ }\r
+\r
+ /**\r
+ * Makes sure that checkContent() is called on _any_ HTTP method,\r
+ * so that the internal representation of commands is always consistent.\r
+ * @param request The HTTPRequest\r
+ * @param filters The filters to apply\r
+ * @return a ReplyInterface instance\r
+ * @exception ProtocolException If processing the request failed.\r
+ * @exception ResourceException If this resource got a fatal error.\r
+ */\r
+\r
+ public ReplyInterface perform(RequestInterface request)\r
+ throws ProtocolException, ResourceException\r
+ {\r
+ if (! checkRequest(request)) {\r
+ return performFrames(request);\r
+ }\r
+ if (fresource != null)\r
+ fresource.checkContent();\r
+ return super.perform(request) ;\r
+ }\r
+\r
+ /**\r
+ * Perform a get (associated with a FileResource)\r
+ * @param request the HTTP request\r
+ * @return a Reply instance.\r
+ * @exception ProtocolException If processing the request failed.\r
+ * @exception ResourceException If this resource got a fatal error.\r
+ */\r
+ protected Reply getFileResource(Request request)\r
+ throws ProtocolException, ResourceException\r
+ {\r
+ Reply reply = handle(request) ;\r
+ return reply != null\r
+ ? reply\r
+ : super.getFileResource(request) ;\r
+ }\r
+\r
+ /**\r
+ * Perform a post.\r
+ * @param request the HTTP request\r
+ * @return a Reply instance.\r
+ * @exception ProtocolException If processing the request failed.\r
+ * @exception ResourceException If this resource got a fatal error.\r
+ */\r
+ public Reply post(Request request)\r
+ throws ProtocolException, ResourceException\r
+ {\r
+ Reply reply = handle(request) ;\r
+ return reply != null\r
+ ? reply\r
+ : super.post(request) ;\r
+ }\r
+\r
+ /**\r
+ * Handles all relevant HTTP methods.\r
+ * Merges the partial replies from each of the segments into\r
+ * one global reply.\r
+ * <strong>Remark</strong>: no direct relation to PostableResource.handle()\r
+ * @param request The HTTP request\r
+ * @return a Reply instance.\r
+ * @exception ProtocolException If processing the request failed.\r
+ */\r
+\r
+ public Reply handle(Request request)\r
+ throws ProtocolException\r
+ {\r
+ if (fresource == null)\r
+ return null;\r
+\r
+ if(SSIFrame.debug)\r
+ System.out.println("@@@@ handle: "+\r
+ (request.isInternal() \r
+ ? "internal" : "external") ) ;\r
+ fresource.checkContent() ;\r
+ \r
+ Integer depth =\r
+ (Integer) request.getState(STATE_DEPTH) ;\r
+ if(depth == null) depth = new Integer(0) ;\r
+\r
+ int unparsedSize = 0 ;\r
+\r
+ Segment[] segments = getSegments() ;\r
+ if(segments == null) {\r
+ parseFirstTime() ;\r
+ if( (segments = getSegments()) == null )\r
+ return null ; // Last resort: fall back to superclass\r
+ }\r
+ Reply reply = null ;\r
+ try {\r
+ // Obtain a command registry\r
+ updateRegistry() ;\r
+\r
+ vars = (Dictionary)\r
+ request.getState(STATE_VARIABLES) ;\r
+\r
+ // Initialize the registry-dependent variables:\r
+ vars = commReg.initVariables(this,request,vars) ;\r
+\r
+ // Add our "very global" variables\r
+ vars.put("secure",getValue(ATTR_SECURE,Boolean.TRUE)) ;\r
+ vars.put("maxDepth",getValue(ATTR_MAX_DEPTH,new Integer(10))) ;\r
+ vars.put("depth",depth) ;\r
+ vars.put("registry",commReg) ;\r
+\r
+ // Prepare the initial reply\r
+ // (which represents the unparsed parts of the document)\r
+ // and a prototype reply for segments that return null.\r
+ reply = createDefaultReply(request,HTTP.OK) ; \r
+ Reply defSegReply = createDefaultReply(request,HTTP.OK) ;\r
+\r
+ int unpSize = getUnparsedSize() ;\r
+ if(unpSize == -1) \r
+ reply.setHeaderValue(Reply.H_CONTENT_LENGTH,null) ;\r
+ else \r
+ reply.setContentLength(unpSize) ;\r
+ defSegReply.setHeaderValue(Reply.H_CONTENT_LENGTH,null) ;\r
+\r
+ long ims = request.getIfModifiedSince() ;\r
+ long cmt = fresource.getFileStamp() ; \r
+ // used to be getLastModified()\r
+ // should be something better\r
+ // than either\r
+ if(SSIFrame.debug)\r
+ System.out.println("@@@@ IMS: "+cmt+" vs "+ims) ;\r
+ if(ims != -1 && cmt != -1 && cmt <= ims) {\r
+ reply.setStatus(HTTP.NOT_MODIFIED) ;\r
+ defSegReply.setStatus(HTTP.NOT_MODIFIED) ;\r
+ } else if(ims != -1) {\r
+ if(SSIFrame.debug)\r
+ System.out.println("@@@@ Removed NOT MODIFIED") ;\r
+ }\r
+\r
+ \r
+ if(cmt != -1)\r
+ defSegReply.setLastModified(cmt) ;\r
+ \r
+ // For each segment:\r
+ // . obtain a reply,\r
+ // . merge its headers with the global reply's headers,\r
+ Reply[] partReps = new Reply[segments.length] ;\r
+ for(int i=0;i<segments.length;i++) {\r
+ if(!segments[i].isUnparsed()) {\r
+ if(SSIFrame.debug)\r
+ System.out.println("@@@@ Analyzing segment " +\r
+ segments[i]) ;\r
+\r
+ partReps[i] = segments[i].init(this, request, \r
+ vars, commReg, i);\r
+\r
+ if(SSIFrame.debug) {\r
+ if (partReps[i] == null)\r
+ System.out.println("@@@@ (null segment)") ;\r
+ System.out.println("@@@@ cacheReplies : "+\r
+ cacheReplies());\r
+ }\r
+ \r
+ merge(reply,\r
+ partReps[i] != null ? partReps[i] : defSegReply) ;\r
+ }\r
+ }\r
+\r
+ // Set a stream, unless we're not supposed to.\r
+ // Also handle the case of no command segments.\r
+ switch(reply.getStatus()) {\r
+ default:\r
+ reply.setStream\r
+ (new SSIStream(cacheReplies(),\r
+ segments,\r
+ partReps,\r
+ new RandomAccessFile(fresource.getFile(),\r
+ "r"))) ;\r
+ case HTTP.NO_CONTENT:\r
+ case HTTP.NOT_MODIFIED:\r
+ }\r
+\r
+ if(SSIFrame.debug)\r
+ System.out.println("@@@@ Last-modified: " +\r
+ reply.getLastModified()) ;\r
+\r
+ reply.setDate(System.currentTimeMillis()) ;\r
+ return reply ;\r
+\r
+ } catch(SSIException ex) {\r
+ reply = createDefaultReply(request,HTTP.INTERNAL_SERVER_ERROR) ;\r
+ reply.setContent("SSIFrame is misconfigured: "+\r
+ ex.getMessage());\r
+ throw new HTTPException(reply) ;\r
+ } catch(Exception ex) {\r
+ ex.printStackTrace() ;\r
+ if(SSIFrame.debug) {\r
+ if(SSIFrame.debug)\r
+ System.out.println("@@@@ Fallback to FileResource") ;\r
+ }\r
+ return null ; // Last resort: fall back to superclass\r
+ }\r
+ }\r
+\r
+ // The headers to merge and their corresponding callbacks\r
+ // (more to come)\r
+ private static final int mergeHeaders[] =\r
+ {\r
+ Reply.H_AGE,\r
+ Reply.H_CONTENT_LENGTH,\r
+ Reply.H_EXPIRES,\r
+ Reply.H_LAST_MODIFIED,\r
+ } ;\r
+\r
+ private static final Merger mergers[] =\r
+ {\r
+ new IntMaximizer(), // Reply.H_AGE\r
+ new IntAdder(), // Reply.H_CONTENT_LENGTH\r
+ new DateMinimizer(), // Reply.H_EXPIRES\r
+ new DateMaximizer() // Reply.H_LAST_MODIFIED\r
+ } ;\r
+\r
+ /**\r
+ * Merges the headers (and status code) of a segment's reply with\r
+ * those of the global reply.\r
+ *\r
+ * @param glob the global reply\r
+ * @param part the segment's partial reply\r
+ */\r
+ private void merge(Reply glob, Reply part)\r
+ {\r
+ // Deal with status code first\r
+ int pstat = part.getStatus() ;\r
+ int gstat = glob.getStatus() ;\r
+ \r
+ if(pstat == HTTP.NOT_MODIFIED) {\r
+ switch(gstat) {\r
+ default:\r
+ glob.setStatus(HTTP.OK) ;\r
+ case HTTP.NOT_MODIFIED:\r
+ }\r
+ } else if(gstat == HTTP.NOT_MODIFIED) {\r
+ if(SSIFrame.debug)\r
+ System.out.println("**** removed NOT MODIFIED") ;\r
+ glob.setStatus(HTTP.OK) ;\r
+ }\r
+ \r
+ // Now handle headers\r
+ // "pointers to methods" would make this simpler\r
+ for(int i=0;i<mergeHeaders.length;i++) \r
+ glob.setHeaderValue(mergeHeaders[i],\r
+ mergers[i]\r
+ .merge(glob.getHeaderValue(mergeHeaders[i]),\r
+ part.getHeaderValue(mergeHeaders[i]))) ;\r
+ \r
+ // Now handle annoying quasi-headers:\r
+ int pint,gint ;\r
+\r
+ // Cache-Control: max-age=n\r
+ // (don't merge if set as attribute)\r
+ if( getMaxAge() != -1 &&\r
+ (pint = part.getMaxAge()) != -1 ) {\r
+ if( (gint = glob.getMaxAge()) != -1)\r
+ pint = Math.min(gint,pint) ;\r
+ glob.setMaxAge(pint) ;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Retrieves the segments from the attribute\r
+ * @return An array of segments\r
+ */\r
+ private final Segment[] getSegments()\r
+ {\r
+ return (Segment[]) getValue(ATTR_SEGMENTS,null) ;\r
+ }\r
+ \r
+ /**\r
+ * Updates the working command registry if either the registryClass\r
+ * attribute has changed or it has never been created before.\r
+ * <p>To avoid unnecessarily creating command registry\r
+ * instances, this method will keep track of which kinds of command\r
+ * registries have been created, and avoid making duplicates.\r
+ * @exception SSIException If the operation can't be performed.\r
+ */\r
+ private void updateRegistry() \r
+ throws SSIException \r
+ {\r
+ try {\r
+ Class attrRegClass = (Class) getValue(ATTR_REGISTRY_CLASS, null) ;\r
+ if(attrRegClass == null)\r
+ attrRegClass = Class.forName\r
+ ("org.w3c.jigsaw.ssi.commands.DefaultCommandRegistry") ;\r
+ \r
+ if(regClass == null ||\r
+ !attrRegClass.equals(regClass)) {\r
+ regClass = attrRegClass ;\r
+ commReg = fetchRegistry(regClass) ;\r
+ }\r
+ } catch(ClassNotFoundException ex) {\r
+ throw new SSIException("Cannot make registry: "+ex.getMessage()) ;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns an instance of the given command registry class,\r
+ * either a new instance, or an old one if it exists in the dictionary.\r
+ * @exception SSIException If the operation can't be performed.\r
+ */\r
+ private CommandRegistry fetchRegistry(Class regClass)\r
+ throws SSIException\r
+ {\r
+ try {\r
+ CommandRegistry reg = (CommandRegistry) regs.get(regClass) ;\r
+ if(reg!=null) \r
+ return reg;\r
+ else {\r
+ reg = (CommandRegistry) regClass.newInstance();\r
+ regs.put(regClass,reg) ;\r
+ return reg;\r
+ }\r
+ } catch(Exception ex) {\r
+ throw new SSIException("Cannot fetch command registry: "+\r
+ ex.getMessage()) ;\r
+ }\r
+ }\r
+\r
+ /** Reads the unparsed file into memory, if not already done */\r
+ private byte[] readUnparsed()\r
+ throws IOException\r
+ {\r
+ File file = fresource.getFile();\r
+ ByteArrayOutputStream out =\r
+ new ByteArrayOutputStream((int)file.length()) ;\r
+ \r
+ FileInputStream in = new FileInputStream(file);\r
+\r
+ byte[] buf = new byte[4096] ;\r
+ int len = 0;\r
+ \r
+ while( (len = in.read(buf)) != -1) \r
+ out.write(buf,0,len);\r
+ \r
+ in.close();\r
+ out.close();\r
+ \r
+ byte[] unparsed = out.toByteArray() ;\r
+ return unparsed ;\r
+ }\r
+ \r
+\r
+ /**\r
+ * Does a first-time parse and sets the segment list attribute\r
+ * accordingly.\r
+ */\r
+ private void parseFirstTime()\r
+ {\r
+ if (debug)\r
+ System.out.println("@@@ parseFirstTime");\r
+ cacheReplies = true;\r
+ byte[] unparsed = null ;\r
+ try {\r
+ unparsed = readUnparsed() ;\r
+ } catch(IOException ex) {\r
+ setValue(ATTR_SEGMENTS,null) ;\r
+ return ;\r
+ }\r
+\r
+ // The parsing code was adapted from phttpd \r
+ \r
+ int byteIdx = 0, startInc, endInc, startParam, endParam, paramIdx,i ;\r
+ byte ch, quote ;\r
+ int max ,length = 0;\r
+ boolean valueFound ;\r
+ \r
+ int unparsedSize = 0 ;\r
+ \r
+ // For maintaining the segment list \r
+ Vector buildSegments = new Vector(20) ;\r
+\r
+ StringBuffer cmdBuf = null ;\r
+ String cmdName = null ;\r
+ Vector /*<String>*/ parNames = null ;\r
+ Vector /*<String>*/ parValues = null ;\r
+ String name = null , value = null ;\r
+ \r
+ // To store where the last segment ended\r
+ int lastSegEnd = 0;\r
+\r
+ do {\r
+ byteIdx += 4;\r
+ while(byteIdx < unparsed.length) {\r
+ if( (ch = unparsed[byteIdx]) == (byte) '#' )\r
+ if(byteArrayNEquals(unparsed, byteIdx-4,\r
+ startPat, 0,\r
+ 4)) {\r
+ break;\r
+ }\r
+\r
+ // This is an ugly work-around to the\r
+ // absence of unsigned bytes in Java.\r
+ byteIdx += startIncrements[ch>=0 ? ch : 0];\r
+ }\r
+\r
+ if(++byteIdx >= unparsed.length)\r
+ break; // Nothing found\r
+ \r
+ // Record the start of the command name and parameter list\r
+ startInc = (startParam = paramIdx = byteIdx) - 5 ;\r
+ \r
+ // Add the previous segment of unparsed text\r
+ // (Unless empty)\r
+ if(startInc > lastSegEnd) {\r
+ buildSegments.addElement(new Segment(lastSegEnd,\r
+ startInc));\r
+ unparsedSize += startInc - lastSegEnd ;\r
+ lastSegEnd = startInc ;\r
+ }\r
+\r
+ // Now find the end of the comment\r
+ byteIdx += 2;\r
+ while(byteIdx < unparsed.length) {\r
+ if( (ch = unparsed[byteIdx]) == (byte) '>')\r
+ if(unparsed[byteIdx-2] == (byte) '-' &&\r
+ unparsed[byteIdx-1] == (byte) '-')\r
+ break;\r
+\r
+ // This is an ugly work-around to the absence of\r
+ // unsigned bytes in Java:\r
+ byteIdx += endIncrements[ch>=0 ? ch : 0] ;\r
+ }\r
+ if(++byteIdx >= unparsed.length)\r
+ break; // No end found\r
+\r
+ // The end of the parameter list is 3 bytes earlier\r
+ endParam = byteIdx - 3 ;\r
+ \r
+ // Record the nominal end of the command segment\r
+ endInc = byteIdx ;\r
+\r
+ // Skip white space before command \r
+ while(paramIdx < endParam && isSpace(unparsed[paramIdx]) )\r
+ paramIdx++;\r
+ if( paramIdx >= endParam )\r
+ continue; // No command name\r
+ \r
+\r
+ max = endParam - paramIdx ;\r
+\r
+ cmdName = parseCmdName(unparsed,paramIdx,max) ;\r
+\r
+ // If not found, take this one as unparsed and\r
+ // search for the next include.\r
+ if(cmdName == null) {\r
+ buildSegments.addElement(new Segment(startInc,\r
+ endInc));\r
+ unparsedSize += endInc - startInc ;\r
+ lastSegEnd = endInc ;\r
+ continue;\r
+ }\r
+\r
+ parNames = new Vector(5) ;\r
+ parValues = new Vector(5) ;\r
+\r
+ parseCmdParams(unparsed,\r
+ paramIdx+cmdName.length(),\r
+ endParam,\r
+ parNames,parValues) ;\r
+\r
+ buildSegments.addElement( new Segment(this,\r
+ cmdName,\r
+ new\r
+ ArrayDictionary(parNames,\r
+ parValues),\r
+ lastSegEnd,\r
+ endInc)) ;\r
+ lastSegEnd = endInc ;\r
+ \r
+ } while(byteIdx < unparsed.length) ;\r
+ \r
+ \r
+ // Add the last chunk of unparsed text as a segment\r
+ buildSegments.addElement(new Segment(lastSegEnd,\r
+ unparsed.length));\r
+ unparsedSize += unparsed.length - lastSegEnd ;\r
+\r
+ setUnparsedSize(unparsedSize) ;\r
+ \r
+ Segment[] segs = new Segment[buildSegments.size()] ;\r
+ buildSegments.copyInto(segs) ;\r
+ setValue(ATTR_SEGMENTS,segs) ;\r
+ }\r
+\r
+ private final String parseCmdName(byte[] unparsed,int start,int max)\r
+ {\r
+ StringBuffer cmdBuf = new StringBuffer(80) ;\r
+ char ch ;\r
+ for(int i=0;i<max;i++) {\r
+ ch = (char) unparsed[start+i];\r
+ if(Character.isWhitespace(ch)) break ;\r
+ cmdBuf.append(ch) ;\r
+ }\r
+ return cmdBuf.length() == 0? null : cmdBuf.toString() ;\r
+ }\r
+\r
+ private final void parseCmdParams(byte[] unparsed,\r
+ int start,int end,\r
+ Vector names,Vector values)\r
+ {\r
+ String name = null ;\r
+ String value = null ;\r
+ int startParam = -1 ;\r
+\r
+ int paramIdx = start ;\r
+ while(paramIdx < end) {\r
+ while(isSpace(unparsed[paramIdx++]))\r
+ ;\r
+ if(paramIdx >= end)\r
+ break;\r
+ \r
+ byte ch = unparsed[--paramIdx];\r
+ startParam = paramIdx;\r
+ while(paramIdx < end\r
+ && !isSpace(ch)\r
+ && ch != (byte) '=')\r
+ ch = unparsed[++paramIdx];\r
+ \r
+ int length = paramIdx - startParam ;\r
+ if(length<=0) break;\r
+ \r
+ name = new String(unparsed,0,startParam,length) ;\r
+ value = emptyString ;\r
+ \r
+ boolean valueFound = false ;\r
+ while(isSpace(ch)\r
+ || ch == (byte) '=') {\r
+ if(ch == (byte) '=')\r
+ valueFound = true ;\r
+ ch = unparsed[++paramIdx] ;\r
+ }\r
+ \r
+ if(paramIdx >= end)\r
+ valueFound = false ;\r
+\r
+ byte quote ;\r
+ if(valueFound)\r
+ if(ch == '"' || ch == '\'' ) {\r
+ quote = ch ;\r
+ ch = unparsed[++paramIdx];\r
+ \r
+ startParam = paramIdx ;\r
+ while(paramIdx < end && ch != quote )\r
+ ch = unparsed[++paramIdx];\r
+ length = paramIdx - startParam ;\r
+ value = new String(unparsed,\r
+ 0,\r
+ startParam,\r
+ length);\r
+ paramIdx++ ; \r
+ } else {\r
+ startParam = paramIdx ;\r
+ while(paramIdx < end\r
+ && ! isSpace(ch))\r
+ ch = unparsed[++paramIdx];\r
+ length = paramIdx - startParam ;\r
+ value = new String (unparsed,\r
+ 0,\r
+ startParam,\r
+ length);\r
+ }\r
+ names.addElement(name) ;\r
+ values.addElement(value) ;\r
+ }\r
+ \r
+ }\r
+ \r
+ /**\r
+ * Analogous to standard C's <code>strncmp</code>, for byte arrays.\r
+ * (Should be in some utility package, I'll put it here for now)\r
+ * @param ba1 the first byte array\r
+ * @param off1 where to start in the first array\r
+ * @param ba2 the second byte array\r
+ * @param off2 where to start in the second array\r
+ * @param n the length to compare up to\r
+ * @return <strong>true</strong> if both specified parts of the arrays are\r
+ * equal, <strong>false</strong> if they aren't .\r
+ */\r
+ public static final boolean byteArrayNEquals(byte[] ba1, int off1,\r
+ byte[] ba2, int off2,\r
+ int n)\r
+ {\r
+ // So that only one addition is needed inside loop\r
+ int corr = off2 - off1;\r
+ int max = n+off1;\r
+ for(int i=off1;i<max;i++) \r
+ if(ba1[i] != ba2[i+corr])\r
+ return false;\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Does the same as Character.isSpace, without need to cast the\r
+ * byte into a char.\r
+ * @param ch the character\r
+ * @return whether or not ch is ASCII white space\r
+ * @see java.lang.Character#isSpace\r
+ */\r
+ private final boolean isSpace(byte ch)\r
+ {\r
+ return ch==' ' || ch=='\t' || ch=='\n' || ch=='\r' ;\r
+ }\r
+\r
+ public long getLastModified()\r
+ //FIXME\r
+ {\r
+ long a = super.getLastModified() ;\r
+ return a - a % 1000 ;\r
+ }\r
+ public final Reply createDefaultReply(Request request, int status)\r
+ {\r
+ Reply reply = super.createDefaultReply(request,status) ;\r
+ reply.setHeaderValue(Reply.H_LAST_MODIFIED,null) ;\r
+ reply.setKeepConnection(false);\r
+ return reply ;\r
+ }\r
+\r
+ public final Reply createCommandReply(Request request, int status)\r
+ {\r
+ return createDefaultReply(request,status) ;\r
+ }\r
+\r
+ static {\r
+ // Initialize search increments first\r
+ for(int i=0;i<128;i++) {\r
+ startIncrements[i] = 5 ;\r
+ endIncrements[i] = 3 ;\r
+ }\r
+ startIncrements[(int)('<')] = 4;\r
+ startIncrements[(int)('!')] = 3;\r
+ startIncrements[(int)('-')] = 1;\r
+ endIncrements[(int)('-')] = 1;\r
+\r
+ // Initialize attributes\r
+ Attribute a = null;\r
+ Class cls = null;\r
+ Class regClass = null ;\r
+\r
+ try {\r
+ cls = Class.forName("org.w3c.jigsaw.ssi.SSIFrame") ;\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ regClass = \r
+ Class.forName("org.w3c.jigsaw.ssi.commands.DefaultCommandRegistry") ;\r
+ } catch (Exception ex) {\r
+ ex.printStackTrace();\r
+ System.exit(0);\r
+ }\r
+\r
+ // The maxDepth attribute\r
+ a = new IntegerAttribute("maxDepth"\r
+ , new Integer(10)\r
+ , Attribute.EDITABLE) ;\r
+ ATTR_MAX_DEPTH = AttributeRegistry.registerAttribute(cls,a) ;\r
+\r
+ // The secure attribute\r
+ a = new BooleanAttribute("secure"\r
+ , Boolean.TRUE\r
+ , Attribute.EDITABLE) ;\r
+ ATTR_SECURE = AttributeRegistry.registerAttribute(cls,a) ;\r
+\r
+ // The segments attribute\r
+ a = new SegmentArrayAttribute("segments"\r
+ , null\r
+ , Attribute.COMPUTED) ;\r
+ ATTR_SEGMENTS = AttributeRegistry.registerAttribute(cls,a) ;\r
+\r
+ // The unparsedSize attribute\r
+ a = new IntegerAttribute("unparsedSize"\r
+ , null\r
+ , Attribute.COMPUTED) ;\r
+ ATTR_UNPARSED_SIZE = AttributeRegistry.registerAttribute(cls,a) ;\r
+\r
+ // The registryClass attribute\r
+ a = new ClassAttribute("registryClass"\r
+ , regClass\r
+ , Attribute.EDITABLE ) ;\r
+ ATTR_REGISTRY_CLASS = AttributeRegistry.registerAttribute(cls,a) ;\r
+\r
+ }\r
+\r
+}\r
+\r
+/**\r
+ * Merger classes are used to provide callbacks and make\r
+ * header merging more uniform. (Though it may be overkill...)\r
+ */\r
+abstract class Merger {\r
+ abstract HeaderValue merge(HeaderValue g,HeaderValue p) ;\r
+}\r
+\r
+class IntMaximizer extends Merger {\r
+ HeaderValue merge(HeaderValue g,HeaderValue p)\r
+ {\r
+ if(p != null) {\r
+ if(g != null) {\r
+ ((HttpInteger) g)\r
+ .setValue(Math.max( ((Integer) g.getValue()).intValue() ,\r
+ ((Integer) p.getValue()).intValue() )) ;\r
+ } else return p ;\r
+ }\r
+ return g ;\r
+ }\r
+} \r
+\r
+class IntMinimizer extends Merger {\r
+ HeaderValue merge(HeaderValue g,HeaderValue p)\r
+ {\r
+ if(p != null) {\r
+ if(g != null) {\r
+ ((HttpInteger) g)\r
+ .setValue(Math.min(((Integer) g.getValue()).intValue() ,\r
+ ((Integer) p.getValue()).intValue())) ;\r
+ } else return p ;\r
+ }\r
+ return g ;\r
+ }\r
+}\r
+\r
+class IntAdder extends Merger {\r
+ HeaderValue merge(HeaderValue g,HeaderValue p) {\r
+ if(SSIFrame.debug)\r
+ System.out.println("&&&& Adder: g="+g+", p="+p) ;\r
+ if(g != null) {\r
+ if(p != null) {\r
+ int b = ((Integer) g.getValue()).intValue() +\r
+ ((Integer) p.getValue()).intValue() ;\r
+\r
+ ((HttpInteger) g)\r
+ .setValue( b ) ;\r
+ } else return null ;\r
+ }\r
+ return g ;\r
+ }\r
+}\r
+\r
+class DateMinimizer extends Merger {\r
+ HeaderValue merge(HeaderValue g,HeaderValue p)\r
+ {\r
+ if(p != null) {\r
+ if(g != null) {\r
+ ((HttpDate) g)\r
+ .setValue(Math.min(((Long) g.getValue()).longValue() ,\r
+ ((Long) p.getValue()).longValue())) ;\r
+ } else return p ;\r
+ }\r
+ return g ;\r
+ }\r
+}\r
+class DateMaximizer extends Merger {\r
+ HeaderValue merge(HeaderValue g,HeaderValue p)\r
+ {\r
+ if(p != null) {\r
+ if(g != null) {\r
+ ((HttpDate) g)\r
+ .setValue(Math.max( ((Long) g.getValue()).longValue() ,\r
+ ((Long) p.getValue()).longValue())) ;\r
+ } else return p ;\r
+ }\r
+ return g ;\r
+ }\r
+}\r
+\r
+ \r