Index: test/org/apache/catalina/startup/SimpleHttpClient.java =================================================================== --- test/org/apache/catalina/startup/SimpleHttpClient.java (revision 1395102) +++ test/org/apache/catalina/startup/SimpleHttpClient.java (working copy) @@ -39,7 +39,7 @@ */ public abstract class SimpleHttpClient { public static final String TEMP_DIR = - System.getProperty("java.io.tmpdir"); + System.getProperty("java.io.tmpdir"); public static final String CRLF = "\r\n"; @@ -48,14 +48,33 @@ public static final String REDIRECT_302 = "HTTP/1.1 302"; public static final String FAIL_400 = "HTTP/1.1 400"; public static final String FAIL_404 = "HTTP/1.1 404"; + public static final String TIMEOUT_408 = "HTTP/1.1 408"; public static final String FAIL_413 = "HTTP/1.1 413"; public static final String FAIL_50X = "HTTP/1.1 50"; public static final String FAIL_500 = "HTTP/1.1 500"; public static final String FAIL_501 = "HTTP/1.1 501"; + private static final String CONTENT_LENGTH_HEADER_PREFIX = + "Content-Length: "; + + protected static final String SESSION_COOKIE_NAME = "JSESSIONID"; + protected static final String SESSION_PARAMETER_NAME = + SESSION_COOKIE_NAME.toLowerCase(); + + private static final String COOKIE_HEADER_PREFIX = "Set-Cookie: "; private static final String SESSION_COOKIE_HEADER_PREFIX = - "Set-Cookie: JSESSIONID="; + COOKIE_HEADER_PREFIX + SESSION_COOKIE_NAME + "="; + private static final String REDIRECT_HEADER_PREFIX = "Location: "; + protected static final String SESSION_PATH_PARAMETER_PREFIX = + SESSION_PARAMETER_NAME + "="; + protected static final String SESSION_PATH_PARAMETER_TAILS = CRLF + ";?"; + + private static final String ELEMENT_HEAD = "<"; + private static final String ELEMENT_TAIL = ">"; + private static final String RESOURCE_TAG = "a"; + private static final String LOGIN_TAG = "form"; + private Socket socket; private Writer writer; private BufferedReader reader; @@ -63,13 +82,19 @@ private String[] request; private boolean useContinue = false; + private boolean useCookies = true; private int requestPause = 1000; private String responseLine; private List responseHeaders = new ArrayList<>(); - private String responseBody; + private String sessionId; private boolean useContentLength; + private int contentLength; + private String redirectUri; + private String responseBody; + private List bodyUriElments = new ArrayList<>(); + protected void setPort(int thePort) { port = thePort; } @@ -86,6 +111,14 @@ return useContinue; } + public void setUseCookies(boolean theUseCookiesFlag) { + useCookies = theUseCookiesFlag; + } + + public boolean getUseCookies() { + return useCookies; + } + public void setRequestPause(int theRequestPause) { requestPause = theRequestPause; } @@ -106,18 +139,20 @@ useContentLength = b; } + public void setSessionId(String theSessionId) { + sessionId = theSessionId; + } + public String getSessionId() { - for (String header : responseHeaders) { - if (header.startsWith(SESSION_COOKIE_HEADER_PREFIX)) { - header = header.substring(SESSION_COOKIE_HEADER_PREFIX.length()); - header = header.substring(0, header.indexOf(';')); - return header; - } - } - return null; + return sessionId; } - public void connect(int connectTimeout, int soTimeout) throws UnknownHostException, IOException { + public String getRedirectUri() { + return redirectUri; + } + + public void connect(int connectTimeout, int soTimeout) + throws UnknownHostException, IOException { final String encoding = "ISO-8859-1"; SocketAddress addr = new InetSocketAddress("localhost", port); socket = new Socket(); @@ -137,10 +172,11 @@ processRequest(true); } - public void processRequest(boolean readBody) throws IOException, InterruptedException { + public void processRequest(boolean wantBody) + throws IOException, InterruptedException { sendRequest(); - readResponse(readBody); + readResponse(wantBody); } @@ -158,13 +194,13 @@ } } - public void readResponse(boolean readBody) throws IOException { - // Reset fields use to hold response - responseLine = null; + public void readResponse(boolean wantBody) throws IOException { + // clear any residual data before starting on this response responseHeaders.clear(); responseBody = null; + bodyUriElments.clear(); - // Read the response + // Read the response status line responseLine = readLine(); // Is a 100 continue response expected? @@ -179,35 +215,116 @@ } } - // Put the headers into the map + // Put the headers into a map, and process interesting ones + processHeaders(); + + // Read the body, if requested and if one exists + processBody(wantBody); + + if (isResponse408()) { + // the session has timed out + sessionId = null; + } + } + + /* + * Accumulate the response headers into a map, and extract + * interesting details at the same time + */ + private void processHeaders() throws IOException { + // Reset response fields + redirectUri = null; + contentLength = -1; + String line = readLine(); - int cl = -1; - while (line!=null && line.length() > 0) { + while ((line != null) && (line.length() > 0)) { responseHeaders.add(line); + if (line.startsWith(CONTENT_LENGTH_HEADER_PREFIX)) { + contentLength = Integer.parseInt(line.substring(16)); + } + else if (line.startsWith(COOKIE_HEADER_PREFIX)) { + if (useCookies) { + String temp = line.substring( + SESSION_COOKIE_HEADER_PREFIX.length()); + temp = temp.substring(0, temp.indexOf(';')); + setSessionId(temp); + } + } + else if (line.startsWith(REDIRECT_HEADER_PREFIX)) { + redirectUri = line.substring(REDIRECT_HEADER_PREFIX.length()); + } line = readLine(); - if (line != null && line.startsWith("Content-Length: ")) { - cl = Integer.parseInt(line.substring(16)); - } } + } - // Read the body, if any + /* + * Read the body of the server response. Save its contents and + * search it for uri-elements only if requested + */ + private void processBody(boolean wantBody) throws IOException { + // Read the body, if one exists StringBuilder builder = new StringBuilder(); - if (readBody) { - if (cl > -1 && useContentLength) { - char[] body = new char[cl]; + if (wantBody) { + if (useContentLength && (contentLength > -1)) { + char[] body = new char[contentLength]; reader.read(body); builder.append(body); - } else { - line = readLine(); - while (line != null) { + } + else { + // not using content length, so just read it line by line + String line = null; + while ((line = readLine()) != null) { builder.append(line); - line = readLine(); } } } responseBody = builder.toString(); + extractUriElements(responseBody); } + /* + * Scan an html body for useful html uri elements. If any are found, + * then accumulate them. Test classes might not use them, but they + * are collected anyway. + */ + private void extractUriElements(String body) { + if (body.length() > 0) { + int ix = 0; + while ((ix = extractUriElement(body, ix, RESOURCE_TAG)) > 0){} + ix = 0; + while ((ix = extractUriElement(body, ix, LOGIN_TAG)) > 0){} + } + } + + /* + * Scan an html body for a given html uri element, starting from the + * given index into the source string. If any are found, simply + * accumulate them as literal strings, including angle brackets. + * note: nested elements will not be collected. + * + * @param body HTTP body of the response + * @param startIx offset into the body to resume the scan (for iteration) + * @param elementName to scan for (only one at a time) + * @returns the index into the body to continue the scan (for iteration) + */ + private int extractUriElement(String body, int startIx, String elementName) { + String detector = ELEMENT_HEAD + elementName + " "; + int iStart = body.indexOf(detector, startIx); + if (iStart > -1) { + int iEnd = body.indexOf(ELEMENT_TAIL, iStart); + if (iEnd < 0) { + throw new IllegalArgumentException( + "Response body check failure.\n" + + "element [" + detector + "] is not terminated with [" + + ELEMENT_TAIL + "]\nActual: [" + body + "]"); + } + String element = body.substring(iStart, iEnd); + bodyUriElments.add(element); + iStart += element.length(); + } + return iStart; + } + public String readLine() throws IOException { return reader.readLine(); } @@ -253,6 +370,10 @@ return getResponseLine().startsWith(FAIL_404); } + public boolean isResponse408() { + return getResponseLine().startsWith(TIMEOUT_408); + } + public boolean isResponse413() { return getResponseLine().startsWith(FAIL_413); }