/* * 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.catalina.startup; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * Simple client for unit testing. It isn't robust, it isn't secure and * should not be used as the basis for production code. Its only purpose * is to do the bare minimum for the unit tests. */ public abstract class SimpleHttpClient { public static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); public static final String CR = "\r"; public static final String LF = "\n"; public static final String CRLF = CR + LF; public static final String INFO_100 = "HTTP/1.1 100 "; public static final String OK_200 = "HTTP/1.1 200 "; public static final String REDIRECT_302 = "HTTP/1.1 302 "; public static final String REDIRECT_303 = "HTTP/1.1 303 "; 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_417 = "HTTP/1.1 417 "; 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(Locale.ENGLISH); private static final String COOKIE_HEADER_PREFIX = "Set-Cookie: "; private static final String SESSION_COOKIE_HEADER_PREFIX = 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; private int port = 8080; private String[] request; private boolean useContinue = false; private boolean useCookies = true; private int requestPause = 1000; private String responseLine; private List<String> responseHeaders = new ArrayList<>(); private String sessionId; private boolean useContentLength; private int contentLength; private String redirectUri; private String responseBody; private List<String> bodyUriElements = null; public void setPort(int thePort) { port = thePort; } public void setRequest(String[] theRequest) { request = theRequest; } /* * Expect the server to reply with 100 Continue interim response */ public void setUseContinue(boolean theUseContinueFlag) { useContinue = theUseContinueFlag; } public boolean getUseContinue() { return useContinue; } public void setUseCookies(boolean theUseCookiesFlag) { useCookies = theUseCookiesFlag; } public boolean getUseCookies() { return useCookies; } public void setRequestPause(int theRequestPause) { requestPause = theRequestPause; } public String getResponseLine() { return responseLine; } public List<String> getResponseHeaders() { return responseHeaders; } public String getResponseBody() { return responseBody; } /** * Return opening tags of HTML elements that were extracted by the * {@link #extractUriElements()} method. * * <p> * Note, that {@link #extractUriElements()} method has to be called * explicitly. * * @return List of HTML tags, accumulated by {@link #extractUriElements()} * method, or {@code null} if the method has not been called yet. */ public List<String> getResponseBodyUriElements() { return bodyUriElements; } public void setUseContentLength(boolean b) { useContentLength = b; } public void setSessionId(String theSessionId) { sessionId = theSessionId; } public String getSessionId() { return sessionId; } 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(); socket.setSoTimeout(soTimeout); socket.connect(addr,connectTimeout); OutputStream os = socket.getOutputStream(); writer = new OutputStreamWriter(os, encoding); InputStream is = socket.getInputStream(); Reader r = new InputStreamReader(is, encoding); reader = new BufferedReader(r); } public void connect() throws UnknownHostException, IOException { connect(0,0); } public void processRequest() throws IOException, InterruptedException { processRequest(true); } public void processRequest(boolean wantBody) throws IOException, InterruptedException { sendRequest(); readResponse(wantBody); } /* * Send the component parts of the request * (be tolerant and simply skip null entries) */ public void sendRequest() throws InterruptedException, IOException { boolean first = true; for (String requestPart : request) { if (requestPart != null) { if (first) { first = false; } else { Thread.sleep(requestPause); } writer.write(requestPart); writer.flush(); } } } public void readResponse(boolean wantBody) throws IOException { // clear any residual data before starting on this response responseHeaders.clear(); responseBody = null; if (bodyUriElements != null) { bodyUriElements.clear(); } // Read the response status line responseLine = readLine(); // Is a 100 continue response expected? if (useContinue) { if (isResponse100()) { // Skip the blank after the 100 Continue response readLine(); // Now get the final response responseLine = readLine(); } else { throw new IOException("No 100 Continue response"); } } // 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(); 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(); } } /* * 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 (wantBody) { if (useContentLength && (contentLength > -1)) { char[] body = new char[contentLength]; reader.read(body); builder.append(body); } else { // not using content length, so just read it line by line String line = null; while ((line = readLine()) != null) { builder.append(line); } } } responseBody = builder.toString(); } /** * Scan the response body for opening tags of certain HTML elements * (<a>, <form>). If any are found, then accumulate them. * * <p> * Note: This method has the following limitations: a) It assumes that the * response is HTML. b) It searches for lowercase tags only. * * @see #getResponseBodyUriElements() */ public void extractUriElements() { bodyUriElements = new ArrayList<>(); if (responseBody.length() > 0) { int ix = 0; while ((ix = extractUriElement(responseBody, ix, RESOURCE_TAG)) > 0){ // loop } ix = 0; while ((ix = extractUriElement(responseBody, ix, LOGIN_TAG)) > 0){ // loop } } } /* * 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); bodyUriElements.add(element); iStart += element.length(); } return iStart; } public String readLine() throws IOException { return reader.readLine(); } public void disconnect() throws IOException { writer.close(); reader.close(); socket.close(); } public void reset() { socket = null; writer = null; reader = null; request = null; requestPause = 1000; useContinue = false; resetResponse(); } public void resetResponse() { responseLine = null; responseHeaders = new ArrayList<>(); responseBody = null; } public boolean responseLineStartsWith(String expected) { String line = getResponseLine(); if (line == null) { return false; } return line.startsWith(expected); } public boolean isResponse100() { return responseLineStartsWith(INFO_100); } public boolean isResponse200() { return responseLineStartsWith(OK_200); } public boolean isResponse302() { return responseLineStartsWith(REDIRECT_302); } public boolean isResponse303() { return responseLineStartsWith(REDIRECT_303); } public boolean isResponse400() { return responseLineStartsWith(FAIL_400); } public boolean isResponse404() { return responseLineStartsWith(FAIL_404); } public boolean isResponse408() { return responseLineStartsWith(TIMEOUT_408); } public boolean isResponse413() { return responseLineStartsWith(FAIL_413); } public boolean isResponse417() { return responseLineStartsWith(FAIL_417); } public boolean isResponse50x() { return responseLineStartsWith(FAIL_50X); } public boolean isResponse500() { return responseLineStartsWith(FAIL_500); } public boolean isResponse501() { return responseLineStartsWith(FAIL_501); } public Socket getSocket() { return socket; } public abstract boolean isResponseBodyOK(); }