Adding JMCR-Stable version
[Benchmarks_CSolver.git] / JMCR-Stable / real-world application / jigsaw / src / org / w3c / jigsaw / ssi / SSIFrame.java
diff --git a/JMCR-Stable/real-world application/jigsaw/src/org/w3c/jigsaw/ssi/SSIFrame.java b/JMCR-Stable/real-world application/jigsaw/src/org/w3c/jigsaw/ssi/SSIFrame.java
new file mode 100644 (file)
index 0000000..8e4feb3
--- /dev/null
@@ -0,0 +1,939 @@
+// 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>&lt;!--#commandName param1=val1\r
+ * ... paramn=valn --&gt;</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