From 236968dcf0e7c04f01269c89d6ed979db3843ca9 Mon Sep 17 00:00:00 2001 From: costin Date: Sun, 5 Apr 2009 01:36:27 +0000 Subject: [PATCH] Unit tests, including the Watchdog converted to Junit. git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@762039 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/tomcat/lite/LiteTestHelper.java | 121 +++ .../apache/tomcat/lite/LiteWatchdogJspTests.java | 51 + .../tomcat/lite/LiteWatchdogServletTests.java | 107 ++ .../org/apache/tomcat/lite/PropertiesSpiTest.java | 54 + .../test/org/apache/tomcat/lite/SimpleServlet.java | 22 + .../tomcat/lite/TomcatLiteNoConnectorTest.java | 82 ++ .../apache/tomcat/lite/TomcatLiteSimpleTest.java | 47 + .../tomcat/test/watchdog/CookieController.java | 640 ++++++++++++ .../org/apache/tomcat/test/watchdog/GTest.java | 1088 ++++++++++++++++++++ .../apache/tomcat/test/watchdog/HttpCookie.java | 290 ++++++ .../apache/tomcat/test/watchdog/RfcDateParser.java | 103 ++ .../tomcat/test/watchdog/WatchdogClient.java | 146 +++ .../tomcat/test/watchdog/WatchdogHttpClient.java | 406 ++++++++ .../apache/tomcat/test/watchdog/WatchdogTest.java | 105 ++ .../org/apache/tomcat/util/buf/UEncoderTest.java | 36 + 15 files changed, 3298 insertions(+) create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/LiteTestHelper.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogJspTests.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogServletTests.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/PropertiesSpiTest.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/SimpleServlet.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteNoConnectorTest.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteSimpleTest.java create mode 100755 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java create mode 100755 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/GTest.java create mode 100755 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java create mode 100755 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTest.java create mode 100644 modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java 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 index 000000000..66477dfcb --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteTestHelper.java @@ -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 index 000000000..b14dfca95 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogJspTests.java @@ -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 index 000000000..a7b7f6468 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/LiteWatchdogServletTests.java @@ -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 index 000000000..53a8ad5b3 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/PropertiesSpiTest.java @@ -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 index 000000000..fd90ddf6f --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/SimpleServlet.java @@ -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 index 000000000..30d944fb0 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteNoConnectorTest.java @@ -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 index 000000000..3b0b53446 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/TomcatLiteSimpleTest.java @@ -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 index 000000000..f6d069935 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java @@ -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. + *

+ * 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 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. + *

+ *

+ * 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 index 000000000..b809d7c3b --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/GTest.java @@ -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 GTest instance. + * + */ + public GTest() { + } + + /** + * setTestSession adds a + * CookieController for the value of sessionName + * + * @param sessionName a String 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() ); + } + } + + /** + * setTestName sets the current test name. + * + * @param tn current testname. + */ + public void setTestName ( String tn ) { + testName = tn; + } + + /** + * setAssertion sets the assertion text + * for the current test. + * + * @param assertion assertion text + */ + public void setAssertion ( String assertion ) { + this.assertion = assertion; + } + + /** + * setTestStrategy sets the test strategy + * for the current test. + * + * @param strategy test strategy text + */ + public void setTestStrategy ( String strategy ) { + testStrategy = strategy; + } + + /** + * getTestName returns the current + * test name. + * + * @return a String value + */ + public String getTestName( ) { + return testName; + } + + /** + * getAssertion returns the current + * assertion text. + * + * @return a String value + */ + public String getAssertion( ) { + return assertion; + } + + /** + * getTestStrategy returns the current + * test strategy test. + * + * @return a String value + */ + public String getTestStrategy( ) { + return testStrategy; + } + + /** + * setFirstTask denotes that current task + * being executed is the first task within the list. + * + * @param a boolean value + */ + public void setFirstTask( boolean val ) { + firstTask = val; + } + + + /** + * setLastTask denotes that the current task + * being executed is the last task within the list. + * + * @param a boolean value + */ + public void setLastTask ( boolean val ) { + lastTask = val; + } + + /** + * setPrefix sets the protocol + * prefix. Defaults to "http" + * + * @param prefix Either http or https + */ + public void setPrefix( String prefix ) { + this.prefix = prefix; + } + + /** + * setHost sets hostname where + * the target server is running. Defaults to + * "localhost" + * + * @param h a String value + */ + public void setHost( String h ) { + this.host = h; + } + + /** + * setPort sets the port + * that the target server is listening on. + * Defaults to "8080" + * + * @param portS a String value + */ + public void setPort( String portS ) { + this.port = Integer.valueOf( portS ).intValue(); + } + + /** + * setExactMatch 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 String value + */ + public void setExactMatch( String exact ) { + exactMatch = Boolean.valueOf( exact ).booleanValue(); + } + + /** + * setContent String value upon which + * the request header Content-Length is based upon. + * + * @param s a String value + */ + public void setContent( String s ) { + this.content = s; + } + + /** + * setDebug enables debug output. + * By default, this is disabled ( value of "0" ). + * + * @param debugS a String value + */ + public void setDebug( String debugS ) { + debug = Integer.valueOf( debugS ).intValue(); + } + + /** + * setMagnitude Expected return + * value of the test execution. + * Defaults to "true" + * + * @param magnitudeS a String value + */ + public void setMagnitude( String magnitudeS ) { + magnitude = Boolean.valueOf( magnitudeS ).booleanValue(); + } + + /** + * setGoldenFile 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; + } + + /** + * setExpectResponseBody sets a flag + * to indicate if a response body is expected from the + * server or not + * + * @param b a boolean value + */ + public void setExpectResponseBody( boolean b ) { + this.expectResponseBody = b; + } + + /** + * setExpectHeaders Configures GTest + * to look for the header passed in the server's + * response. + * + * @param s a String value in the + * format of : + */ + 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 ); + } + } + + /** + * setUnexpectedHeaders Configures GTest + * to look for the header passed to validate that it + * doesn't exist in the server's response. + * + * @param s a String value in the + * format of : + */ + public void setUnexpectedHeaders( String s ) { + this.unexpectedHeaders = new HashMap(); + setHeaderDetails( s, unexpectedHeaders, false ); + } + + public void setNested( String s ) { + nested = Boolean.valueOf( s ).booleanValue(); + } + + /** + * setResponseMatch Match the + * passed value in the server's response. + * + * @param s a String value + */ + public void setResponseMatch( String s ) { + this.responseMatch = s; + } + + /** + * setRequest Sets the HTTP/HTTPS + * request to be sent to the target server + * Ex. + * GET /servlet_path/val HTTP/1.0 + * + * @param s a String value in the form + * of METHOD PATH HTTP_VERSION + */ + public void setRequest ( String s ) { + this.request = s; + } + + /** + * setReturnCode 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 setReturnCodeMsg Sets the expected + * return message to be found in the server's + * response. + * + * @param code a valid HTTP resonse status code + * @param message a String value + */ + public void setReturnCodeMsg( String message ) { + this.returnCodeMsg = message; + } + + /** + * setRequestHeaders Configures the request + * headers GTest should send to the target server. + * + * @param s a String value in for format + * of : + */ + 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); +// } + + /** + * execute 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( "PASS\n" ); + } + System.out.println( " PASSED " + testName + "\n (" + request + ")" ); + } else if ( hasFailed && !nested ){ + failureCount++; + if ( resultOut != null ) { + resultOut.append( "FAIL\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; + } + } + } + + /** + * checkResponse Executes various response + * checking mechanisms against the server's response. + * Checks include: + *

+ * + * @param testCondition a boolean value + * @return a boolean 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 = "" + returnCode + "\n"; + String expectedReasonPhrase = "" + returnCodeMsg + ""; + actualString = "" + responseLine + "\n"; + resultOut.append( expectedStatusCode ); + resultOut.append( expectedReasonPhrase ); + resultOut.append( actualString ); + } + + return false; + } + } + } else { + resultOut.append("\nWrong Http version: " + responseLine + + ""); + return false; + } + } else { + resultOut.append("\nNo response from server" ); + 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 = "" + currentHeaderField + ": " + currentHeaderValue + "\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( "" + headerName + ": " + val[ i ] + "\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 = "" + new String(expResult) + "\n"; + actualString = "" + + (responseBody != null ? new String(responseBody) : "null" ) + + "\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; + } + } + + + + /** + * getExpectedResult 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; + } + } + + /** + * compare 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 byte[] value + * @param fromGoldenFile a byte[] value + * @return boolean 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; + } + + /** + * compareWeak creates new Strings from the passed arrays + * and then uses a StringTokenizer to compare non-whitespace tokens. + * + * @param fromServer a byte[] value + * @param fromGoldenFile a byte[] value + * @return a boolean 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; + } + + /** + * readBody reads the body of the response + * from the InputStream. + * + * @param input an InputStream + * @return a byte[] 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(); + } + + /** + * setHeaderDetails Wrapper method for parseHeader. + * Allows easy addition of headers to the specified + * HashMap + * + * @param line a String value + * @param headerMap a HashMap value + * @param isRequest a boolean 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 ); + } + } + + /** + * parseHeader 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 aHashMap 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 ); + } + + /** + * dumpHex helper method to dump formatted + * hex output of the server response and the goldenfile. + * + * @param serverResponse a byte[] value + * @param goldenFile a byte[] 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() ); + + } + + /** + * cloneHeaders returns a "cloned" + * HashMap of the map passed in. + * + * @param map a HashMap value + * @return a HashMap 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 index 000000000..33e21a07c --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java @@ -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 index 000000000..2aeebb7f1 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java @@ -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 index 000000000..21f427605 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java @@ -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 index 000000000..0f52a6c37 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java @@ -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; + } + + } + + /** + * readBody reads the body of the response + * from the InputStream. + * + * @param input an InputStream + * @return a byte[] 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 null 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 ); + } + + + + /** + * CRBufferedInputStream 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 CRBufferedInputStream instance. + * + * @param in an InputStream value + */ + public CRBufferedInputStream( InputStream in ) { + super( in, DEFAULT_BUFFER ); + } + + /** + * read 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 int 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; + } + + /** + * fill 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 index 000000000..54e8a4432 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTest.java @@ -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 index 000000000..2dbef1031 --- /dev/null +++ b/modules/tomcat-lite/test/org/apache/tomcat/util/buf/UEncoderTest.java @@ -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); + + + } + +} -- 2.11.0