Unit tests, including the Watchdog converted to Junit.
authorcostin <costin@13f79535-47bb-0310-9956-ffa450edef68>
Sun, 5 Apr 2009 01:36:27 +0000 (01:36 +0000)
committercostin <costin@13f79535-47bb-0310-9956-ffa450edef68>
Sun, 5 Apr 2009 01:36:27 +0000 (01:36 +0000)
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@762039 13f79535-47bb-0310-9956-ffa450edef68

15 files changed:
modules/tomcat-lite/test/org/apache/tomcat/lite/LiteTestHelper.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogJspTests.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogServletTests.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/lite/PropertiesSpiTest.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/lite/SimpleServlet.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteNoConnectorTest.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteSimpleTest.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java [new file with mode: 0755]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/GTest.java [new file with mode: 0755]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java [new file with mode: 0755]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java [new file with mode: 0755]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTest.java [new file with mode: 0644]
modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java [new file with mode: 0644]

diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteTestHelper.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteTestHelper.java
new file mode 100644 (file)
index 0000000..66477df
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ */
+package org.apache.tomcat.lite;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.lite.coyote.CoyoteHttp;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class LiteTestHelper {
+
+    public static void initServletsAndRun(TomcatLite lite, int port) throws ServletException, IOException {
+        ServletContextImpl ctx = 
+          (ServletContextImpl) lite.addServletContext(null, null, "/test1");
+        
+        ctx.addServlet("test", new SimpleServlet());
+        ctx.addMapping("/1stTest", "test");
+
+        ctx.addServlet("testException", new HttpServlet() {
+            public void doGet(HttpServletRequest req, HttpServletResponse res) 
+              throws IOException {
+              throw new NullPointerException();
+            }
+          });
+          ctx.addMapping("/testException", "testException");
+        
+        lite.init();
+        lite.start(); 
+
+
+        if (port > 0) {
+            // This should be added after all local initialization to avoid
+            // the server from responding.
+            // Alternatively, you can load this early but set it to return
+            // 'unavailable' if load balancers depend on this.
+            addConnector(lite, port, true);
+            
+            // At this point we can add contexts and inject requests, if we want to 
+            // do it over HTTP need to start the connector as well.
+            lite.startConnector(); 
+        }
+    }
+    
+    public static void addConnector(TomcatLite lite, 
+                                    int port, boolean daemon) { 
+        CoyoteHttp coyoteAdapter = (CoyoteHttp) lite.getConnector();
+        coyoteAdapter.getConnectors().setPort(port);
+        coyoteAdapter.getConnectors().setDaemon(daemon);
+    }
+    
+    /**
+     *  Get url using URLConnection.
+     */
+    public static ByteChunk getUrl(String path) throws IOException {
+    
+        ByteChunk out = new ByteChunk();
+        
+        URL url = new URL(path);
+        URLConnection connection = url.openConnection();
+        connection.setReadTimeout(5000);
+        connection.connect();
+        InputStream is = connection.getInputStream();
+        BufferedInputStream bis = new BufferedInputStream(is);
+        byte[] buf = new byte[2048];
+        int rd = 0;
+        while((rd = bis.read(buf)) > 0) {
+            out.append(buf, 0, rd);
+        }
+        return out;
+    }
+
+    /** 
+     * Create a ServletRequestImpl object that can be used with 
+     *  TomcatLite.service(request).
+     *  
+     * All output will be added to the ByteChunk out.
+     * 
+     * This requires no HTTP connector.
+     * 
+     * @see TomcatLiteNoConnector
+     */
+    public static ServletRequestImpl createMessage(TomcatLite lite, 
+                                                   String uri,
+                                                   final ByteChunk out) {
+       ServletRequestImpl req = lite.createMessage();
+       req.setRequestURI(uri);
+       ServletResponseImpl res = req.getResponse();
+       res.getCoyoteResponse().setOutputBuffer(
+                   new ByteChunkOutputBuffer(out));
+       return req;
+     }
+    
+
+    static class ByteChunkOutputBuffer implements OutputBuffer {
+        
+        protected ByteChunk output = null;
+    
+        public ByteChunkOutputBuffer(ByteChunk output) {
+          this.output = output;
+        }
+    
+        public int doWrite(ByteChunk chunk, Response response) 
+            throws IOException {
+          output.append(chunk);
+          return chunk.getLength();
+        }
+    }
+    
+        
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogJspTests.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogJspTests.java
new file mode 100644 (file)
index 0000000..b14dfca
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.lite;
+
+
+import junit.framework.Test;
+
+public class LiteWatchdogJspTests  extends LiteWatchdogServletTests {
+    
+    public LiteWatchdogJspTests() {
+        super();
+        testMatch = 
+            //"precompileNegativeTest";
+            null;
+        // Test we know are failing - need to fix at some point.
+        exclude = new String[] {
+                "negativeDuplicateExtendsFatalTranslationErrorTest",
+                "negativeDuplicateErrorPageFatalTranslationErrorTest",
+                "negativeDuplicateInfoFatalTranslationErrorTest",
+                "negativeDuplicateLanguageFatalTranslationErrorTest",
+                "negativeDuplicateSessionFatalTranslationErrorTest",
+                "positiveIncludeCtxRelativeHtmlTest",
+                "precompileNegativeTest"
+            }; 
+        file = base + "/src/conf/jsp-gtest.xml";
+        goldenDir = 
+            base + "/src/clients/org/apache/jcheck/jsp/client/";
+        targetMatch = "jsp-test";
+        
+    }
+    
+    public static Test suite() {
+        return new LiteWatchdogJspTests().getSuite();
+    }
+    
+}
+
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogServletTests.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogServletTests.java
new file mode 100644 (file)
index 0000000..a7b7f64
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.lite;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.tomcat.test.watchdog.WatchdogClient;
+
+
+public class LiteWatchdogServletTests extends WatchdogClient {
+  
+  
+  public LiteWatchdogServletTests() {
+      goldenDir = base + "/src/clients/org/apache/jcheck/servlet/client/";
+      testMatch = 
+          //"HttpServletResponseWrapperSetStatusMsgTest";
+          //"ServletContextAttributeAddedEventTest";
+          null;
+          // ex: "ServletToJSP";
+      file = base + "/src/conf/servlet-gtest.xml";
+      targetMatch = "gtestservlet-test";
+  }
+  
+  protected void beforeSuite() {
+      // required for the tests
+      System.setProperty("org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER",
+              "true");
+      String path = System.getProperty("watchdog.home");
+      if (path != null) {
+          base = path;
+      }
+      
+      try {
+          initServerWithWatchdog(base);
+      } catch (ServletException e) {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+      } catch (IOException e) {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+      }
+  }
+  
+  public void initServerWithWatchdog(String wdDir) throws ServletException, 
+          IOException {
+
+      File f = new File(wdDir + "/build/webapps");
+      
+      //CoyoteServer connector = new CoyoteServer();
+      //connector.addAdapter("/", new MapperAdapter());
+
+      TomcatLite liteServer = new TomcatLite();
+      LiteTestHelper.addConnector(liteServer, 8080, true);
+      liteServer.init("webapps/ROOT", "/");
+
+      for (String s : new String[] {      
+              "servlet-compat", 
+              "servlet-tests",
+              "jsp-tests"} ) {
+          liteServer.init(f.getCanonicalPath() + "/" + s, 
+                        "/" + s);
+      }
+
+      //connector.init();
+      liteServer.init();
+      liteServer.start();
+
+      liteServer.startConnector();
+  }
+
+  
+  
+  protected void afterSuite(TestResult res) {
+      // no need to stop it - using daemon threads.
+      System.err.println("DONE");
+  }
+  
+  
+  /** 
+   * Magic JUnit method 
+   */
+  public static Test suite() {
+      // The individual targets are dups - and bad ones, 
+      // RequestWrapper are missing part of the URL
+      return new LiteWatchdogServletTests().getSuite();
+  }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/PropertiesSpiTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/PropertiesSpiTest.java
new file mode 100644 (file)
index 0000000..53a8ad5
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ */
+package org.apache.tomcat.lite;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.integration.simple.SimpleObjectManager;
+
+
+public class PropertiesSpiTest extends TestCase {
+
+    SimpleObjectManager spi;
+    
+    public void setUp() {
+        spi = new SimpleObjectManager();
+        
+        spi.getProperties().put("obj1.name", "foo");
+        spi.getProperties().put("obj1.(class)", BoundObj.class.getName());
+        
+    }
+    
+    public void testArgs() throws IOException { 
+        Properties res = new Properties();
+        SimpleObjectManager.processArgs(new String[] {
+            "-a=1", "-b", "2"}, res);
+        
+        assertEquals("1", res.get("a"));
+        assertEquals("2", res.get("b"));
+        
+        
+    }
+    
+    public static class BoundObj {
+        String name;
+        
+        public void setName(String n) {
+            this.name = n;
+        }
+    }
+    
+    public void testBind() throws Exception {
+        BoundObj bo = new BoundObj();
+        spi.bind("obj1", bo);
+        assertEquals(bo.name, "foo");        
+    }
+    
+    public void testCreate() throws Exception {
+        BoundObj bo = (BoundObj) spi.get("obj1");
+        assertEquals(bo.name, "foo");
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/SimpleServlet.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/SimpleServlet.java
new file mode 100644 (file)
index 0000000..fd90ddf
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ */
+package org.apache.tomcat.lite;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class SimpleServlet extends HttpServlet {
+  public void doGet(HttpServletRequest req, HttpServletResponse res) 
+  throws IOException {
+    res.setHeader("Foo", "Bar");
+    res.getWriter().write("Hello world");
+  }
+  public void doPost(HttpServletRequest req, HttpServletResponse res) 
+      throws IOException {
+    res.setHeader("Foo", "Post");
+    res.getWriter().write("Hello post world");
+  }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteNoConnectorTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteNoConnectorTest.java
new file mode 100644 (file)
index 0000000..30d944f
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.lite;
+
+
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TomcatLiteNoConnectorTest extends TestCase {
+
+  TomcatLite lite = new TomcatLite();
+  
+  public void setUp() throws Exception {
+      LiteTestHelper.initServletsAndRun(lite, 0);
+  }
+  
+  public void tearDown() throws Exception {
+    lite.stop();
+  }
+  
+
+  
+  public void testSimpleRequest() throws Exception {
+    ByteChunk out = new ByteChunk();
+    
+    ServletRequestImpl req = 
+        LiteTestHelper.createMessage(lite, "/test1/1stTest", out);
+    
+    // more changes can be made to the req, populate fields that a 
+    // connector would
+    
+    ServletResponseImpl res = lite.service(req);
+    
+    assertEquals("Hello world", out.toString());
+    // Headers are still in the response
+    assertEquals(res.getHeader("Foo"), "Bar");
+    assertEquals(res.getStatus(), 200);
+  }
+
+  public void testPostRequest() throws Exception {
+    ByteChunk out = new ByteChunk();
+    ServletRequestImpl req = 
+      LiteTestHelper.createMessage(lite, "/test1/1stTest", out);
+    req.setMethod("POST");
+
+    ServletResponseImpl res = lite.service(req);
+
+    assertEquals("Hello post world", out.toString());
+    // Headers are still in the response
+    assertEquals(res.getHeader("Foo"), "Post");
+    assertEquals(res.getStatus(), 200);
+  }
+  
+  public void testException() throws IOException, Exception {
+    ByteChunk out = new ByteChunk();
+    ServletRequestImpl req = 
+        LiteTestHelper.createMessage(lite, "/test1/testException", out);
+    ServletResponseImpl res = lite.service(req);
+    assertEquals(res.getStatus(), 500);
+  }
+  
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteSimpleTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteSimpleTest.java
new file mode 100644 (file)
index 0000000..3b0b534
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.lite;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+public class TomcatLiteSimpleTest extends TestCase {
+
+  TomcatLite lite = new TomcatLite(); 
+  
+  void initServer() throws Exception {
+    LiteTestHelper.initServletsAndRun(lite, 8804);
+  }
+  
+  public void setUp() throws Exception {
+    initServer();
+  }
+  
+  public void tearDown() throws Exception {
+    lite.stop();
+  }
+  
+  public void testSimpleRequest() throws Exception {
+    ByteChunk resb = 
+        LiteTestHelper.getUrl("http://localhost:8804/test1/1stTest");
+    assertTrue(resb.length() > 0);
+    assertEquals("Hello world", resb.toString());
+  }
+
+  
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java
new file mode 100755 (executable)
index 0000000..f6d0699
--- /dev/null
@@ -0,0 +1,640 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a collection of Cookie instances. 
+ * <p>
+ * Fires events when the cookies have been changed internally. Deals
+ * with management of cookies in terms of saving and loading them,
+ * and disabling them.
+ *
+ * @author     Ramesh.Mandava 
+ */
+public class CookieController {
+
+//    private VetoableChangeSupport vceListeners;
+
+    private static Hashtable cookieJar = new Hashtable();
+
+    /* public no arg constructor for bean */
+    public CookieController() {
+    }
+
+/////////////////////////////////////////////////////////////
+ /**
+     * Records any cookies which have been sent as part of an HTTP response.
+     * The connection parameter must be already have been opened, so that
+     * the response headers are available.  It's ok to pass a non-HTTP
+     * URL connection, or one which does not have any set-cookie headers.
+     */
+    public void recordAnyCookies(Vector rcvVectorOfCookies , URL url ) {
+
+       if ((rcvVectorOfCookies == null) || ( rcvVectorOfCookies.size()== 0) ) {
+           // no headers here
+           return;
+       }       
+       try {
+       /*
+        Properties properties = new Properties(); 
+       FileInputStream fin = new FileInputStream("ServerAutoRun.properties");
+       properties.load(fin);
+      
+       String cookiepolicy = properties.getProperty("cookie.acceptpolicy");
+       if (cookiepolicy == null || cookiepolicy.equals("none")) {
+           return;
+       }
+       */
+
+
+
+
+       for (int hi = 0; hi<rcvVectorOfCookies.size(); hi++) {
+
+               String cookieValue = (String)rcvVectorOfCookies.elementAt(hi) ;
+               recordCookie(url, cookieValue); // What to do here
+           }
+
+       }
+       catch( Exception e )
+       {
+               System.out.println("exception : " + e );
+       }
+    }
+
+
+    /**
+     * Create a cookie from the cookie, and use the HttpURLConnection to
+     * fill in unspecified values in the cookie with defaults.
+     */
+    public void recordCookie(URL url,
+                                    String cookieValue) {
+       HttpCookie cookie = new HttpCookie(url, cookieValue);
+       
+       // First, check to make sure the cookie's domain matches the
+       // server's, and has the required number of '.'s
+       String twodot[]=
+           {"com", "edu", "net", "org", "gov", "mil", "int"};
+       String domain = cookie.getDomain();
+       if( domain == null )
+           return;
+       int index = domain.indexOf(':');
+       if (index != -1) {
+           int portCookie;
+           try {
+               portCookie = (Integer.valueOf(domain.substring(index+1,domain.length()))).intValue();
+           } catch (Exception e) {
+               return;
+           }
+           portCookie = ( portCookie == -1 ) ? 80 : portCookie;
+           domain=domain.substring(0,index);
+       }
+       domain.toLowerCase();
+       
+       String host = url.getHost();
+       host.toLowerCase();
+
+       boolean domainOK = host.equals(domain);
+       if( !domainOK && host.endsWith( domain ) ) {
+           int dotsNeeded = 2;
+           for( int i = 0; i < twodot.length; i++ ) {
+               if( domain.endsWith( twodot[i] ) ) {
+                   dotsNeeded = 1;
+               }
+           }
+
+           int lastChar = domain.length();
+           for( ; lastChar > 0 && dotsNeeded > 0; dotsNeeded-- ) {
+               lastChar = domain.lastIndexOf( '.', lastChar-1 );
+           }
+
+           if( lastChar > 0 )
+               domainOK = true;
+       }
+
+       if( domainOK ) {
+           recordCookie(cookie);
+
+       }
+    }
+
+
+    /**
+     * Record the cookie in the in-memory container of cookies.  If there
+     * is already a cookie which is in the exact same domain with the
+     * exact same
+     */
+    public void recordCookie(HttpCookie cookie) {
+       if (!checkIfCookieOK(cookie)) {
+           return;
+       }
+       synchronized (cookieJar) {
+           
+           String domain = cookie.getDomain().toLowerCase();
+
+           Vector cookieList = (Vector)cookieJar.get(domain);
+           if (cookieList == null) {
+               cookieList = new Vector();
+           }
+
+           addOrReplaceCookie(cookieList, cookie);
+           cookieJar.put(domain, cookieList);
+           
+       }
+
+    }
+
+    public boolean checkIfCookieOK(HttpCookie cookie) {
+       return true;
+    }
+
+    /**
+     * Scans the vector of cookies looking for an exact match with the
+     * given cookie.  Replaces it if there is one, otherwise adds
+     * one at the end.  The vector is presumed to have cookies which all
+     * have the same domain, so the domain of the cookie is not checked.
+     * <p>
+     * <p>
+     * If this is called, it is assumed that the cookie jar is exclusively
+     * held by the current thread.
+     *
+     */
+    private void addOrReplaceCookie(Vector cookies,
+                                      HttpCookie cookie) {
+       int numCookies = cookies.size();
+
+       String path = cookie.getPath();
+       String name = cookie.getName();
+       HttpCookie replaced = null;
+       int replacedIndex = -1;
+
+       for (int i = 0; i < numCookies; i++) {
+           HttpCookie existingCookie = (HttpCookie)cookies.elementAt(i);
+       
+           String existingPath = existingCookie.getPath();
+           if (path.equals(existingPath)) {
+               String existingName = existingCookie.getName();
+               if (name.equals(existingName)) {
+                   // need to replace this one!
+                   replaced = existingCookie;
+                   replacedIndex = i;
+                   break;
+               }
+           }
+       }
+       
+       
+       // Do the replace - if cookie has already expired, remove 
+       // the replaced cookie. 
+       if (replaced != null) {
+           if (cookie.isSaveableInMemory()) {
+               cookies.setElementAt(cookie, replacedIndex);
+               //System.out.println("REPLACED existing cookie with " + cookie);
+           } else {
+               cookies.removeElementAt(replacedIndex);
+               //System.out.println("Removed cookie b/c or expr " + cookie);
+           }
+
+       } else { // only save the cookie in memory if it is non persistent
+                // or not expired.
+           if (cookie.isSaveableInMemory()) {
+               cookies.addElement(cookie);
+               //System.out.println("RECORDED new cookie " + cookie);
+           } 
+
+       }
+
+    }
+
+    public String applyRelevantCookies(URL url ) {
+
+       try {   
+               /*
+               Properties properties = new Properties(); 
+               FileInputStream fin = new FileInputStream("ServerAutoRun.properties");
+               properties.load(fin);
+               // check current accept policy instead enableCookies
+               String cookiepolicy = properties.getProperty("cookie.acceptpolicy");
+               if (cookiepolicy == null || cookiepolicy.equals("none")) {
+                   return null;
+               }
+
+               */
+
+               return applyCookiesForHost(url);
+
+       }
+       catch ( Exception e )
+       {
+               System.out.println("Exception : " +e );
+               return null;
+       }
+      
+
+
+
+    }
+
+    
+   /**
+     * Host may be a FQDN, or a partial domain name starting with a dot.
+     * Adds any cookies which match the host and path to the
+     * cookie set on the URL connection.
+     */
+    private String applyCookiesForHost(URL url ){
+       String cookieString = null;
+       Vector cookieVector = getAllRelevantCookies(url);
+       
+       if (cookieVector != null) {
+
+           for (Enumeration e = cookieVector.elements(); e.hasMoreElements();) {
+               HttpCookie cookie = (HttpCookie)e.nextElement();
+               if( cookieString == null ) {
+                   cookieString = cookie.getNameValue();
+               } else {
+                   cookieString = cookieString + "; " + cookie.getNameValue();
+               }
+           }
+           
+        /*
+
+           if( cookieString != null ) {
+               httpConn.setRequestProperty("Cookie", cookieString);
+        
+//             System.out.println("Returned cookie string: " + cookieString + " for HOST = " + host);
+            }
+
+         */
+
+
+       }
+//             System.out.println("Returned cookie string: " + cookieString + " for HOST = " + host);
+       return cookieString;
+       
+    }
+
+    private Vector getAllRelevantCookies(URL url) {
+       String host = url.getHost();
+       Vector cookieVector = getSubsetRelevantCookies(host, url);
+
+       Vector tempVector;
+       int index;
+
+       while ((index = host.indexOf('.', 1)) >= 0) {
+           // trim off everything up to, and including the dot.
+           host = host.substring(index+1);
+           
+            // add onto cookieVector
+           tempVector = getSubsetRelevantCookies(host,url);
+           if (tempVector != null ) {
+               for (Enumeration e = tempVector.elements(); e.hasMoreElements(); ) {
+                   if (cookieVector == null) {
+                       cookieVector = new Vector(2);
+                   }
+
+                   cookieVector.addElement(e.nextElement());
+
+               }
+           }
+       }
+       return cookieVector;
+    }
+
+    private Vector getSubsetRelevantCookies(String host, URL url) {
+
+       Vector cookieList = (Vector)cookieJar.get(host);
+       
+//     System.out.println("getRelevantCookies() .. for host, url " + host +", "+url);
+       Vector cookiePortList = (Vector)cookieJar.get(host+":"+((url.getPort() == -1) ? 80 : url.getPort()));
+       if (cookiePortList != null) {
+           if (cookieList == null) {
+               cookieList = new Vector(10);
+           }
+           Enumeration cookies = cookiePortList.elements(); 
+           while (cookies.hasMoreElements()) {
+               cookieList.addElement(cookies.nextElement());
+           }  
+       }
+       
+           
+       if (cookieList == null) {
+           return null;
+       }
+
+       String path = url.getFile();
+//     System.out.println("        path is " + path + "; protocol = " + url.getProtocol());
+
+
+       int queryInd = path.indexOf('?');
+       if (queryInd > 0) {
+           // strip off the part following the ?
+           path = path.substring(0, queryInd);
+       }
+
+       Enumeration cookies = cookieList.elements();
+       Vector cookiesToSend = new Vector(10);
+
+       while (cookies.hasMoreElements()) {
+           HttpCookie cookie = (HttpCookie)cookies.nextElement();
+       
+           String cookiePath = cookie.getPath();
+
+           if (path.startsWith(cookiePath)) {
+               // larrylf: Actually, my documentation (from Netscape)
+               // says that /foo should
+               // match /foobar and /foo/bar.  Yuck!!!
+
+               if (!cookie.hasExpired()) {
+                   cookiesToSend.addElement(cookie);
+               }
+
+/*
+   We're keeping this piece of commented out code around just in
+   case we decide to put it back.  the spec does specify the above,
+   but it is so disgusting!
+
+               int cookiePathLen = cookiePath.length();
+
+               // verify that /foo does not match /foobar by mistake
+               if ((path.length() == cookiePathLen)
+                   || (path.length() > cookiePathLen &&
+                       path.charAt(cookiePathLen) == '/')) {
+               
+                   // We have a matching cookie!
+
+                   if (!cookie.hasExpired()) {
+                       cookiesToSend.addElement(cookie);
+                   }
+               }
+*/
+           }
+       }
+
+       // Now, sort the cookies in most to least specific order
+       // Yes, its the deaded bubblesort!! Wah Ha-ha-ha-ha....
+       // (it should be a small vector, so perf is not an issue...)
+       if( cookiesToSend.size() > 1 ) {
+           for( int i = 0; i < cookiesToSend.size()-1; i++ ) {
+               HttpCookie headC = (HttpCookie)cookiesToSend.elementAt(i);
+               String head = headC.getPath();
+               // This little excercise is a cheap way to get
+               // '/foo' to read more specfic then '/'
+               if( !head.endsWith("/") ) {
+                   head = head + "/";
+               }
+               for( int j = i+1; j < cookiesToSend.size(); j++ ) {
+                   HttpCookie scanC = (HttpCookie)cookiesToSend.elementAt(j);
+                   String scan = scanC.getPath();
+                   if( !scan.endsWith("/") ) {
+                       scan = scan + "/";
+                   }
+
+                   int headCount = 0;
+                   int index = -1;
+                   while( (index=head.indexOf('/', index+1)) != -1 ) {
+                       headCount++;
+                   }
+                   index = -1;
+
+                   int scanCount = 0;
+                   while( (index=scan.indexOf('/', index+1)) != -1 ) {
+                       scanCount++;
+                   }
+
+                   if( scanCount > headCount ) {
+                       cookiesToSend.setElementAt(headC, j);
+                       cookiesToSend.setElementAt(scanC, i);
+                       headC = scanC;
+                       head = scan;
+                   }
+               }
+           }
+       }
+
+
+    return cookiesToSend;
+
+    }
+
+    /*
+     * Writes cookies out to PrintWriter if they are persistent
+     * (i.e. have a expr date)
+     * and haven't expired. Will remove cookies that have expired as well
+     */
+    private void saveCookiesToStream(PrintWriter pw) {
+
+       Enumeration cookieLists = cookieJar.elements();
+               
+       while (cookieLists.hasMoreElements()) {
+           Vector cookieList = (Vector)cookieLists.nextElement();
+
+           Enumeration cookies = cookieList.elements();
+
+           while (cookies.hasMoreElements()) {
+               HttpCookie cookie = (HttpCookie)cookies.nextElement();
+               
+               if (cookie.getExpirationDate() != null) {
+                   if (cookie.isSaveable()) {
+                       pw.println(cookie);
+                   } else { // the cookie must have expired, 
+                       //remove from Vector cookieList
+                       cookieList.removeElement(cookie);
+                   }
+                
+               }   
+           }
+       }
+        // Must print something to the printwriter in the case that 
+       // the cookieJar has been cleared - otherwise the old cookie
+       // file will continue to exist.
+       pw.print("");
+    }
+/////////////////////////////////////////////////////////////
+    /* adds cookieList to the existing cookie jar*/
+    public void addToCookieJar(HttpCookie[] cookieList) {
+
+       if (cookieList != null) {
+           for (int i = 0; i < cookieList.length; i++) {
+               
+               recordCookie(cookieList[i]);
+           }
+       }
+
+    }
+
+    /*adds one cookie to the Cookie Jar */
+    public void addToCookieJar(String cookieString, URL docURL) {
+       recordCookie(new HttpCookie(docURL, cookieString));
+    }
+
+    /* loads the cookies from the given filename */
+    public void loadCookieJarFromFile(String cookieFileName) {
+       try {
+           FileReader fr = new FileReader(cookieFileName);
+           
+           BufferedReader in = new BufferedReader(fr);
+
+           try {
+               String cookieString;
+               while ((cookieString = in.readLine()) != null) {
+                   HttpCookie cookie = new HttpCookie(cookieString);
+                   // Record the cookie, without notification.  We don't
+                   // do a notification for cookies that are read at
+                   // program start-up.
+                   recordCookie(cookie);
+               }
+           } finally {
+               in.close();
+           }
+
+           
+       } catch (IOException e) {
+           // do nothing; it's not an error not to have persistent cookies
+       }
+
+    }
+    
+    /* saves the cookies to the given file specified by fname */
+    public void saveCookieJarToFile(String cookieFileName) {
+       try {
+           FileWriter fw = new FileWriter(cookieFileName);
+           PrintWriter pw = new PrintWriter(fw, false);
+
+           try {
+               saveCookiesToStream(pw);
+           } finally {
+               pw.close();
+           }
+
+       } catch (IOException e) {
+           // REMIND: I18N
+           System.err.println("Saving cookies failed " + e.getMessage());
+       }
+    }
+
+    /**
+     * Return an array with all of the cookies represented by this
+     * jar.  This is useful when the bean is shutting down, and the client
+     * wants to make the cookie jar persist.
+     */
+    public HttpCookie[] getAllCookies() {
+
+       Vector result = new Vector();
+       Hashtable jar;
+       jar = (Hashtable) cookieJar.clone();
+       
+       synchronized (jar) {
+       
+           for (Enumeration e = jar.elements(); e.hasMoreElements() ;) {
+               Vector v = (Vector) e.nextElement();
+               for (int i = 0; i < v.size(); i++) {
+                   HttpCookie hc = (HttpCookie) v.elementAt(i);
+                   result.addElement(hc);
+                   
+               }
+               
+           }
+       }
+
+       HttpCookie[] resultA = new HttpCookie[result.size()];
+       for (int i = 0; i < result.size(); i++) {
+           resultA[i] = (HttpCookie) result.elementAt(i);
+       }
+       return resultA;
+    }
+
+    /* Gets all cookies that applies for the URL */
+    public HttpCookie[] getCookiesForURL(URL url) {
+
+       Vector cookieVector = getAllRelevantCookies(url);
+
+       if (cookieVector == null) {
+           return null;
+       }
+
+       int i = 0;
+       HttpCookie[] cookieArr = new HttpCookie[cookieVector.size()];
+
+       for (Enumeration e = cookieVector.elements(); e.hasMoreElements(); ) {
+
+           cookieArr[i++] = (HttpCookie)e.nextElement();
+//         System.out.println("cookieArr["+(i-1)+"] = " +cookieArr[i-1].toString());
+       }
+           
+       return cookieArr;
+    }
+
+    /* this will set the property of enableCookies to isDisabled */
+    public void setCookieDisable(boolean isDisabled) {
+
+       // Pending visit back this again
+       try {
+       Properties properties = new Properties();
+       properties.load(new FileInputStream("ServerAutoRun.properties") );
+       
+       
+       properties.put("enableCookies", isDisabled ? "false" : "true");
+       properties.store(new FileOutputStream("ServerAutoRun.properties"),"comments");
+       }
+       catch ( Exception e )
+       {
+               System.out.println("Exception : " + e );
+       }
+    }
+
+    public void discardAllCookies() {
+       cookieJar.clear();
+       
+    }
+
+    /* 
+     * purges any expired cookies in the Cookie hashtable.
+     */
+    public void purgeExpiredCookies() {
+       Enumeration cookieLists = cookieJar.elements();
+               
+       while (cookieLists.hasMoreElements()) {
+           Vector cookieList = (Vector)cookieLists.nextElement();
+
+           Enumeration cookies = cookieList.elements();
+
+           while (cookies.hasMoreElements()) {
+               HttpCookie cookie = (HttpCookie)cookies.nextElement();
+               
+               if (cookie.hasExpired()) {
+                   cookieList.removeElement(cookie);
+               }   
+           }
+       }
+
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/GTest.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/GTest.java
new file mode 100755 (executable)
index 0000000..b809d7c
--- /dev/null
@@ -0,0 +1,1088 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+* @Author Costin, Ramesh.Mandava
+*/
+
+package org.apache.tomcat.test.watchdog;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import org.apache.tomcat.util.buf.HexUtils;
+
+
+// derived from Jsp
+
+public class GTest {
+
+    int failureCount = 0;
+    int passCount = 0;
+    Throwable lastError;
+    boolean hasFailed = false;
+
+    String prefix = "http";
+    String host = "localhost";
+    String localHost = null;
+    String localIP = null;
+    int port = 8080;
+    int debug = 0;
+
+    String description = "No description";
+
+    String request;
+    HashMap requestHeaders = new HashMap();
+    String content;
+    
+    // true if task is nested
+    private boolean nested = false;
+
+    // Expected response
+    boolean magnitude = true;
+    boolean exactMatch = false;
+
+    // expect a response body
+    boolean expectResponseBody = true;
+
+    // Match the body against a golden file
+    String goldenFile;
+    // Match the body against a string
+    String responseMatch;
+    // the response should include the following headers
+    HashMap expectHeaders = new HashMap();
+
+    // Headers that should not be found in response
+    HashMap unexpectedHeaders = new HashMap();
+
+    // Match request line
+    String returnCode = "";
+    String returnCodeMsg = "";
+
+    // Actual response
+    String responseLine;
+    byte[] responseBody;
+    HashMap headers;
+
+
+    // For Report generation
+    StringBuffer resultOut = new StringBuffer();
+    
+    boolean firstTask = false;
+    boolean lastTask = false;
+    String expectedString;
+    String actualString;
+
+    String testName;
+    String assertion;
+    String testStrategy;
+
+    // For Session Tracking
+    static Hashtable sessionHash;
+    static Hashtable cookieHash;
+
+    String testSession;
+    Vector cookieVector;
+    URL requestURL;
+    CookieController cookieController ;
+
+    /**
+     * Creates a new <code>GTest</code> instance.
+     *
+     */
+    public GTest() {
+    }
+
+    /**
+     * <code>setTestSession</code> adds a 
+     * CookieController for the value of sessionName
+     *
+     * @param sessionName a <code>String</code> value
+     */
+    public void setTestSession( String sessionName ) {
+        testSession = sessionName;
+
+        if ( sessionHash == null ) {
+            sessionHash = new Hashtable();
+        } else if ( sessionHash.get( sessionName ) == null ) {
+            sessionHash.put ( sessionName, new CookieController() );
+        }
+    }
+
+    /**
+     * <code>setTestName</code> sets the current test name.
+     *
+     * @param tn current testname.
+     */
+    public void setTestName ( String tn ) {
+        testName = tn;
+    }
+
+    /**
+     * <code>setAssertion</code> sets the assertion text
+     * for the current test.
+     *
+     * @param assertion assertion text
+     */
+    public void setAssertion ( String assertion ) {
+        this.assertion = assertion;
+    }
+    /**
+     * <code>setTestStrategy</code> sets the test strategy
+     * for the current test.
+     *
+     * @param strategy test strategy text
+     */
+    public void setTestStrategy ( String strategy ) {
+        testStrategy = strategy;
+    }
+
+    /**
+     * <code>getTestName</code> returns the current 
+     * test name.
+     *
+     * @return a <code>String</code> value
+     */
+    public String getTestName( ) {
+        return testName;
+    }
+
+    /**
+     * <code>getAssertion</code> returns the current
+     * assertion text.
+     *
+     * @return a <code>String</code> value
+     */
+    public String getAssertion( ) {
+        return assertion;
+    }
+
+    /**
+     * <code>getTestStrategy</code> returns the current
+     * test strategy test.
+     *
+     * @return a <code>String</code> value
+     */
+    public String getTestStrategy( ) {
+        return testStrategy;
+    }
+
+    /**
+     * <code>setFirstTask</code> denotes that current task
+     * being executed is the first task within the list.
+     *
+     * @param a <code>boolean</code> value
+     */    
+    public void setFirstTask( boolean val ) {
+        firstTask = val;
+    }
+   
+
+    /**
+     * <code>setLastTask</code> denotes that the current task
+     * being executed is the last task within the list.
+     *
+     * @param a <code>boolean</code> value
+     */ 
+    public void setLastTask ( boolean val ) {
+        lastTask = val;
+    }
+
+    /**
+     * <code>setPrefix</code> sets the protocol
+     * prefix.  Defaults to "http"
+     *
+     * @param prefix Either http or https
+     */
+    public void setPrefix( String prefix ) {
+        this.prefix = prefix;
+    }
+
+    /**
+     * <code>setHost</code> sets hostname where
+     * the target server is running. Defaults to
+     * "localhost"
+     *
+     * @param h a <code>String</code> value
+     */
+    public void setHost( String h ) {
+        this.host = h;
+    }
+
+    /**
+     * <code>setPort</code> sets the port
+     * that the target server is listening on.
+     * Defaults to "8080"
+     *
+     * @param portS a <code>String</code> value
+     */
+    public void setPort( String portS ) {
+        this.port = Integer.valueOf( portS ).intValue();
+    }
+
+    /**
+     * <code>setExactMatch</code> determines if a
+     * byte-by-byte comparsion is made of the server's
+     * response and the test's goldenFile, or if
+     * a token comparison is made.  By default, only
+     * a token comparison is made ("false").
+     *
+     * @param exact a <code>String</code> value
+     */
+    public void setExactMatch( String exact ) {
+        exactMatch = Boolean.valueOf( exact ).booleanValue();
+    }
+
+    /**
+     * <code>setContent</code> String value upon which
+     * the request header Content-Length is based upon.
+     *
+     * @param s a <code>String</code> value
+     */
+    public void setContent( String s ) {
+        this.content = s;
+    }
+
+    /**
+     * <code>setDebug</code> enables debug output.
+     * By default, this is disabled ( value of "0" ).
+     *
+     * @param debugS a <code>String</code> value
+     */
+    public void setDebug( String debugS ) {
+        debug = Integer.valueOf( debugS ).intValue();
+    }
+
+    /**
+     * <code>setMagnitude</code> Expected return
+     * value of the test execution.
+     * Defaults to "true"
+     *
+     * @param magnitudeS a <code>String</code> value
+     */
+    public void setMagnitude( String magnitudeS ) {
+        magnitude = Boolean.valueOf( magnitudeS ).booleanValue();
+    }
+
+    /**
+     * <code>setGoldenFile</code> Sets the goldenfile
+     * that will be used to validate the server's response.
+     *
+     * @param s fully qualified path and filename
+     */
+    public void setGoldenFile( String s ) {
+        this.goldenFile = s;
+    }
+
+    /**
+     * <code>setExpectResponseBody</code> sets a flag
+     * to indicate if a response body is expected from the
+     * server or not
+     *
+     * @param b a <code>boolean</code> value
+     */
+    public void setExpectResponseBody( boolean b ) {
+        this.expectResponseBody = b;
+    }
+
+    /**
+     * <code>setExpectHeaders</code> Configures GTest
+     * to look for the header passed in the server's
+     * response.  
+     *
+     * @param s a <code>String</code> value in the 
+     *          format of <header-field>:<header-value>
+     */
+    public void setExpectHeaders( String s ) {
+        this.expectHeaders = new HashMap();
+        StringTokenizer tok = new StringTokenizer( s, "|" );
+        while ( tok.hasMoreElements() ) {
+            String header = (String) tok.nextElement();
+            setHeaderDetails( header, expectHeaders, false );
+        }
+    }
+
+    /**
+     * <code>setUnexpectedHeaders</code> Configures GTest
+     * to look for the header passed to validate that it
+     * doesn't exist in the server's response.
+     *
+     * @param s a <code>String</code> value in the
+     *          format of <header-field>:<header-value>
+     */
+    public void setUnexpectedHeaders( String s ) {
+        this.unexpectedHeaders = new HashMap();
+        setHeaderDetails( s, unexpectedHeaders, false );
+    }
+
+    public void setNested( String s ) {
+        nested = Boolean.valueOf( s ).booleanValue();
+    }
+
+    /**
+     * <code>setResponseMatch</code> Match the
+     * passed value in the server's response.
+     *
+     * @param s a <code>String</code> value
+     */
+    public void setResponseMatch( String s ) {
+        this.responseMatch = s;
+    }
+
+    /**
+     * <code>setRequest</code> Sets the HTTP/HTTPS
+     * request to be sent to the target server
+     * Ex.
+     *    GET /servlet_path/val HTTP/1.0
+     *
+     * @param s a <code>String</code> value in the form
+     *          of METHOD PATH HTTP_VERSION
+     */
+    public void setRequest ( String s ) {
+        this.request = s;
+    }
+
+    /**
+     * <code>setReturnCode</code> Sets the expected
+     * return code from the server's response.
+     *
+     * @param code a valid HTTP response status code
+     */
+    public void setReturnCode( String code ) {
+        this.returnCode = code;
+    }
+
+    /**
+     * Describe <code>setReturnCodeMsg</code> Sets the expected
+     * return message to be found in the server's
+     * response.
+     *
+     * @param code a valid HTTP resonse status code
+     * @param message a <code>String</code> value
+     */
+    public void setReturnCodeMsg( String message ) {
+        this.returnCodeMsg = message;
+    }
+
+    /**
+     * <code>setRequestHeaders</code> Configures the request
+     * headers GTest should send to the target server.
+     *
+     * @param s a <code>String</code> value in for format
+     *          of <field-name>:<field-value>
+     */
+    public void setRequestHeaders( String s ) {
+        requestHeaders = new HashMap();
+        StringTokenizer tok = new StringTokenizer( s, "|" );
+        while ( tok.hasMoreElements() ) {
+            String header = (String) tok.nextElement();
+            setHeaderDetails( header, requestHeaders, true );
+        }
+    }
+
+    // Inner tests are not used currently, can be reworked
+    
+//    /**
+//     * Add a Task to this container
+//     *
+//     * @param Task to add
+//     */
+//    public void addTask(Task task) {
+//        children.add(task);
+//    }
+
+    /**
+     * <code>execute</code> Executes the test.
+     *
+     * @exception BuildException if an error occurs
+     */
+    public void execute()  {
+
+        try {
+
+            if ( resultOut != null && !nested ) {
+                resultOut.append("\ntestName: " + testName);
+                resultOut.append("\nreq: " + request);
+                resultOut.append("\nassertion: " + assertion);
+                resultOut.append("\ntestStrategy: " + testStrategy);
+            }
+
+            WatchdogHttpClient.dispatch(this);
+
+            hasFailed = !checkResponse( magnitude );
+
+
+//            if ( !children.isEmpty() ) {
+//                Iterator iter = children.iterator();
+//                while (iter.hasNext()) {
+//                    Task task = (Task) iter.next();
+//                    task.perform();
+//                }
+//            }
+
+            if ( !hasFailed && !nested ) {
+                passCount++;
+                if ( resultOut != null ) {
+                    resultOut.append( "<result>PASS</result>\n" );
+                }
+                System.out.println( " PASSED " + testName + "\n        (" + request + ")" );
+            } else if ( hasFailed && !nested ){
+                       failureCount++;
+                if ( resultOut != null ) {
+                    resultOut.append( "<result>FAIL</result>\n" );
+                }
+                System.out.println( " FAILED " + testName + "\n        (" + request + ")\n" +
+                        resultOut.toString());
+            }
+
+        } catch ( Exception ex ) {
+            failureCount++;
+            System.out.println( " FAIL " + description + " (" + request + ")" );
+            lastError = ex;
+            ex.printStackTrace();
+        } finally {
+            if ( !nested ) {
+                hasFailed = false;
+            }
+        }
+    }
+
+    /**
+     * <code>checkResponse</code> Executes various response
+     * checking mechanisms against the server's response.
+     * Checks include:
+     * <ul>
+     *    <li>expected headers
+     *    <li>unexpected headers
+     *    <li>return codes and messages in the Status-Line
+     *    <li>response body comparison againt a goldenfile
+     * </ul>
+     *
+     * @param testCondition a <code>boolean</code> value
+     * @return a <code>boolean</code> value
+     * @exception Exception if an error occurs
+     */
+    private boolean checkResponse( boolean testCondition )
+    throws Exception {
+       boolean match = false;
+
+       if ( responseLine != null ) {
+        // If returnCode doesn't match
+           if ( responseLine.indexOf( "HTTP/1." ) > -1 ) {
+
+                   if ( !returnCode.equals( "" ) ) {
+                       boolean resCode = ( responseLine.indexOf( returnCode ) > -1 );
+                       boolean resMsg  = ( responseLine.indexOf( returnCodeMsg ) > -1 );
+
+                       if ( returnCodeMsg.equals( "" ) ) {
+                               match = resCode;
+                       } else {
+                               match = ( resCode && resMsg );
+                       }
+
+                       if ( match != testCondition ) {
+
+                               if ( resultOut != null ) {
+                                  String expectedStatusCode = "<expectedStatusCode>" + returnCode + "</expectedReturnCode>\n";
+                                  String expectedReasonPhrase = "<expectedReasonPhrase>" + returnCodeMsg + "</expectedReasonPhrase>";
+                                  actualString = "<actualStatusLine>" + responseLine + "</actualStatusLine>\n";
+                                   resultOut.append( expectedStatusCode );
+                                   resultOut.append( expectedReasonPhrase );
+                                   resultOut.append( actualString );
+                               }
+
+                               return false;
+                       }
+                   }
+           } else {
+               resultOut.append("\n<failure>Wrong Http version: " + responseLine +
+                       "</failure>");
+               return false;
+           }
+       } else {
+           resultOut.append("\n<failure>No response from server</failure>" );
+           return false;
+       }
+
+       /* 
+        * Check for headers the test expects to be in the server's response
+        */
+
+       // Duplicate set of response headers
+       HashMap copiedHeaders = cloneHeaders( headers );
+
+       // used for error reporting
+       String currentHeaderField = null;
+       String currentHeaderValue = null;
+
+        if ( !expectHeaders.isEmpty() ) {
+           boolean found = false;
+           String expHeader = null;
+
+           if ( !headers.isEmpty() ) {
+               Iterator expectIterator = expectHeaders.keySet().iterator();
+                while ( expectIterator.hasNext() ) {
+                    found = false;
+                    String expFieldName = (String) expectIterator.next();
+                    currentHeaderField = expFieldName;
+                    ArrayList expectValues = (ArrayList) expectHeaders.get( expFieldName );
+                    Iterator headersIterator = copiedHeaders.keySet().iterator();
+
+                    while( headersIterator.hasNext() ) {
+                        String headerFieldName = (String) headersIterator.next();
+                        ArrayList headerValues = (ArrayList) copiedHeaders.get( headerFieldName );
+              
+                        // compare field names and values in an HTTP 1.x compliant fashion
+                        if ( ( headerFieldName.equalsIgnoreCase( expFieldName ) ) ) {
+                            int hSize = headerValues.size();
+                            int eSize = expectValues.size();
+
+                            // number of expected headers found in server response
+                            int numberFound = 0;
+             
+                            for ( int i = 0; i < eSize; i++ ) {
+                                currentHeaderValue = (String) expectValues.get( i );
+                                               
+                                /*
+                                 * Handle the Content-Type header appropriately
+                                 * based on the the test is configured to look for.
+                                 */
+                                if ( currentHeaderField.equalsIgnoreCase( "content-type" ) ) {
+                                    String resVal = (String) headerValues.get( 0 );
+                                    if ( currentHeaderValue.indexOf( ';' ) > -1 ) {
+                                        if ( currentHeaderValue.equals( resVal ) ) {
+                                            numberFound++;
+                                            headerValues.remove( 0 );
+                                        }
+                                    } else if ( resVal.indexOf( currentHeaderValue ) > -1 ) {
+                                        numberFound++;
+                                        headerValues.remove( 0 );
+                                    }
+                                } else if ( currentHeaderField.equalsIgnoreCase( "location" ) ) {
+                                    String resVal = (String) headerValues.get( 0 );
+                                    int idx = currentHeaderValue.indexOf( ":80/" );
+                                    if ( idx > -1 ) {
+                                        String tempValue = currentHeaderValue.substring( 0, idx ) +
+                                                           currentHeaderValue.substring( idx + 3 );
+                                        if ( currentHeaderValue.equals( resVal ) || 
+                                             tempValue.equals( resVal ) ) {
+                                            numberFound++;
+                                            headerValues.remove( 0 );
+                                        }
+                                    } else {
+                                        if ( currentHeaderValue.equals( resVal ) ) {
+                                            numberFound++;
+                                            headerValues.remove( 0 );
+                                        }
+                                    }
+                                } else if ( headerValues.contains( currentHeaderValue ) ) {
+                                    numberFound++;
+                                    headerValues.remove( headerValues.indexOf( currentHeaderValue ) );
+                                }
+                            }
+                            if ( numberFound == eSize ) {
+                                found = true;
+                            }
+                        }
+                    }
+                    if ( !found ) {
+                        /*
+                         * Expected headers not found in server response.
+                         * Break the processing loop.
+                         */
+                        break;
+                    }
+                }
+            }
+
+           if ( !found ) {
+               StringBuffer actualBuffer = new StringBuffer( 128 );
+               if ( resultOut != null ) {
+                   expectedString = "<expectedHeaderNotFound>" + currentHeaderField + ": " + currentHeaderValue + "</expectedHeader>\n";
+               }
+                if ( !headers.isEmpty() ) {
+                    Iterator iter = headers.keySet().iterator();
+                    while ( iter.hasNext() ) {
+                        String headerName = (String) iter.next();
+                        ArrayList vals = (ArrayList) headers.get( headerName );
+                        String[] val = (String[]) vals.toArray( new String[ vals.size() ] );
+                        for ( int i = 0; i < val.length; i++ ) {
+                           if ( resultOut != null ) {
+                               actualBuffer.append( "<actualHeader>" + headerName + ": " + val[ i ] + "</actualHeader>\n" );
+                           }
+                        }
+                    }
+                   if ( resultOut != null ) {
+                       resultOut.append( expectedString );
+                       resultOut.append( actualBuffer.toString() );
+                   }
+                }
+                return false;
+            }
+        }
+
+       /*
+         * Check to see if we're looking for unexpected headers.
+         * If we are, compare the values in the unexectedHeaders
+         * ArrayList against the headers from the server response.
+         * if the unexpected header is found, then return false.
+         */
+
+        if ( !unexpectedHeaders.isEmpty() ) {
+            boolean found = false;
+            String unExpHeader = null;
+            // Check if we got any unexpected headers
+
+            if ( !copiedHeaders.isEmpty() ) {
+                Iterator unexpectedIterator = unexpectedHeaders.keySet().iterator();
+                while ( unexpectedIterator.hasNext() ) {
+                    found = false;
+                    String unexpectedFieldName = (String) unexpectedIterator.next();
+                    ArrayList unexpectedValues = (ArrayList) unexpectedHeaders.get( unexpectedFieldName );
+                    Iterator headersIterator = copiedHeaders.keySet().iterator();
+
+                    while ( headersIterator.hasNext() ) {
+                        String headerFieldName = (String) headersIterator.next();
+                        ArrayList headerValues = (ArrayList) copiedHeaders.get( headerFieldName );
+                        
+                        // compare field names and values in an HTTP 1.x compliant fashion
+                        if ( ( headerFieldName.equalsIgnoreCase( unexpectedFieldName ) ) ) {
+                            int hSize = headerValues.size();
+                            int eSize = unexpectedValues.size();
+                            int numberFound = 0;
+                            for ( int i = 0; i < eSize; i++ ) {
+                                if ( headerValues.contains( unexpectedValues.get( i ) ) ) {
+                                    numberFound++;
+                                    headerValues.remove( headerValues.indexOf( headerFieldName ) );
+                                }
+                            }
+                            if ( numberFound == eSize ) {
+                                found = true;
+                            }
+                        }
+                    }
+                    if ( !found ) {
+                        /*
+                         * Expected headers not found in server response.
+                         * Break the processing loop.
+                         */
+                        break;
+                    }
+                }
+            }
+
+            if ( found ) {
+                resultOut.append( "\n Unexpected header received from server: " + unExpHeader );
+                return false;
+            }
+       }
+
+           
+
+        if ( responseMatch != null ) {
+            // check if we got the string we wanted
+            if ( expectResponseBody && responseBody == null ) {
+                resultOut.append( "\n ERROR: got no response, expecting " + responseMatch );
+                return false;
+            }
+           String responseBodyString = new String( responseBody );
+            if ( responseBodyString.indexOf( responseMatch ) < 0 ) {
+                resultOut.append( "\n ERROR: expecting match on " + responseMatch );
+                resultOut.append( "\n Received: \n" + responseBodyString );
+            }
+        }
+
+       if ( !expectResponseBody && responseBody != null ) {
+           resultOut.append("Received a response body from the server where none was expected" );
+           return false;
+       }
+
+        // compare the body
+        if ( goldenFile == null )
+            return true;
+
+        // Get the expected result from the "golden" file.
+        byte[] expResult = getExpectedResult();
+        String expResultS = (expResult == null) ? "" : new String(expResult);
+        // Compare the results and set the status
+        boolean cmp = true;
+
+        if ( exactMatch ) {
+            cmp = compare( responseBody, expResult );
+       } else {
+            cmp = compareWeak( responseBody, expResult );
+       }
+
+        if ( cmp != testCondition ) {
+
+            if ( resultOut != null ) {
+              expectedString = "<expectedBody>" + new String(expResult) + "</expectedBody>\n";
+              actualString = "<actualBody>" + 
+              (responseBody != null ? new String(responseBody) : "null" ) + 
+              "</actualBody>\n";
+                resultOut.append( expectedString );
+                resultOut.append( actualString );
+            }
+           return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Replaces any |client.ip| and |client.host| parameter marks
+     * with the host and IP values of the host upon which Watchdog
+     * is running.
+     *
+     * @param request An HTTP request. 
+     */
+     String replaceMarkers( String req, Socket socket ) {
+        
+        final String CLIENT_IP = "client.ip";
+        final String CLIENT_HOME = "client.host";
+
+        if (localIP == null || localHost == null) {
+            InetAddress addr = socket.getLocalAddress(); 
+            localHost = addr.getHostName();
+            localIP = addr.getHostAddress();
+        }
+
+        if (req.indexOf('|') > -1) {
+            StringTokenizer tok = new StringTokenizer( request, "|" );
+            StringBuffer sb = new StringBuffer( 50 );
+        
+            while ( tok.hasMoreElements() ) {
+                String token = tok.nextToken();
+                if ( token.equals( CLIENT_IP ) ) {
+                    sb.append( localIP );
+                } else if ( token.equals( CLIENT_HOME ) ) {
+                    sb.append( localHost );
+                } else {
+                    sb.append( token );
+                }
+            }
+            return sb.toString(); 
+        } else {
+            return req;
+        }
+    }
+            
+
+    
+    /**
+     * <code>getExpectedResult</code> returns a byte array
+     * containing the content of the configured goldenfile
+     *
+     * @return goldenfile as a byte[]
+     * @exception IOException if an error occurs
+     */
+    private byte[] getExpectedResult()
+    throws IOException {
+        byte[] expResult = { 'N','O',' ',
+                             'G','O','L','D','E','N','F','I','L','E',' ',
+                             'F','O','U','N','D' };
+                            
+        try {
+            InputStream in = new BufferedInputStream(
+                                new FileInputStream( goldenFile ) );
+            return readBody ( in );
+        } catch ( Exception ex ) {
+            System.out.println( "Golden file not found: " + goldenFile );
+            return expResult;
+        }
+    }
+
+    /**
+     * <code>compare</code> compares the two byte arrays passed
+     * in to verify that the lengths of the arrays are equal, and
+     * that the content of the two arrays, byte for byte are equal.
+     *
+     * @param fromServer a <code>byte[]</code> value
+     * @param fromGoldenFile a <code>byte[]</code> value
+     * @return <code>boolean</code> true if equal, otherwise false
+     */
+    private boolean compare( byte[] fromServer, byte[] fromGoldenFile ) {
+        if ( fromServer == null || fromGoldenFile == null ) {
+            return false;
+       }
+
+       /*
+         * Check to see that the respose and golden file lengths
+         * are equal.  If they are not, dump the hex and don't
+         * bother comparing the bytes.  If they are equal,
+         * iterate through the byte arrays and compare each byte.
+         * If the bytes don't match, dump the hex representation
+         * of the server response and the goldenfile and return
+         * false.
+         */
+       if ( fromServer.length != fromGoldenFile.length ) {
+            StringBuffer sb = new StringBuffer( 50 );
+            sb.append( " Response and golden files lengths do not match!\n" );
+            sb.append( " Server response length: " );
+            sb.append( fromServer.length );
+            sb.append( "\n Goldenfile length: " );
+            sb.append( fromGoldenFile.length );
+            resultOut.append( sb.toString() );
+            sb = null;
+            // dump the hex representation of the byte arrays
+            dumpHex( fromServer, fromGoldenFile );
+
+            return false;
+        } else {
+
+            int i = 0;
+            int j = 0;
+
+            while ( ( i < fromServer.length ) && ( j < fromGoldenFile.length ) ) {
+                if ( fromServer[ i ] != fromGoldenFile[ j ] ) {
+                    resultOut.append( "\n Error at position " + ( i + 1 ) );
+                    // dump the hex representation of the byte arrays
+                    dumpHex( fromServer, fromGoldenFile );
+
+                    return false;
+                }
+
+                i++;
+                j++;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * <code>compareWeak</code> creates new Strings from the passed arrays
+     * and then uses a StringTokenizer to compare non-whitespace tokens.
+     *
+     * @param fromServer a <code>byte[]</code> value
+     * @param fromGoldenFile a <code>byte[]</code> value
+     * @return a <code>boolean</code> value
+     */
+    private boolean compareWeak( byte[] fromServer, byte[] fromGoldenFile ) {
+        if ( fromServer == null || fromGoldenFile == null ) {
+            return false;
+           }
+
+        boolean status = true;
+
+        String server = new String( fromServer );
+        String golden = new String( fromGoldenFile );
+
+        StringTokenizer st1 = new StringTokenizer( server );
+
+        StringTokenizer st2 = new StringTokenizer( golden );
+
+        while ( st1.hasMoreTokens() && st2.hasMoreTokens() ) {
+            String tok1 = st1.nextToken();
+            String tok2 = st2.nextToken();
+
+            if ( !tok1.equals( tok2 ) ) {
+                resultOut.append( "\t FAIL*** : Rtok1 = " + tok1
+                                    + ", Etok2 = " + tok2 );
+                status = false;
+            }
+        }
+
+        if ( st1.hasMoreTokens() || st2.hasMoreTokens() ) {
+             status = false;
+        }
+
+        if ( !status ) {
+            StringBuffer sb = new StringBuffer( 255 );
+            sb.append( "ERROR: Server's response and configured goldenfile do not match!\n" );
+            sb.append( "Response received from server:\n" );
+            sb.append( "---------------------------------------------------------\n" );
+            sb.append( server );
+            sb.append( "\nContent of Goldenfile:\n" );
+            sb.append( "---------------------------------------------------------\n" );
+            sb.append( golden );
+            sb.append( "\n" );
+            resultOut.append( sb.toString() );
+        }
+        return status;
+    }
+
+    /**
+     * <code>readBody</code> reads the body of the response
+     * from the InputStream.
+     *
+     * @param input an <code>InputStream</code>
+     * @return a <code>byte[]</code> representation of the response
+     */
+    private byte[] readBody( InputStream input ) {
+        StringBuffer sb = new StringBuffer( 255 );
+        while ( true ) {
+            try {
+                int ch = input.read();
+
+                if ( ch < 0 ) {
+                    if ( sb.length() == 0 ) {
+                        return ( null );
+                    } else {
+                        break;
+                    }
+                }
+                sb.append( ( char ) ch );
+                 
+            } catch ( IOException ex ) {
+                return null;
+            }
+        }
+        return sb.toString().getBytes();
+    }
+
+    /**
+     * <code>setHeaderDetails</code> Wrapper method for parseHeader.
+     * Allows easy addition of headers to the specified
+     * HashMap
+     *
+     * @param line a <code>String</code> value
+     * @param headerMap a <code>HashMap</code> value
+     * @param isRequest a <code>boolean</code> indicating if the passed Header 
+     *                  HashMap is for request headers
+     */
+    void setHeaderDetails( String line, HashMap headerHash, boolean isRequest ) {
+        StringTokenizer stk = new StringTokenizer( line, "##" );
+
+        while ( stk.hasMoreElements( ) ) {
+            String presentHeader = stk.nextToken();
+            parseHeader( presentHeader, headerHash, isRequest );
+        }
+    }
+
+    /**
+     * <code>parseHeader</code> parses input headers in format of "key:value"
+     * The parsed header field-name will be used as a key in the passed 
+     * HashMap object, and the values found will be stored in an ArrayList
+     * associated with the field-name key.
+     *
+     * @param line String representation of an HTTP header line.
+     * @param headers a<code>HashMap</code> to store key/value header objects.
+     * @param isRequest set to true if the headers being processed are 
+     *        requestHeaders.
+     */
+    void parseHeader( String line, HashMap headerMap, boolean isRequest ) {
+        // Parse the header name and value
+        int colon = line.indexOf( ":" );
+
+        if ( colon < 0 ) {
+            resultOut.append( "\n ERROR: Header is in incorrect format: " + line );
+            return ;
+        }
+
+        String name = line.substring( 0, colon ).trim();
+        String value = line.substring( colon + 1 ).trim();
+
+        if ( ( cookieVector != null ) && ( name.equalsIgnoreCase( "Set-Cookie" ) ) ) {
+            cookieVector.addElement( value );
+            /*
+            if ( ( value.indexOf("JSESSIONID") > -1 ) || (value.indexOf("jsessionid")  > -1 ) )
+        {
+                 String sessionId= value.substring( value.indexOf("=")+1);
+                 if ( testSession != null )
+                 {
+                       sessionHash.put( testSession, sessionId );
+                 }
+                 System.out.println("Got Session-ID : " + sessionId );
+        }
+            */
+        }
+
+        //     System.out.println("HEADER: " +name + " " + value);
+
+       ArrayList values = (ArrayList) headerMap.get( name );
+       if ( values == null ) {
+           values = new ArrayList();
+       }
+       // HACK
+       if ( value.indexOf( ',' ) > -1 && !isRequest && !name.equalsIgnoreCase( "Date" ) ) {
+           StringTokenizer st = new StringTokenizer( value, "," );
+           while ( st.hasMoreElements() ) {
+               values.add( st.nextToken() );
+           }
+       } else {
+           values.add( value );
+       }
+       
+        headerMap.put( name, values );
+    }
+
+    /**
+     * <code>dumpHex</code> helper method to dump formatted
+     * hex output of the server response and the goldenfile.
+     *
+     * @param serverResponse a <code>byte[]</code> value
+     * @param goldenFile a <code>byte[]</code> value
+     */
+    private void dumpHex( byte[] serverResponse, byte[] goldenFile ) {
+        StringBuffer outBuf = new StringBuffer( ( serverResponse.length + goldenFile.length ) * 2 );
+
+        String fromServerString = HexUtils.getHexDump( serverResponse, 0, serverResponse.length, true );
+        String fromGoldenFileString = HexUtils.getHexDump( goldenFile, 0, goldenFile.length, true );
+
+        outBuf.append( " Hex dump of server response and goldenfile below.\n\n### RESPONSE FROM SERVER ###\n" );
+        outBuf.append( "----------------------------\n" );
+        outBuf.append( fromServerString );
+        outBuf.append( "\n\n### GOLDEN FILE ###\n" );
+        outBuf.append( "-------------------\n" );
+        outBuf.append( fromGoldenFileString );
+        outBuf.append( "\n\n### END OF DUMP ###\n" );
+
+        resultOut.append( outBuf.toString() );
+
+    }
+
+    /**
+     * <code>cloneHeaders</code> returns a "cloned"
+     * HashMap of the map passed in.
+     *
+     * @param map a <code>HashMap</code> value
+     * @return a <code>HashMap</code> value
+     */
+    private HashMap cloneHeaders( HashMap map ) {
+       HashMap dupMap = new HashMap();
+       Iterator iter = map.keySet().iterator();
+       
+       while ( iter.hasNext() ) {
+           String key = new String( (String) iter.next() );
+           ArrayList origValues = (ArrayList) map.get( key );
+           ArrayList dupValues = new ArrayList();
+
+           String[] dupVal = (String[]) origValues.toArray( new String[ origValues.size() ] );
+           for ( int i = 0; i < dupVal.length; i++ ) {
+               dupValues.add( new String( dupVal[ i ] ) );
+           }
+           
+           dupMap.put( key, dupValues );
+       }
+       return dupMap;
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java
new file mode 100755 (executable)
index 0000000..33e21a0
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.test.watchdog;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Date;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+/**
+ * An object which represents an HTTP cookie.  Can be constructed by
+ * parsing a string from the set-cookie: header.
+ *
+ * Syntax: Set-Cookie: NAME=VALUE; expires=DATE;
+ *             path=PATH; domain=DOMAIN_NAME; secure
+ *
+ * All but the first field are optional.
+ *
+ * @author     Ramesh.Mandava
+ */
+
+public class HttpCookie {
+    private Date expirationDate = null;
+    private String nameAndValue;
+    private String path;
+    private String domain;
+    private boolean isSecure = false;
+    private static boolean defaultSet = true;
+    private static long defExprTime = 100;
+
+    public HttpCookie(String cookieString) {
+       /*
+       System.out.println("Calling default expiration :");
+       getDefaultExpiration();
+       */
+       parseCookieString(cookieString);
+    }
+
+    //
+    // Constructor for use by the bean
+    //
+    public HttpCookie(Date expirationDate,
+                     String nameAndValue,
+                     String path,
+                     String domain,
+                     boolean isSecure) {
+       this.expirationDate = expirationDate;
+       this.nameAndValue = nameAndValue;
+       this.path = path;
+       this.domain = domain;
+       this.isSecure = isSecure;
+    }
+
+    public HttpCookie(URL url, String cookieString) {
+       parseCookieString(cookieString);
+       applyDefaults(url);
+    }
+
+    /**
+     * Fills in default values for domain, path, etc. from the URL
+     * after creation of the cookie.
+     */
+    private void applyDefaults(URL url) {
+       if (domain == null) {
+           domain = url.getHost()+":"+((url.getPort() == -1) ? 80 : url.getPort());
+       }
+
+       if (path == null) {
+           path = url.getFile();
+
+           // larrylf: The documentation for cookies say that the path is
+           // by default, the path of the document, not the filename of the
+           // document.  This could be read as not including that document
+           // name itself, just its path (this is how NetScape intrprets it)
+           // so amputate the document name!
+           int last = path.lastIndexOf("/");
+           if( last > -1 ) {
+               path = path.substring(0, last);
+           }
+       }
+    }
+
+
+    /**
+     * Parse the given string into its individual components, recording them
+     * in the member variables of this object.
+     */
+    private void parseCookieString(String cookieString) {
+       StringTokenizer tokens = new StringTokenizer(cookieString, ";");
+
+       if (!tokens.hasMoreTokens()) {
+           // REMIND: make this robust against parse errors
+           nameAndValue="=";
+           return;
+       }
+
+       nameAndValue = tokens.nextToken().trim();
+       
+       while (tokens.hasMoreTokens()) {
+           String token = tokens.nextToken().trim();
+       
+           if (token.equalsIgnoreCase("secure")) {
+               isSecure = true;
+           } else {
+               int equIndex = token.indexOf("=");
+               
+               if (equIndex < 0) {
+                   continue;
+                   // REMIND: malformed cookie
+               }
+               
+               String attr = token.substring(0, equIndex);
+               String val = token.substring(equIndex+1);
+               
+               if (attr.equalsIgnoreCase("path")) {
+                   path = val;
+               } else if (attr.equalsIgnoreCase("domain")) {
+                   if( val.indexOf(".") == 0 ) {
+                       // spec seems to allow for setting the domain in
+                       // the form 'domain=.eng.sun.com'.  We want to
+                       // trim off the leading '.' so we can allow for
+                       // both leading dot and non leading dot forms
+                       // without duplicate storage.
+                       domain = val.substring(1);
+                   } else {
+                       domain = val;
+                   }
+               } else if (attr.equalsIgnoreCase("expires")) {
+                   expirationDate = parseExpireDate(val);
+               } else {
+                   // unknown attribute -- do nothing
+               }
+           }
+       }
+
+       // commented the following out, b/c ok to have no expirationDate
+       // that means that the cookie should last only for that particular 
+       // session.
+       //      if (expirationDate == null) {
+       //          expirationDate = getDefaultExpiration();
+       //      }
+    }
+    
+    /* Returns the default expiration, which is the current time + default
+       expiration as specified in the properties file.
+       This uses reflection to get at the properties file, since Globals is 
+       not in the utils/ directory 
+       */
+    private Date getDefaultExpiration() {
+       if (defaultSet == false) {
+           Properties props = new Properties();
+           
+           try {
+               FileInputStream fin = new FileInputStream("ServerAutoRun.properties");
+               props.load( fin );
+               
+               System.out.println("Got properties from ServerAutoRun.properties");
+               props.list(System.out);
+               
+           } catch (IOException ex) {
+               System.out.println("HttpCookie getDefaultExpiration : ServerAutoRun.properties not found!" + ex);
+           }
+           
+                // defExprTime = props.getProperty("cookies.default.expiration");
+                defExprTime = Long.parseLong( props.getProperty("cookies.default.expiration") );
+
+           }
+           defaultSet = true;
+         
+       return (new Date(System.currentTimeMillis() + defExprTime));
+       
+    }
+
+    //======================================================================
+    //
+    // Accessor functions
+    //
+
+
+
+     public String getNameValue() {
+       return nameAndValue;
+    }
+
+    /**
+     * Returns just the name part of the cookie
+     */
+     public String getName() {
+
+        // it probably can't have null value, but doesn't hurt much
+        // to check.
+        if (nameAndValue == null) {
+            return "=";
+        }
+        int index = nameAndValue.indexOf("=");
+        return (index < 0) ? "=" : nameAndValue.substring(0, index);
+     }
+
+
+    /**
+     * Returns the domain of the cookie as it was presented
+     */
+     public String getDomain() {
+       // REMIND: add port here if appropriate
+       return domain;
+    }
+
+     public String getPath() {
+       return path;
+    }
+
+     public Date getExpirationDate() {
+       return expirationDate;
+    }
+
+    public boolean hasExpired() {
+       if(expirationDate == null) {
+           return false;
+       }
+       return (expirationDate.getTime() <= System.currentTimeMillis());
+    }
+
+    /**
+     * Returns true if the cookie has an expiration date (meaning it's
+     * persistent), and if the date nas not expired;
+     */
+    public boolean isSaveable() {
+       return (expirationDate != null)
+           && (expirationDate.getTime() > System.currentTimeMillis());
+    }
+
+    public boolean isSaveableInMemory() {
+       return ((expirationDate == null) ||
+               (expirationDate != null && expirationDate.getTime() > System.currentTimeMillis()));
+    }
+               
+     public boolean isSecure() {
+       return isSecure;
+    }
+
+    private Date parseExpireDate(String dateString) {
+       // format is wdy, DD-Mon-yyyy HH:mm:ss GMT
+       RfcDateParser parser = new RfcDateParser(dateString);
+       Date theDate = parser.getDate();
+       if (theDate == null) {
+           // Expire in some intelligent default time
+           theDate = getDefaultExpiration();
+       }
+       return theDate;
+    }
+
+    public String toString() {
+
+       String result = (nameAndValue == null) ? "=" : nameAndValue;
+       if (expirationDate != null) {
+           result += "; expires=" + expirationDate;
+       }
+       
+       if (path != null) {
+           result += "; path=" + path;
+       }
+
+       if (domain != null) {
+           result += "; domain=" + domain;
+       }
+
+       if (isSecure) {
+           result += "; secure";
+       }
+
+       return result;
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java
new file mode 100755 (executable)
index 0000000..2aeebb7
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * A parser for date strings commonly found in http and email headers that
+ * follow various RFC conventions.  Given a date-string, the parser will
+ * attempt to parse it by trying matches with a set of patterns, returning
+ * null on failure, a Date object on success.
+ *
+ * @author Ramesh.Mandava 
+ */
+public class RfcDateParser {
+
+    private boolean isGMT = false;
+
+    static final String[] standardFormats = {
+       "EEEE', 'dd-MMM-yy HH:mm:ss z",   // RFC 850 (obsoleted by 1036)
+       "EEEE', 'dd-MMM-yy HH:mm:ss",     // ditto but no tz. Happens too often
+       "EEE', 'dd-MMM-yyyy HH:mm:ss z",  // RFC 822/1123
+       "EEE', 'dd MMM yyyy HH:mm:ss z",  // REMIND what rfc? Apache/1.1
+       "EEEE', 'dd MMM yyyy HH:mm:ss z", // REMIND what rfc? Apache/1.1
+       "EEE', 'dd MMM yyyy hh:mm:ss z",  // REMIND what rfc? Apache/1.1
+       "EEEE', 'dd MMM yyyy hh:mm:ss z", // REMIND what rfc? Apache/1.1
+       "EEE MMM dd HH:mm:ss z yyyy",      // Date's string output format
+       "EEE MMM dd HH:mm:ss yyyy",       // ANSI C asctime format()
+       "EEE', 'dd-MMM-yy HH:mm:ss",      // No time zone 2 digit year RFC 1123
+       "EEE', 'dd-MMM-yyyy HH:mm:ss"     // No time zone RFC 822/1123
+    };
+
+    /* because there are problems with JDK1.1.6/SimpleDateFormat with
+     * recognizing GMT, we have to create this workaround with the following
+     * hardcoded strings */
+    static final String[] gmtStandardFormats = {
+       "EEEE',' dd-MMM-yy HH:mm:ss 'GMT'",   // RFC 850 (obsoleted by 1036)
+       "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",  // RFC 822/1123
+       "EEE',' dd MMM yyyy HH:mm:ss 'GMT'",  // REMIND what rfc? Apache/1.1
+       "EEEE',' dd MMM yyyy HH:mm:ss 'GMT'", // REMIND what rfc? Apache/1.1
+       "EEE',' dd MMM yyyy hh:mm:ss 'GMT'",  // REMIND what rfc? Apache/1.1
+       "EEEE',' dd MMM yyyy hh:mm:ss 'GMT'", // REMIND what rfc? Apache/1.1
+       "EEE MMM dd HH:mm:ss 'GMT' yyyy"      // Date's string output format
+    };
+
+    String dateString;
+
+    public RfcDateParser(String dateString) {
+       this.dateString = dateString.trim();
+       if (this.dateString.indexOf("GMT") != -1) {
+           isGMT = true;
+       }
+    }
+
+    public Date getDate() {
+
+        int arrayLen = isGMT ? gmtStandardFormats.length : standardFormats.length;
+        for (int i = 0; i < arrayLen; i++) {
+            Date d = null;
+
+            if (isGMT) {
+                d = tryParsing(gmtStandardFormats[i]);
+            } else {
+                d = tryParsing(standardFormats[i]);
+            }
+            if (d != null) {
+                return d;
+            }
+
+        }
+
+        return null;
+    }    
+
+    private Date tryParsing(String format) {
+
+       java.text.SimpleDateFormat df = new java.text.SimpleDateFormat(format, Locale.US);
+       if (isGMT) {
+           df.setTimeZone(TimeZone.getTimeZone("GMT"));
+       }
+       try {
+               return df.parse(dateString);
+       } catch (Exception e) {
+           return null;
+       }
+    }
+} /* class RfcDateParser */
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java
new file mode 100644 (file)
index 0000000..21f4276
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.apache.tomcat.util.DomUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+public class WatchdogClient {
+
+  protected String base = "../watchdog";
+        
+  protected String goldenDir; 
+  protected String testMatch;
+  protected String file;
+  protected String[] exclude = null;
+  protected String[] slow = 
+  { 
+      "SingleModelTest" // slow  
+  };  
+
+  protected String targetMatch;
+    
+
+  Properties props = new Properties();
+  
+  protected void beforeSuite() {
+      
+  }
+  
+  protected void afterSuite(TestResult res) {
+      
+  }
+  
+  /** 
+   * Return a test suite for running a watchdog-like 
+   * test file. 
+   *
+   * @param base base dir for the watchdog dir
+   * @param testMatch Prefix of tests to be run
+   * @return
+   */
+  public Test getSuite() {
+    TestSuite tests = new WatchdogTests();
+    tests.setName(this.getClass().getSimpleName());
+    
+    props.setProperty("port", "8080");
+    props.setProperty("host", "localhost");
+    props.setProperty("wgdir", 
+        goldenDir);
+    
+    
+    try {
+      Document doc = DomUtil.readXml(new FileInputStream(file));
+      Element docE = doc.getDocumentElement();
+      NodeList targetsL = docE.getElementsByTagName("target");
+      for (int i = 0; i < targetsL.getLength(); i++) {
+        Element target = (Element) targetsL.item(i);
+        String targetName = target.getAttribute("name");
+        if (targetMatch != null && !targetName.equals(targetMatch)) {
+            continue;
+        }
+        
+        // Tests are duplicated
+        //TestSuite targetSuite = new TestSuite(targetName);
+        
+        NodeList watchDogL = target.getElementsByTagName("watchdog");
+        for (int j = 0; j < watchDogL.getLength(); j++) {
+          Element watchE = (Element) watchDogL.item(j);
+          String testName = watchE.getAttribute("testName");
+          if (testMatch != null) {
+              if (!testName.startsWith(testMatch)) {
+                  continue;
+              }
+          }
+          if (exclude != null) {
+              boolean found = false;
+              for (String e: exclude) {
+                  if (e.equals(testName)) {
+                      found = true; 
+                      break;
+                  }
+              }
+              if (found) {
+                  continue;
+              }
+          }
+          testName = testName + ";" + this.getClass().getName();
+          WatchdogTest test = new WatchdogTest(watchE, props, testName);
+          tests.addTest(test);
+        }
+        
+        //        if (targetSuite.countTestCases() > 0) { 
+        //          tests.addTest(targetSuite);
+        //              }
+      }
+      
+    } catch (IOException e) {
+      e.printStackTrace();
+    } catch (SAXException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+    } catch (ParserConfigurationException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+    }
+    return tests;
+  }
+  
+  // --------- Inner classes -------------
+
+  public class WatchdogTests extends TestSuite {
+      public void run(TestResult res) {
+          beforeSuite();
+          super.run(res);
+          afterSuite(res);
+      }
+  }
+  
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java
new file mode 100644 (file)
index 0000000..0f52a6c
--- /dev/null
@@ -0,0 +1,406 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+
+
+public class WatchdogHttpClient {
+    private static final String CRLF         = "\r\n";
+    private static final int LINE_FEED       = 10;
+    
+    static int debug = 0;
+    
+    public static void dispatch(GTest client) throws Exception {
+        HashMap requestHeaders = client.requestHeaders;
+        String host = client.host;
+        int port = client.port;
+        String content = client.content;
+        String request = client.request;
+        
+        // XXX headers are ignored
+        Socket socket = new Socket( host, port );
+
+        //socket obtained, rebuild the request.
+        rebuildRequest(client, client.request, socket);
+
+        InputStream in = new CRBufferedInputStream( socket.getInputStream() );
+
+        // Write the request
+        socket.setSoLinger( true, 1000 );
+
+        OutputStream out = new BufferedOutputStream( 
+                               socket.getOutputStream() );
+        StringBuffer reqbuf = new StringBuffer( 128 );
+
+        // set the Host header
+        client.setHeaderDetails( "Host:" + host + ":" + port, requestHeaders, true );
+
+        // set the Content-Length header
+        if ( content != null ) {
+            client.setHeaderDetails( "Content-Length:" + content.length(),
+                              requestHeaders, true );
+        }
+
+        // set the Cookie header
+        if ( client.testSession != null ) {
+            client.cookieController = ( CookieController ) client.sessionHash.get( client.testSession );
+
+            if ( client.cookieController != null ) {
+
+                String releventCookieString = client.cookieController.applyRelevantCookies( client.requestURL );
+
+                if ( ( releventCookieString != null ) && ( !releventCookieString.trim().equals( "" ) ) ) {
+                    client.setHeaderDetails( "Cookie:" + releventCookieString, requestHeaders, true );
+                }
+            }
+        }
+
+        if ( debug > 0 ) {
+            System.out.println( " REQUEST: " + request );
+        }
+        reqbuf.append( client.request ).append( CRLF );
+
+        // append all request headers 
+        if ( !requestHeaders.isEmpty() ) {
+            Iterator iter = requestHeaders.keySet().iterator();
+                        
+            while ( iter.hasNext() ) {
+                StringBuffer tmpBuf = new StringBuffer(32);
+                String headerKey = ( String ) iter.next();
+                        ArrayList values = (ArrayList) requestHeaders.get( headerKey );
+                        String[] value = (String[]) values.toArray( new String[ values.size() ] );
+                tmpBuf.append( headerKey ).append(": ");
+                        for ( int i = 0; i < value.length; i++ ) {
+                    if ((i + 1) == value.length) {
+                                    tmpBuf.append( value[ i ] );
+                    } else {
+                        tmpBuf.append( value[ i ] ).append(", ");
+                    }
+                        }
+                            if ( debug > 0 ) {
+                                System.out.println( " REQUEST HEADER: " + tmpBuf.toString());
+                            }
+                tmpBuf.append( CRLF );
+                reqbuf.append(tmpBuf.toString());
+            }
+        }
+
+        /*
+
+        if ( ( testSession != null ) && ( sessionHash.get( testSession ) != null ) ) {
+            System.out.println("Sending Session Id : " + (String)sessionHash.get( testSession ) );
+            pw.println("JSESSIONID:" + (String)sessionHash.get( testSession) );
+        }
+
+        */
+
+        if ( request.indexOf( "HTTP/1." ) > -1 ) {
+            reqbuf.append( "" ).append( CRLF );
+        }
+
+        // append request content 
+        if ( content != null ) {
+            reqbuf.append( content );
+            // XXX no CRLF at the end -see HTTP specs!
+        }
+        
+        byte[] reqbytes = reqbuf.toString().getBytes();
+
+        try {
+            // write the request
+            out.write( reqbytes, 0, reqbytes.length );
+            out.flush();
+            reqbuf = null;
+        } catch ( Exception ex1 ) {
+            System.out.println( " Error writing request " + ex1 );
+                if ( debug > 0 ) {
+                        System.out.println( "Message: " + ex1.getMessage() );
+                        ex1.printStackTrace();
+                }
+        }
+
+        // read the response
+        try {
+  
+                client.responseLine = read( in );
+
+                if ( debug > 0 ) {
+                        System.out.println( " RESPONSE STATUS-LINE: " + client.responseLine );
+                }
+
+                client.headers = parseHeaders( client, in );
+           
+            byte[] result = readBody( in );
+
+            if ( result != null ) {
+                client.responseBody = result;
+                        if ( debug > 0 ) {
+                            System.out.println( " RESPONSE BODY:\n" + new String( client.responseBody ) );
+                        }
+                }
+                
+        } catch ( SocketException ex ) {
+            System.out.println( " Socket Exception: " + ex );
+            ex.printStackTrace();
+        } finally {
+                if ( debug > 0 ) {
+                        System.out.println( " closing socket" );
+                }
+                socket.close();
+                socket = null;
+            }
+        
+    }
+    
+    /**
+     * <code>readBody</code> reads the body of the response
+     * from the InputStream.
+     *
+     * @param input an <code>InputStream</code>
+     * @return a <code>byte[]</code> representation of the response
+     */
+    private static byte[] readBody( InputStream input ) {
+        StringBuffer sb = new StringBuffer( 255 );
+        while ( true ) {
+            try {
+                int ch = input.read();
+
+                if ( ch < 0 ) {
+                    if ( sb.length() == 0 ) {
+                        return ( null );
+                    } else {
+                        break;
+                    }
+                }
+                sb.append( ( char ) ch );
+                 
+            } catch ( IOException ex ) {
+                return null;
+            }
+        }
+        return sb.toString().getBytes();
+    }
+
+    
+    
+    /**
+     * Read a line from the specified servlet input stream, and strip off
+     * the trailing carriage return and newline (if any).  Return the remaining
+     * characters that were read as a string.7
+     *
+     * @returns The line that was read, or <code>null</code> if end of file
+     *  was encountered
+     *
+     * @exception IOException if an input/output error occurred
+     */
+    private static String read( InputStream input ) throws IOException {
+        // Read the next line from the input stream
+        StringBuffer sb = new StringBuffer();
+
+        while ( true ) {
+            try {
+                int ch = input.read();
+                //              System.out.println("XXX " + (char)ch );
+                if ( ch < 0 ) {
+                    if ( sb.length() == 0 ) {
+                        if ( debug > 0 )
+                            System.out.println( " Error reading line " + ch + " " + sb.toString() );
+                        return "";
+                    } else {
+                        break;
+                    }
+                } else if ( ch == LINE_FEED ) {
+                    break;
+                }
+
+                sb.append( ( char ) ch );
+            } catch ( IOException ex ) {
+                System.out.println( " Error reading : " + ex );
+                debug = 1;
+
+                if ( debug > 0 ) {
+                    System.out.println( "Partial read: " + sb.toString() );
+                    ex.printStackTrace();
+                }
+            }
+        }
+        return  sb.toString();
+    }
+
+    
+    // ==================== Code from JSERV !!! ====================
+    /**
+     * Parse the incoming HTTP request headers, and set the corresponding
+     * request properties.
+     *
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private static HashMap parseHeaders( GTest client, InputStream is ) throws IOException {
+        HashMap headers = new HashMap();
+        client.cookieVector = new Vector();
+
+        while ( true ) {
+            // Read the next header line
+            String line = read( is );
+
+            if ( ( line == null ) || ( line.length() < 1 ) ) {
+                break;
+            }
+
+            client.parseHeader( line, headers, false );
+
+            if ( debug > 0 ) {
+                System.out.println( " RESPONSE HEADER: " + line );
+            }
+
+        }
+
+        if ( client.testSession != null ) {
+            client.cookieController = ( CookieController ) client.sessionHash.get( client.testSession );
+
+            if ( client.cookieController != null ) {
+                client.cookieController.recordAnyCookies( client.cookieVector, client.requestURL );
+            }
+        }
+
+        return headers;
+    }
+
+    
+    
+    /**
+     * Private utility method to 'massage' a request string that
+     * may or may not have replacement markers for the request parameters.
+     *
+     * @param req the request to manipulate
+     * @param socket local socket.  Used to rebuild specified query strings.
+     *
+     * @exception Exception if an error occurs
+     */
+    private static void rebuildRequest(GTest client, String req, Socket socket) throws Exception {
+        client.request = client.replaceMarkers(req, socket );
+        String addressString = client.request.substring( client.request.indexOf( "/" ), client.request.indexOf( "HTTP" ) ).trim();
+
+        if ( addressString.indexOf( "?" ) > -1 ) {
+            addressString = addressString.substring( 0, addressString.indexOf( "?" ) ) ;
+        }
+
+        client.requestURL = new URL( "http", client.host, client.port, addressString );
+    }
+
+    
+    
+    /**
+     * <code>CRBufferedInputStream</code> is a modified version of
+     * the java.io.BufferedInputStream class.  The fill code is 
+     * the same, but the read is modified in that if a carriage return
+     * is found in the response stream from the target server, 
+     * it will skip that byte and return the next in the stream.
+     */
+    private static class CRBufferedInputStream extends BufferedInputStream {
+        private static final int CARRIAGE_RETURN = 13;
+        
+        private static final int DEFAULT_BUFFER = 2048;
+
+        /**
+         * Creates a new <code>CRBufferedInputStream</code> instance.
+         *
+         * @param in an <code>InputStream</code> value
+         */
+        public CRBufferedInputStream( InputStream in ) {
+            super( in, DEFAULT_BUFFER );
+        }
+
+        /**
+         * <code>read</code> reads a single byte value per call.
+         * If, the byte read, is a carriage return, the next byte
+         * in the stream in returned instead.
+         *
+         * @return an <code>int</code> value
+         * @exception IOException if an error occurs
+         */
+        public int read() throws IOException {
+            if ( in == null ) {
+                throw new IOException ( "Stream closed" );
+            }
+            if ( pos >= count ) {
+                fill();
+                if ( pos >= count ) {
+                    return -1;
+                }
+            }
+            int val = buf[pos++] & 0xff;
+            if ( val == CARRIAGE_RETURN ) {
+                if (pos >= count) {
+                    fill();
+                    if (pos >= count) {
+                       return -1;
+                    }
+                }
+                return buf[pos++] & 0xff;
+            }
+            return val;
+        }
+
+        /**
+         * <code>fill</code> is used to fill the internal
+         * buffer used by this BufferedInputStream class.
+         *
+         * @exception IOException if an error occurs
+         */
+        private void fill() throws IOException {
+            if (markpos < 0)
+                pos = 0;        /* no mark: throw away the buffer */
+            else if (pos >= buf.length)  /* no room left in buffer */
+                if (markpos > 0) {  /* can throw away early part of the buffer */
+                    int sz = pos - markpos;
+                    System.arraycopy(buf, markpos, buf, 0, sz);
+                    pos = sz;
+                    markpos = 0;
+                } else if (buf.length >= marklimit) {
+                    markpos = -1;   /* buffer got too big, invalidate mark */
+                    pos = 0;    /* drop buffer contents */
+                } else {        /* grow buffer */
+                    int nsz = pos * 2;
+                    if (nsz > marklimit)
+                        nsz = marklimit;
+                    byte nbuf[] = new byte[nsz];
+                    System.arraycopy(buf, 0, nbuf, 0, pos);
+                    buf = nbuf;
+                }
+                count = pos;
+                int n = in.read(buf, pos, buf.length - pos); 
+                if (n > 0)
+                count = n + pos;
+        }
+    }
+    
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTest.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTest.java
new file mode 100644 (file)
index 0000000..54e8a44
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.util.Properties;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class WatchdogTest extends TestCase {
+    String testName;
+
+    Element watchE;
+
+    private Properties props;
+
+    private WatchdogTest delegate;
+    private WatchdogClient wc;
+    
+    public WatchdogTest(String s) throws Throwable {
+        String[] comp = s.split(";");
+        Class c = Class.forName(comp[1]);
+        wc = (WatchdogClient) c.newInstance();
+        TestSuite suite = (TestSuite) wc.getSuite();
+        // need to encode the base, file, etc in the test name
+
+        System.err.println(s);
+
+        for (int i = 0; i < suite.testCount(); i++) {
+            WatchdogTest t = (WatchdogTest) suite.testAt(i);
+            if (s.equals(t.getName())) {
+                delegate = t;
+                return;
+            }
+        }
+    }
+
+    public WatchdogTest(Element watchE, Properties props, String testName) {
+        this.testName = testName;
+        this.watchE = watchE;
+        this.props = props;
+    }
+
+    public int countTestCases() {
+        return 1;
+    }
+
+    public String getName() {
+        return testName;
+    }
+
+    public void run(TestResult res) {
+        if (delegate != null) {
+            // Single method run
+            wc.beforeSuite();
+            delegate.run(res);
+            wc.afterSuite(res);
+            return;
+        }
+        GTest test = new GTest();
+        NamedNodeMap attrs = watchE.getAttributes();
+
+        for (int i = 0; i < attrs.getLength(); i++) {
+            Node n = attrs.item(i);
+            String name = n.getNodeName();
+            String value = n.getNodeValue();
+            value = IntrospectionUtils.replaceProperties(value, props, null);
+            try {
+                IntrospectionUtils.setProperty(test, name, value);
+            } catch (Exception e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+
+        try {
+            res.startTest(this);
+            IntrospectionUtils.execute(test, "execute");
+        } catch (Throwable e) {
+            res.addError(this, e);
+            // res.stop();
+        }
+
+        if (test.passCount == 1) {
+            res.endTest(this);
+            return;
+        } else {
+            if (test.lastError == null) {
+                res.addFailure(this, new AssertionFailedError(test.request
+                        + " " + test.description + "\n" + test.resultOut));
+            } else {
+                res.addError(this, test.lastError);
+            }
+        }
+        res.endTest(this);
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java b/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java
new file mode 100644 (file)
index 0000000..2dbef10
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ */
+package org.apache.tomcat.util.buf;
+
+import junit.framework.TestCase;
+
+public class UEncoderTest extends TestCase {
+    UEncoder enc=new UEncoder();
+    
+    /*
+     * 
+     * Test method for 'org.apache.tomcat.util.buf.UEncoder.encodeURL(String)'
+     * TODO: find the relevant rfc and apache tests and add more 
+     */
+    public void testEncodeURL() {
+
+        String eurl1=enc.encodeURL("test");
+        assertEquals("test", eurl1);
+        
+        eurl1=enc.encodeURL("/test");
+        assertEquals("%2ftest", eurl1);
+
+        // safe ranges
+        eurl1=enc.encodeURL("test$-_.");
+        assertEquals("test$-_.", eurl1);
+
+        eurl1=enc.encodeURL("test$-_.!*'(),");
+        assertEquals("test$-_.!*'(),", eurl1);
+
+        eurl1=enc.encodeURL("//test");
+        assertEquals("%2f%2ftest", eurl1);
+
+        
+    }
+
+}