/* * Created on May 26, 2004 * * Paros and its related class files. * * Paros is an HTTP/HTTPS proxy for assessing web application security. * Copyright (C) 2003-2004 Chinotec Technologies Company * * This program is free software; you can redistribute it and/or * modify it under the terms of the Clarified Artistic License * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Clarified Artistic License for more details. * * You should have received a copy of the Clarified Artistic License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // ZAP: 2012/01/12 Changed the method createRequestMethod to always use CRLF // ZAP: 2012/03/15 Changed to use the class StringBuilder instead of StringBuffer // ZAP: 2012/05/04 Changed to use the class ZapGetMethod instead of org.apache.commons.httpclient.methods.GetMethod // ZAP: 2013/01/23 Clean up of exception handling/logging. // ZAP: 2013/06/17 Issue 687: Change HTTP response header parser to be less strict // ZAP: 2013/07/10 Issue 721: Non POST and PUT requests receive a 504 when server expects a request body // ZAP: 2016/05/16 Throw exception if failed to set the request URI // ZAP: 2016/10/13 Include URI in exception's message package org.parosproxy.paros.network; import java.util.regex.Pattern; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpVersion; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.EntityEnclosingMethod; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.log4j.Logger; import org.zaproxy.zap.ZapGetMethod; import org.zaproxy.zap.network.ZapDeleteMethod; import org.zaproxy.zap.network.ZapHeadMethod; import org.zaproxy.zap.network.ZapOptionsMethod; import org.zaproxy.zap.network.ZapPostMethod; import org.zaproxy.zap.network.ZapPutMethod; import org.zaproxy.zap.network.ZapTraceMethod; public class HttpMethodHelper { private static final Logger logger = Logger.getLogger(HttpMethodHelper.class); private static final String OPTIONS = "OPTIONS"; private static final String GET = "GET"; private static final String HEAD = "HEAD"; private static final String POST = "POST"; private static final String PUT = "PUT"; private static final String DELETE = "DELETE"; private static final String TRACE = "TRACE"; // TODO: What's up with the CONNECT method? //private static final String CONNECT = "CONNECT"; private static final String CRLF = "\r\n"; private static final String LF = "\n"; private static final Pattern patternCRLF = Pattern.compile("\\r\\n", Pattern.MULTILINE); private static final Pattern patternLF = Pattern.compile("\\n", Pattern.MULTILINE); private String mUserAgent = ""; public void setUserAgent(String userAgent) { mUserAgent = userAgent; } // Not used - all abstract using Generic method but GET cannot be used. public HttpMethod createRequestMethodNew(HttpRequestHeader header, HttpBody body) throws URIException { HttpMethod httpMethod = null; String method = header.getMethod(); URI uri = header.getURI(); String version = header.getVersion(); httpMethod = new GenericMethod(method); httpMethod.setURI(uri); HttpMethodParams httpParams = httpMethod.getParams(); // default to use HTTP 1.0 httpParams.setVersion(HttpVersion.HTTP_1_0); if (version.equalsIgnoreCase(HttpHeader.HTTP11)) { httpParams.setVersion(HttpVersion.HTTP_1_1); } // set various headers int pos = 0; // ZAP: FindBugs fix - always initialise pattern Pattern pattern = patternCRLF; String delimiter = CRLF; String msg = header.getHeadersAsString(); if ((pos = msg.indexOf(CRLF)) < 0) { if ((pos = msg.indexOf(LF)) < 0) { delimiter = LF; pattern = patternLF; } } else { delimiter = CRLF; pattern = patternCRLF; } String[] split = pattern.split(msg); String token = null; String name = null; String value = null; //String host = null; for (int i=0; i<split.length; i++) { token = split[i]; if (token.equals("")) { continue; } if ((pos = token.indexOf(":")) < 0) { return null; } name = token.substring(0, pos).trim(); value = token.substring(pos +1).trim(); httpMethod.addRequestHeader(name, value); } // set body if post method or put method if (body != null && body.length() > 0) { EntityEnclosingMethod generic = (EntityEnclosingMethod) httpMethod; // generic.setRequestEntity(new StringRequestEntity(body.toString())); generic.setRequestEntity(new ByteArrayRequestEntity(body.getBytes())); } httpMethod.setFollowRedirects(false); return httpMethod; } // This is the currently in use method. // may be replaced by the New method - however the New method is not yet fully tested so this is stil used. public HttpMethod createRequestMethod(HttpRequestHeader header, HttpBody body) throws URIException { HttpMethod httpMethod = null; String method = header.getMethod(); URI uri = header.getURI(); String version = header.getVersion(); if (method == null || method.trim().length() < 3) { throw new URIException("Invalid HTTP method: " + method); } if (method.equalsIgnoreCase(GET)) { //httpMethod = new GetMethod(); // ZAP: avoid discarding HTTP status code 101 that is used for WebSocket upgrade httpMethod = new ZapGetMethod(); } else if (method.equalsIgnoreCase(POST)) { httpMethod = new ZapPostMethod(); } else if (method.equalsIgnoreCase(DELETE)) { httpMethod = new ZapDeleteMethod(); } else if (method.equalsIgnoreCase(PUT)) { httpMethod = new ZapPutMethod(); } else if (method.equalsIgnoreCase(HEAD)) { httpMethod = new ZapHeadMethod(); } else if (method.equalsIgnoreCase(OPTIONS)) { httpMethod = new ZapOptionsMethod(); } else if (method.equalsIgnoreCase(TRACE)) { httpMethod = new ZapTraceMethod(uri.toString()); } else { httpMethod = new GenericMethod(method); } try { httpMethod.setURI(uri); } catch (Exception e1) { throw new URIException("Failed to set URI [" + uri + "]: " + e1.getMessage()); } HttpMethodParams httpParams = httpMethod.getParams(); // default to use HTTP 1.0 httpParams.setVersion(HttpVersion.HTTP_1_0); if (version.equalsIgnoreCase(HttpHeader.HTTP11)) { httpParams.setVersion(HttpVersion.HTTP_1_1); } // set various headers int pos = 0; // ZAP: changed to always use CRLF, like the HttpHeader Pattern pattern = patternCRLF; String delimiter = header.getLineDelimiter(); // ZAP: Shouldn't happen as the HttpHeader always uses CRLF if (delimiter.equals(LF)) { delimiter = LF; pattern = patternLF; } String msg = header.getHeadersAsString(); String[] split = pattern.split(msg); String token = null; String name = null; String value = null; for (int i=0; i<split.length; i++) { token = split[i]; if (token.equals("")) { continue; } if ((pos = token.indexOf(":")) < 0) { return null; } name = token.substring(0, pos).trim(); value = token.substring(pos +1).trim(); httpMethod.addRequestHeader(name, value); } // set body if post method or put method if (body != null && body.length() > 0 && (httpMethod instanceof EntityEnclosingMethod)) { EntityEnclosingMethod post = (EntityEnclosingMethod) httpMethod; // post.setRequestEntity(new StringRequestEntity(body.toString())); post.setRequestEntity(new ByteArrayRequestEntity(body.getBytes())); } httpMethod.setFollowRedirects(false); return httpMethod; } /* * Build a HttpMethod (eg GET, POST) from raw string. All headers will be set accordingly as in * the raw string. * @param request raw request string with header and body. * @param isSecure true if the connection is SSL. * @return an unexecuted HttpMethod */ /* This is the original code using a String as request. Now obsolete * private HttpMethod createRequestMethod(String request, boolean isSecure) throws HttpException, URIException { HttpMethod httpMethod = null; Pattern pattern = null; String delimiter = CRLF; int pos = 0; if (request == null || request.equals("")) { throw new HttpException ("Null or empty request"); } if ((pos = request.indexOf(CRLF)) < 0) { if ((pos = request.indexOf(LF)) < 0) { throw new HttpException ("Invalid HTTP request with missing CR/LF"); } else { delimiter = LF; pattern = patternLF; } } else { delimiter = CRLF; pattern = patternCRLF; } String[] split = pattern.split(request); String startLine = split[0]; httpMethod = createMethodFromStartLine(startLine); String token = null; String name = null; String value = null; String host = null; for (int i=1; i<split.length; i++) { token = split[i]; if (token.equals("")) { continue; } if ((pos = token.indexOf(":")) < 0) { return null; } name = token.substring(0, pos).trim(); value = token.substring(pos +1).trim(); if (name.equalsIgnoreCase(HEADER_HOST)) { host = value; } httpMethod.addRequestHeader(name, value); } URI uri = httpMethod.getURI(); boolean isUriChanged = false; if (uri.getScheme() == null || uri.getScheme().equals("")) { uri = new URI(HTTP + "://" + host + uri.toString(), true); isUriChanged = true; } if (isSecure && uri.getScheme().equalsIgnoreCase(HTTP)) { uri = new URI(uri.toString().replaceFirst(HTTP, HTTPS), true); isUriChanged = true; } if (isUriChanged) { httpMethod.setURI(uri); } httpMethod.setFollowRedirects(false); return httpMethod; } private HttpMethod createMethodFromStartLine(String startLine) throws URIException { HttpMethod httpMethod = null; Matcher matcher = patternRequestLine.matcher(startLine); if (!matcher.find()) { throw new URIException("Missing startLine in HTTP request"); } String method = matcher.group(1); String uri = matcher.group(2); String version = matcher.group(3); if (method.equalsIgnoreCase(GET)) { httpMethod = new GetMethod(); } else if (method.equalsIgnoreCase(POST)) { httpMethod = new PostMethod(); } else if (method.equalsIgnoreCase(DELETE)) { httpMethod = new DeleteMethod(); } else if (method.equalsIgnoreCase(PUT)) { httpMethod = new PutMethod(); } else if (method.equalsIgnoreCase(HEAD)) { httpMethod = new HeadMethod(); } else if (method.equalsIgnoreCase(OPTIONS)) { httpMethod = new OptionsMethod(); } else if (method.equalsIgnoreCase(TRACE)) { httpMethod = new TraceMethod(uri); } else { // httpMethod = GenericMethod(); } httpMethod.setURI(new URI(uri, true)); HttpMethodParams httpParams = httpMethod.getParams(); // default to use HTTP 1.0 httpParams.setVersion(HttpVersion.HTTP_1_0); if (version.equalsIgnoreCase(HTTP11)) { httpParams.setVersion(HttpVersion.HTTP_1_1); } httpMethod.setParams(httpParams); return httpMethod; } */ public static void updateHttpRequestHeaderSent(HttpRequestHeader req, HttpMethod httpMethodSent) { // Not used yet, no need to update request. if (!httpMethodSent.hasBeenUsed()) { return; } StringBuilder sb = new StringBuilder(200); String name = null; String value = null; // add status line sb.append(req.getPrimeHeader()).append(CRLF); Header[] header = httpMethodSent.getRequestHeaders(); for (int i=0; i<header.length; i++) { name = header[i].getName(); value = header[i].getValue(); sb.append(name).append(": ").append(value).append(CRLF); } sb.append(CRLF); try { req.setMessage(sb.toString()); } catch (HttpMalformedHeaderException e) { logger.error(e.getMessage(), e); } } private static String getHttpResponseHeaderAsString(HttpMethod httpMethod) { StringBuilder sb = new StringBuilder(200); String name = null; String value = null; // add status line sb.append(httpMethod.getStatusLine().toString()).append(CRLF); Header[] header = httpMethod.getResponseHeaders(); for (int i=0; i<header.length; i++) { name = header[i].getName(); value = header[i].getValue(); sb.append(name).append(": ").append(value).append(CRLF); } sb.append(CRLF); return sb.toString(); } public static HttpResponseHeader getHttpResponseHeader(HttpMethod httpMethod) throws HttpMalformedHeaderException { return new HttpResponseHeader(getHttpResponseHeaderAsString(httpMethod)); } /* public static String getHttpRequestHeaderAsString(HttpMethod httpMethod) { StringBuilder sb = new StringBuilder(200); String name = null; String value = null; // add status line try { sb.append(httpMethod.getName()).append(' ').append(httpMethod.getURI().toString()).append(' ').append(httpMethod.getParams().getVersion()).append(CRLF); } catch (URIException e) { } Header[] header = httpMethod.getRequestHeaders(); for (int i=0; i<header.length; i++) { name = header[i].getName(); value = header[i].getValue(); sb.append(name).append(": ").append(value).append(CRLF); } sb.append(CRLF); return sb.toString(); } */ }