All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.parosproxy.paros.network.HttpMessage Maven / Gradle / Ivy

Go to download

The Zed Attack Proxy (ZAP) is an easy to use integrated penetration testing tool for finding vulnerabilities in web applications. It is designed to be used by people with a wide range of security experience and as such is ideal for developers and functional testers who are new to penetration testing. ZAP provides automated scanners as well as a set of tools that allow you to find security vulnerabilities manually.

The newest version!
/*
 * Created on 22 Jun 2004.
 *
 * Paros and its related class files.
 *
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2006 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: 2011/10/29 Fixed cookie parsing
// ZAP: 2012/03/15 Changed to use the classes HttpRequestBody and HttpResponseBody.
// Changed to use the byte[] body. Changed to use the class StringBuilder instead
// of StringBuffer. Reworked some methods.
// ZAP: 2012/04/23 Added @Override annotation to the appropriate method.
// ZAP: 2012/06/11 Added method boolean isWebSocketUpgrade()
// ZAP: 2012/07/02 Implement Message interface for more flexibility.
// ZAP: 2012/06/24 Added method to add Cookies of type java.net.HttpCookie to request header
// ZAP: 2012/08/01 Issue 332: added support for Modes
// ZAP: 2012/08/09 Added HttpSession field
// ZAP: 2012/10/08 Issue 391: Performance improvements
// ZAP: 2013/01/23 Clean up of exception handling/logging.
// ZAP: 2013/04/08 Issue 605: Force intercepts via header
// ZAP: 2013/07/25 Added support for sending the message from the perspective of a User
// ZAP: 2013/09/26 Issue 716: ZAP flags its own HTTP responses
// ZAP: 2013/11/16 Issue 867: HttpMessage#getFormParams should return an empty TreeSet if
// the request body is not "x-www-form-urlencoded"
// ZAP: 2014/01/06 Issue 965: Support 'single page' apps and 'non standard' parameter separators
// ZAP: 2014/03/23 Tidy up, do not allow to set null request/response headers/bodies.
// ZAP: 2014/03/28 Issue 1127: 	Allow scripts to generate breaks
// ZAP: 2014/06/16 Issue 1217: Table format does not display information when charset is
// present in Content-Type header
// ZAP: 2015/02/09 Fix NullPointerException in equals(Object) when comparing with empty messages
// ZAP: 2015/08/07 Issue 1768: Update to use a more recent default user agent
// ZAP: 2015/08/19 Deprecate/change methods with unused parameters
// ZAP: 2016/05/31 Implement hashCode()
// ZAP: 2017/02/01 Set whether or not the charset should be determined when setting a (String)
// response.
// ZAP: 2017/08/23 queryEquals correct comparison and add JavaDoc. equalType update JavaDoc.
// ZAP: 2018/03/13 Added toEventData()
// ZAP: 2018/04/04 Add a copy constructor.
// ZAP: 2018/08/10 Use non-deprecated HttpRequestHeader constructor (Issue 4846).
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
// ZAP: 2019/12/09 Address deprecation of getHeaders(String) Vector method.
// ZAP: 2020/07/31 Tidy up parameter methods
// ZAP: 2020/11/26 Use Log4j 2 classes for logging.
// ZAP: 2020/12/09 Handle content encodings in request/response bodies.
// ZAP: 2021/04/01 Detect WebSocket upgrade messages having multiple Connection directives
// ZAP: 2021/05/11 Fixed conversion of Request Method to/from CONNECT
// ZAP: 2021/05/14 Add missing override annotation.
// ZAP: 2022/09/21 Use format specifiers instead of concatenation when logging.
// ZAP: 2023/01/10 Tidy up logger.
// ZAP: 2023/10/17 Allow to set content encodings handler.
package org.parosproxy.paros.network;

import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.Model;
import org.zaproxy.zap.eventBus.Event;
import org.zaproxy.zap.extension.httppanel.Message;
import org.zaproxy.zap.extension.httpsessions.HttpSession;
import org.zaproxy.zap.model.NameValuePair;
import org.zaproxy.zap.network.HttpEncoding;
import org.zaproxy.zap.network.HttpEncodingDeflate;
import org.zaproxy.zap.network.HttpEncodingGzip;
import org.zaproxy.zap.network.HttpRequestBody;
import org.zaproxy.zap.network.HttpResponseBody;
import org.zaproxy.zap.users.User;

/**
 * Representation of a HTTP message request (header and body) and response (header and body) pair.
 */
public class HttpMessage implements Message {

    public static final String EVENT_DATA_URI = "uri";
    public static final String EVENT_DATA_REQUEST_HEADER = "requestHeader";
    public static final String EVENT_DATA_REQUEST_BODY = "requestBody";
    public static final String EVENT_DATA_RESPONSE_HEADER = "responseHeader";
    public static final String EVENT_DATA_RESPONSE_BODY = "responseBody";

    public static final String MESSAGE_TYPE = "HTTP";

    private static final HttpEncodingsHandler DEFAULT_CONTENT_ENCODINGS_HANDLER =
            (header, body) -> {
                String encoding = header.getHeader(HttpHeader.CONTENT_ENCODING);
                if (encoding == null || encoding.isEmpty()) {
                    body.setContentEncodings(List.of());
                    return;
                }

                List encodings = List.of();
                if (encoding.contains(HttpHeader.DEFLATE)) {
                    encodings = List.of(HttpEncodingDeflate.getSingleton());
                } else if (encoding.contains(HttpHeader.GZIP)) {
                    encodings = List.of(HttpEncodingGzip.getSingleton());
                }

                body.setContentEncodings(encodings);
            };

    private static HttpEncodingsHandler contentEncodingsHandler;

    private HttpRequestHeader mReqHeader = new HttpRequestHeader();
    private HttpRequestBody mReqBody = new HttpRequestBody();
    private HttpResponseHeader mResHeader = new HttpResponseHeader();
    private HttpResponseBody mResBody = new HttpResponseBody();
    private Object userObject = null;
    private int timeElapsed = 0;
    private long timeSent = 0;
    // private String tag = "";
    // ZAP: Added note to HttpMessage
    private String note = "";
    // ZAP: Added historyRef
    private HistoryReference historyRef = null;
    // ZAP: Added logger
    private static final Logger LOGGER = LogManager.getLogger(HttpMessage.class);
    // ZAP: Added HttpSession
    private HttpSession httpSession = null;
    // ZAP: Added support for requesting the message to be sent as a particular User
    private User requestUser;
    // Can be set by scripts to force a break
    private boolean forceIntercept = false;

    /**
     * Flag that indicates if the response has been received or not from the target host.
     *
     * 

Default is {@code false}. */ private boolean responseFromTargetHost = false; public HistoryReference getHistoryRef() { return historyRef; } public void setHistoryRef(HistoryReference historyRef) { this.historyRef = historyRef; } /** * Gets the http session associated with this message. * * @return the http session */ public HttpSession getHttpSession() { return this.httpSession; } /** * Sets the http session associated with this message. * * @param session the new http session */ public void setHttpSession(HttpSession session) { this.httpSession = session; } /** Constructor for a empty HTTP message. */ public HttpMessage() {} /** * Constructs a {@code HttpMessage} with a HTTP/1.1 GET request to the given URI. * *

The following headers are automatically added: * *

    *
  • {@code Host}, with the domain and port from the given URI. *
  • {@code User-Agent}, using the {@link HttpRequestHeader#getDefaultUserAgent()}. *
  • {@code Pragma: no-cache} *
  • {@code Cache-Control: no-cache}, if version is HTTP/1.1 *
  • {@code Content-Type: application/x-www-form-urlencoded}, if the method is POST or PUT. *
* * @param uri the request target. * @throws HttpMalformedHeaderException if the resulting HTTP header is malformed. */ public HttpMessage(URI uri) throws HttpMalformedHeaderException { setRequestHeader(new HttpRequestHeader(HttpRequestHeader.GET, uri, HttpHeader.HTTP11)); } /** * Constructs a {@code HttpMessage} with a HTTP/1.1 GET request to the given URI. * *

The following headers are automatically added: * *

    *
  • {@code Host}, with the domain and port from the given URI. *
  • {@code User-Agent}, using the {@link HttpRequestHeader#getDefaultUserAgent()}. *
  • {@code Pragma: no-cache} *
  • {@code Cache-Control: no-cache}, if version is HTTP/1.1 *
  • {@code Content-Type: application/x-www-form-urlencoded}, if the method is POST or PUT. *
* * @param uri the request target. * @param params unused. * @throws HttpMalformedHeaderException if the resulting HTTP header is malformed. * @deprecated (2.8.0) Use {@link #HttpMessage(URI)} instead. * @since 2.4.2 */ @Deprecated public HttpMessage(URI uri, ConnectionParam params) throws HttpMalformedHeaderException { this(uri); } /** * Constructs an HTTP message with the given request header. * * @param reqHeader the request header * @throws IllegalArgumentException if the parameter {@code reqHeader} is {@code null}. */ public HttpMessage(HttpRequestHeader reqHeader) { setRequestHeader(reqHeader); } /** * Constructs an HTTP message with the given request header and request body. * * @param reqHeader the request header * @param reqBody the request body * @throws IllegalArgumentException if the parameter {@code reqHeader} or {@code reqBody} are * {@code null}. */ public HttpMessage(HttpRequestHeader reqHeader, HttpRequestBody reqBody) { setRequestHeader(reqHeader); setRequestBody(reqBody); } /** * Constructor for a HTTP message with given request and response pair. * * @param reqHeader the request header * @param reqBody the request body * @param resHeader the response header * @param resBody the response body * @throws IllegalArgumentException if one of the parameters is {@code null}. */ public HttpMessage( HttpRequestHeader reqHeader, HttpRequestBody reqBody, HttpResponseHeader resHeader, HttpResponseBody resBody) { setRequestHeader(reqHeader); setRequestBody(reqBody); setResponseHeader(resHeader); setResponseBody(resBody); } public HttpMessage(String reqHeader, byte[] reqBody, String resHeader, byte[] resBody) throws HttpMalformedHeaderException { setRequestHeader(reqHeader); setRequestBody(reqBody); if (resHeader != null && !resHeader.equals("")) { setResponseHeader(resHeader); setResponseBody(resBody); } } /** * Constructs a {@code HttpMessage} from the given message. * *

All the {@code HttpMessage} state is copied, except for the following which are the same: * *

    *
  • {@link #getUserObject()} *
  • {@link #getHistoryRef()} *
  • {@link #getHttpSession()} *
  • {@link #getRequestingUser()} *
* * @param message the message to copy. * @since 2.8.0 */ public HttpMessage(HttpMessage message) { if (message == null) { throw new IllegalArgumentException("The parameter message must not be null."); } message.copyRequestInto(this); message.copyResponseInto(this); setUserObject(message.getUserObject()); setTimeSentMillis(message.getTimeSentMillis()); setTimeElapsedMillis(message.getTimeElapsedMillis()); setNote(message.getNote()); setHistoryRef(message.getHistoryRef()); setHttpSession(message.getHttpSession()); setRequestingUser(message.getRequestingUser()); setForceIntercept(message.isForceIntercept()); setResponseFromTargetHost(message.isResponseFromTargetHost()); } /** * Gets the request header of this message. * * @return the request header, never {@code null} */ public HttpRequestHeader getRequestHeader() { return mReqHeader; } /** * Sets the request header of this message. * * @param reqHeader the new request header * @throws IllegalArgumentException if parameter {@code reqHeader} is {@code null}. */ public void setRequestHeader(HttpRequestHeader reqHeader) { if (reqHeader == null) { throw new IllegalArgumentException("The parameter reqHeader must not be null."); } mReqHeader = reqHeader; } /** * Gets the response header of this message. * *

To know if a response has been set call the method {@code HttpResponseHeader#isEmpty()} on * the returned response header. The response header is initially empty. * * @return the response header, never {@code null} * @see HttpResponseHeader#isEmpty() */ public HttpResponseHeader getResponseHeader() { return mResHeader; } /** * Sets the response header of this message. * * @param resHeader the new response header * @throws IllegalArgumentException if parameter {@code resHeader} is {@code null}. */ public void setResponseHeader(HttpResponseHeader resHeader) { if (resHeader == null) { throw new IllegalArgumentException("The parameter resHeader must not be null."); } mResHeader = resHeader; } /** * Gets the request body of this message. * * @return the request body, never {@code null} */ public HttpRequestBody getRequestBody() { return mReqBody; } /** * Sets the request body of this message. * *

Note: No encodings are set to the request body to match the header. * * @param reqBody the new request body * @throws IllegalArgumentException if parameter {@code reqBody} is {@code null}. */ public void setRequestBody(HttpRequestBody reqBody) { if (reqBody == null) { throw new IllegalArgumentException("The parameter reqBody must not be null."); } mReqBody = reqBody; } /** * Gets the response body of this message. * * @return the response body, never {@code null} */ public HttpResponseBody getResponseBody() { return mResBody; } /** * Sets the response body of this message. * *

Note: No encodings are set to the response body to match the header. * * @param resBody the new response body * @throws IllegalArgumentException if parameter {@code resBody} is {@code null}. */ public void setResponseBody(HttpResponseBody resBody) { if (resBody == null) { throw new IllegalArgumentException("The parameter resBody must not be null."); } mResBody = resBody; getResponseBody().setCharset(getResponseHeader().getCharset()); } /** * Sets the given string as the request header. * *

Note: No encodings are set to the request body to match the header. * * @param reqHeader the new request header. * @throws HttpMalformedHeaderException if the given header is malformed. * @see #setContentEncodings(HttpHeader, HttpBody) */ public void setRequestHeader(String reqHeader) throws HttpMalformedHeaderException { HttpRequestHeader newHeader = new HttpRequestHeader(reqHeader); setRequestHeader(newHeader); } /** * Sets the given string as the response header. * *

Note: No encodings are set to the response body to match the header. * * @param resHeader the new response header. * @throws HttpMalformedHeaderException if the given header is malformed. * @see #setContentEncodings(HttpHeader, HttpBody) */ public void setResponseHeader(String resHeader) throws HttpMalformedHeaderException { HttpResponseHeader newHeader = new HttpResponseHeader(resHeader); setResponseHeader(newHeader); } /** * Sets the content encodings defined in the header into the body. * *

Note: By default supports only {@code gzip} and {@code deflate}. * * @param header the header. * @param body the body. */ public static void setContentEncodings(HttpHeader header, HttpBody body) { var localHandler = contentEncodingsHandler; if (localHandler == null) { localHandler = DEFAULT_CONTENT_ENCODINGS_HANDLER; } localHandler.handle(header, body); } /** * Sets the handler of content encodings. * *

Note: Not part of the public API. * * @param handler the handler. * @see #setContentEncodings(HttpHeader, HttpBody) */ public static void setContentEncodingsHandler(HttpEncodingsHandler handler) { contentEncodingsHandler = handler; } /** * Sets the given string body as the request body. * *

The defined request header content encodings are set to the body, the string body will be * encoded accordingly. * * @param body the new body. * @see HttpBody#setContentEncodings(List) */ public void setRequestBody(String body) { setContentEncodings(getRequestHeader(), getRequestBody()); getRequestBody().setCharset(getRequestHeader().getCharset()); getRequestBody().setBody(body); } /** * Sets the given byte body as the request body. * *

The defined request header content encodings are set to the body, the byte body is assumed * to be properly encoded. * * @param body the new body. * @see HttpBody#setContentEncodings(List) */ public void setRequestBody(byte[] body) { getRequestBody().setBody(body); getRequestBody().setCharset(getRequestHeader().getCharset()); setContentEncodings(getRequestHeader(), getRequestBody()); } /** * Sets the given string body as the response body. * *

The defined response header content encodings are set to the body, the string body will be * encoded accordingly. * * @param body the new body. * @see HttpBody#setContentEncodings(List) */ public void setResponseBody(String body) { setContentEncodings(getResponseHeader(), getResponseBody()); getResponseBody().setCharset(getResponseHeader().getCharset()); getResponseBody().setDetermineCharset(getResponseHeader().isText()); getResponseBody().setBody(body); } /** * Sets the given byte body as the response body. * *

The defined response header content encodings are set to the body, the byte body is * assumed to be properly encoded. * * @param body the new body. * @see HttpBody#setContentEncodings(List) */ public void setResponseBody(byte[] body) { getResponseBody().setBody(body); getResponseBody().setCharset(getResponseHeader().getCharset()); setContentEncodings(getResponseHeader(), getResponseBody()); } /** * Compare if 2 message is the same. 2 messages are the same if: Host, port, path and query * param and VALUEs are the same. For POST request, the body must be the same. * * @param object * @return */ @Override public boolean equals(Object object) { if (!(object instanceof HttpMessage)) { return false; } HttpMessage msg = (HttpMessage) object; boolean result = false; // compare method if (!this.getRequestHeader() .getMethod() .equalsIgnoreCase(msg.getRequestHeader().getMethod())) { return false; } // compare host, port and URI URI uri1 = this.getRequestHeader().getURI(); URI uri2 = msg.getRequestHeader().getURI(); if (uri1 == null) { if (uri2 != null) { return false; } return true; } else if (uri2 == null) { return false; } try { if (uri1.getHost() == null || uri2.getHost() == null || !uri1.getHost().equalsIgnoreCase(uri2.getHost())) { return false; } if (uri1.getPort() != uri2.getPort()) { return false; } String pathQuery1 = uri1.getPathQuery(); String pathQuery2 = uri2.getPathQuery(); if (pathQuery1 == null && pathQuery2 == null) { return true; } else if (pathQuery1 != null && pathQuery2 != null) { return pathQuery1.equalsIgnoreCase(pathQuery2); } else if (pathQuery1 == null || pathQuery2 == null) { return false; } if (this.getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.POST)) { return this.getRequestBody().equals(msg.getRequestBody()); } result = true; } catch (URIException e) { try { result = this.getRequestHeader() .getURI() .toString() .equalsIgnoreCase(msg.getRequestHeader().getURI().toString()); } catch (Exception e1) { // ZAP: log error LOGGER.error(e.getMessage(), e); } } return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getRequestHeader().getMethod().toLowerCase(Locale.ROOT).hashCode(); URI uri = getRequestHeader().getURI(); if (uri != null) { result = prime * result + uri.getPort(); try { result = prime * result + (uri.getRawHost() == null ? 0 : uri.getHost().toLowerCase(Locale.ROOT).hashCode()); } catch (URIException e) { LOGGER.error("Failed to obtain the host for hashCode calculation: {}", uri, e); } result = prime * result + ((uri.getRawPathQuery() == null) ? 0 : uri.getEscapedPathQuery() .toLowerCase(Locale.ROOT) .hashCode()); } if (getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.POST)) { result = prime * result + getRequestBody().hashCode(); } return result; } /** * Compares this {@code HttpMessage} against another. Messages are equal type if the host, port, * path and parameter names are equal. Even though the query values may differ. For POST this * assumes x-www-form-urlencoded, for other types (such as JSON) this means that parameter names * and values (the full request body) could be included. * * @param msg the message against which this {@code HttpMessage} is being compared. * @return {@code true} if the messages are considered equal, {@code false} otherwise */ public boolean equalType(HttpMessage msg) { boolean result = false; // compare method if (!this.getRequestHeader() .getMethod() .equalsIgnoreCase(msg.getRequestHeader().getMethod())) { return false; } // compare host, port and URI URI uri1 = this.getRequestHeader().getURI(); URI uri2 = msg.getRequestHeader().getURI(); try { if (uri1.getHost() == null || uri2.getHost() == null || !uri1.getHost().equalsIgnoreCase(uri2.getHost())) { return false; } if (uri1.getPort() != uri2.getPort()) { return false; } String path1 = uri1.getPath(); String path2 = uri2.getPath(); if (path1 == null && path2 == null) { return true; } if (path1 != null && path2 != null && !path1.equalsIgnoreCase(path2)) { return false; } else { if (path1 == null || path2 == null) { return false; } } if (!queryEquals(msg)) { return false; } result = true; } catch (URIException e) { // ZAP: log error LOGGER.error(e.getMessage(), e); } return result; } /** * Compares the parameter names used in GET and POST messages. For POST this assumes * x-www-form-urlencoded, for other types (such as JSON) this means that parameter names and * values (the full request body) could be included. * * @param msg the message against which this {@code HttpMessage} parameter names are being * compared. * @return {@code true} if the set of parameter names are considered equal, {@code false} * otherwise */ private boolean queryEquals(HttpMessage msg) { boolean result = false; SortedSet set1 = null; SortedSet set2 = null; set1 = getParamNameSet(HtmlParameter.Type.url); set2 = msg.getParamNameSet(HtmlParameter.Type.url); if (!set1.equals(set2)) { return false; } // compare here if this is a POST // the POST body part must also be the same set if (getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.POST)) { set1 = getParamNameSet(HtmlParameter.Type.form); set2 = msg.getParamNameSet(HtmlParameter.Type.form); if (!set1.equals(set2)) { return false; } } result = true; return result; } /** * Returns the names of the parameters of the given {@code type}. As a Set is used no names will * be duplicated. * * @param type the type of the parameters that will be extracted from the message * @return a {@code TreeSet} with the names of the parameters of the given {@code type}, never * {@code null} * @since 2.4.2 */ public TreeSet getParamNameSet(HtmlParameter.Type type) { TreeSet set = new TreeSet<>(); List paramList = Model.getSingleton().getSession().getParameters(this, type); for (NameValuePair nvp : paramList) { set.add(nvp.getName()); } return set; } /** * Returns the names of the parameters of the given {@code type} in a List. The List can return * duplicated names. * * @param type the type of the parameters that will be extracted from the message * @return a {@code List} with the names of the parameters of the given {@code type}, never * {@code null} * @since 2.10.0 */ public List getParameterNames(HtmlParameter.Type type) { List list = new ArrayList<>(); Model.getSingleton() .getSession() .getParameters(this, type) .forEach((nvp) -> list.add(nvp.getName())); return list; } private TreeSet getParamsSet(HtmlParameter.Type type) { TreeSet set = new TreeSet<>(); List paramList = Model.getSingleton().getSession().getParameters(this, type); for (NameValuePair nvp : paramList) { set.add(new HtmlParameter(type, nvp.getName(), nvp.getValue())); } return set; } /** * Returns the parameters of the given {@code type} in a List. The List can return duplicated * parameter names. * * @param type the type of the parameters that will be extracted from the message * @return a {@code List} with the parameters of the given {@code type}, never {@code null} * @since 2.10.0 */ public List getParameters(HtmlParameter.Type type) { List list = new ArrayList<>(); Model.getSingleton() .getSession() .getParameters(this, type) .forEach((nvp) -> list.add(new HtmlParameter(type, nvp.getName(), nvp.getValue()))); return list; } // ZAP: Added getParamNames public String[] getParamNames() { Vector v = new Vector<>(); // Get the params names from the query SortedSet pns = this.getParamNameSet(HtmlParameter.Type.url); Iterator iterator = pns.iterator(); while (iterator.hasNext()) { String name = iterator.next(); if (name != null) { v.add(name); } } if (getRequestHeader().getMethod().equalsIgnoreCase(HttpRequestHeader.POST)) { // Get the param names from the POST pns = this.getParamNameSet(HtmlParameter.Type.form); iterator = pns.iterator(); while (iterator.hasNext()) { String name = iterator.next(); if (name != null) { v.add(name); } } } String[] a = new String[v.size()]; v.toArray(a); return a; } // ZAP: Added getUrlParams public TreeSet getUrlParams() { return this.getParamsSet(HtmlParameter.Type.url); } // ZAP: Added getFormParams public TreeSet getFormParams() { final String contentType = mReqHeader.getHeader(HttpRequestHeader.CONTENT_TYPE); if (contentType == null || !StringUtils.startsWithIgnoreCase( contentType.trim(), HttpHeader.FORM_URLENCODED_CONTENT_TYPE)) { return new TreeSet<>(); } return this.getParamsSet(HtmlParameter.Type.form); } public void setCookieParamsAsString(String data) { this.getRequestHeader().setHeader(HttpHeader.COOKIE, data); } public String getCookieParamsAsString() { List cookies = new LinkedList<>(); if (!this.getRequestHeader().isEmpty()) { cookies.addAll(this.getRequestHeader().getHeaderValues(HttpHeader.COOKIE)); } if (!this.getResponseHeader().isEmpty()) { cookies.addAll(this.getResponseHeader().getHeaderValues(HttpHeader.SET_COOKIE)); cookies.addAll(this.getResponseHeader().getHeaderValues(HttpHeader.SET_COOKIE2)); } // Fix error requesting cookies, but there are none if (cookies.isEmpty()) { return ""; } StringBuilder sb = new StringBuilder(); for (String header : cookies) { sb.append(header); } return sb.toString(); } // ZAP: Added getCookieParams public TreeSet getCookieParams() { TreeSet set = new TreeSet<>(); if (!this.getRequestHeader().isEmpty()) { set.addAll(this.getRequestHeader().getCookieParams()); } if (!this.getResponseHeader().isEmpty()) { set.addAll(this.getResponseHeader().getCookieParams()); } return set; } /** * @return Returns the userObject. */ public Object getUserObject() { return userObject; } /** * @param userObject The userObject to set. */ public void setUserObject(Object userObject) { this.userObject = userObject; } /** * Clones this message. * *

It returns a new {@code HttpMessage} with a copy of the request/response headers and * bodies, no other state is copied. * * @return a new {@code HttpMessage} with the same (contents) request/response headers and * bodies as this one. * @see #HttpMessage(HttpMessage) */ public HttpMessage cloneAll() { HttpMessage newMsg = cloneRequest(); copyResponseInto(newMsg); return newMsg; } private void copyResponseInto(HttpMessage newMsg) { if (!this.getResponseHeader().isEmpty()) { try { newMsg.getResponseHeader().setMessage(this.getResponseHeader().toString()); } catch (HttpMalformedHeaderException e) { } newMsg.setResponseBody(this.getResponseBody().getBytes()); } } /** * Clones the request of this message. * *

It returns a new {@code HttpMessage} with a copy of the request header and body, no other * state is copied. * * @return a new {@code HttpMessage} with the same (contents) request header and body as this * one. * @see #HttpMessage(HttpMessage) */ public HttpMessage cloneRequest() { HttpMessage newMsg = new HttpMessage(); copyRequestInto(newMsg); return newMsg; } private void copyRequestInto(HttpMessage newMsg) { if (!this.getRequestHeader().isEmpty()) { try { newMsg.getRequestHeader().setMessage(this.getRequestHeader().toString()); } catch (HttpMalformedHeaderException e) { LOGGER.error(e.getMessage(), e); } newMsg.setRequestBody(this.getRequestBody().getBytes()); } } /** * @return Get the elapsed time (time difference) between the request is sent and all response * is received. In millis. The value is zero if the response is not received. */ public int getTimeElapsedMillis() { return timeElapsed; } /** * Set the elapsed time (time difference) between the request is sent and all response is * received. In millis. * * @param timeElapsed */ public void setTimeElapsedMillis(int timeElapsed) { this.timeElapsed = timeElapsed; } /** * Get the starting time when the request is going to be sent. This is the * System.currentTimeMillis before the message is sent. The value is zero if the request is not * sent yet. */ public long getTimeSentMillis() { return timeSent; } /** * Set the time when the request is sent. * * @param timeSent The timeSent to set. */ public void setTimeSentMillis(long timeSent) { this.timeSent = timeSent; } /** * @return Returns the note. */ public String getNote() { return note; } /** * @param note The note to set. */ public void setNote(String note) { this.note = note; } public void mutateHttpMethod(String method) { try { URI uri = getRequestHeader().getURI(); String body = getRequestBody().toString(); String prevMethod = getRequestHeader().getMethod(); if (prevMethod.equalsIgnoreCase(method)) { return; } if (prevMethod.equals(HttpRequestHeader.POST)) { // Was POST, move all params onto the URL if (body != null && body.length() > 0) { StringBuilder sb = new StringBuilder(); if (uri.getQuery() != null) { sb.append(uri.getQuery()); } String[] params = body.split("&"); for (String param : params) { if (sb.length() > 0) { sb.append('&'); } String[] nv = param.split("="); if (nv.length == 1) { // This effectively strips out the equals if theres // no value sb.append(nv[0]); } else { sb.append(param); } } uri.setQuery(sb.toString()); } // Clear the body body = ""; // Remove Content-Type if present getRequestHeader().setHeader(HttpRequestHeader.CONTENT_TYPE, null); } else if (method.equals(HttpRequestHeader.POST)) { // To be a port, move all URL query params into the body String query = uri.getQuery(); if (query != null) { StringBuilder sb = new StringBuilder(); String[] params = query.split("&"); for (String param : params) { if (sb.length() > 0) { sb.append('&'); } sb.append(param); String[] nv = param.split("="); if (nv.length == 1) { // Cope with URL params with no values e.g. // http://www.example.com/test?key sb.append('='); } } getRequestHeader() .setHeader( HttpRequestHeader.CONTENT_TYPE, HttpRequestHeader.FORM_URLENCODED_CONTENT_TYPE); body = sb.toString(); uri.setQuery(null); } } if (prevMethod.equalsIgnoreCase(HttpRequestHeader.CONNECT)) { String scheme; if (getRequestHeader().getHostPort() == 443) { scheme = "https://"; } else { scheme = "http://"; } uri = new URI(scheme + uri, true); } else if (method.equals(HttpRequestHeader.CONNECT)) { uri = URI.fromAuthority(uri.getAuthority()); } getRequestHeader() .setMessage( method + " " + uri + " " + getRequestHeader().getVersion() + "\r\n" + getRequestHeader().getHeadersAsString()); getRequestBody().setBody(body); } catch (HttpMalformedHeaderException e) { // Ignore? LOGGER.error(e.getMessage(), e); } catch (URIException e) { LOGGER.error(e.getMessage(), e); } } // Construct new POST Body from parameter in the postParams argument // in the Request Body public void setFormParams(TreeSet postParams) { // TODO: Maybe update content length, etc.? mReqBody.setFormParams(postParams); } // Construct new URL from get Request, based on the getParams argument // in the Request Header public void setGetParams(TreeSet getParams) { mReqHeader.setGetParams(getParams); } // Rewrite cookie line in the Request Header, // based on values in cookieParams public void setCookieParams(TreeSet cookieParams) { mReqHeader.setCookieParams(cookieParams); } /** * ZAP: New method checking for connection upgrade. * * @return True if this connection should be upgraded to WebSockets. */ public boolean isWebSocketUpgrade() { HttpHeader messageHeader = getResponseHeader(); if (messageHeader.isEmpty()) { messageHeader = getRequestHeader(); } if (!messageHeader.isEmpty()) { String connectionHeader = messageHeader.getHeader("connection"); String upgradeHeader = messageHeader.getHeader("upgrade"); if (upgradeHeader != null && upgradeHeader.equalsIgnoreCase("websocket")) { if (connectionHeader != null) { for (String directive : connectionHeader.split(",")) { if (directive.trim().equalsIgnoreCase("upgrade")) { return true; } } } } } return false; } // Rewrite cookie line in the Request Header, // based on values in cookies public void setCookies(List cookies) { mReqHeader.setCookies(cookies); } @Override public boolean isInScope() { return Model.getSingleton() .getSession() .isInScope(this.getRequestHeader().getURI().toString()); } /** * ZAP: New method checking if connection is a Server-Sent Events stream. * * @return */ public boolean isEventStream() { boolean isEventStream = false; if (!getResponseHeader().isEmpty()) { isEventStream = getResponseHeader().hasContentType("text/event-stream"); } else { // response not available // is request for event-stream? String acceptHeader = getRequestHeader().getHeader("Accept"); if (acceptHeader != null && acceptHeader.equals("text/event-stream")) { // request is for an SSE stream isEventStream = true; } } return isEventStream; } @Override public boolean isForceIntercept() { String vals = this.getRequestHeader().getHeader(HttpHeader.X_SECURITY_PROXY); if (vals != null) { for (String val : vals.split(",")) { if (HttpHeader.SEC_PROXY_INTERCEPT.equalsIgnoreCase(val.trim())) { // The browser told us to do it Your Honour return true; } } } return forceIntercept; } public void setForceIntercept(boolean force) { this.forceIntercept = force; } /** * Gets the request user. * * @return the request user */ public User getRequestingUser() { return requestUser; } /** * Sets the requesting user. When sending the message, if a requesting user has been set, the * message will be modified so that it will be sent as from the point of view of this particular * user. * * @param requestUser the new request user */ public void setRequestingUser(User requestUser) { this.requestUser = requestUser; } /** * Tells whether or not the response has been received from the target host. * *

Note: No distinction is done between responses from intermediate proxy * servers (if any) and the target host. * * @return {@code true} if the response has been received from the target host, {@code false} * otherwise. */ public boolean isResponseFromTargetHost() { return this.responseFromTargetHost; } /** * Sets if the response has been received or not from the target host. * * @param responseFromTargetHost {@code true} if the response has been received from the target * host, {@code false} otherwise. */ public void setResponseFromTargetHost(final boolean responseFromTargetHost) { this.responseFromTargetHost = responseFromTargetHost; } /** * Returns a map of data suitable for including in an {@link Event} * * @since 2.8.0 */ @Override public Map toEventData() { Map map = new HashMap<>(); map.put(EVENT_DATA_URI, getRequestHeader().getURI().toString()); map.put(EVENT_DATA_REQUEST_HEADER, getRequestHeader().toString()); map.put(EVENT_DATA_REQUEST_BODY, getRequestBody().toString()); if (!getResponseHeader().isEmpty()) { map.put(EVENT_DATA_RESPONSE_HEADER, getResponseHeader().toString()); map.put(EVENT_DATA_RESPONSE_BODY, getResponseBody().toString()); } return map; } /** * Returns "HTTP" * * @since 2.8.0 */ @Override public String getType() { return MESSAGE_TYPE; } /** Note: Not part of the public API. */ public interface HttpEncodingsHandler { void handle(HttpHeader header, HttpBody body); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy