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

org.parosproxy.paros.network.HttpRequestHeader 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.

There is a newer version: 2.15.0
Show newest version
/*
 * Created on Jun 14, 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:02/01 Changed getHostPort() to return proper port number even if it
// is not explicitly specified in URI
// ZAP: 2011/08/04 Changed to support Logging
// ZAP: 2011/10/29 Log errors
// ZAP: 2011/11/03 Changed isImage() to prevent a NullPointerException when the path doesn't exist
// ZAP: 2011/12/09 Changed HttpRequestHeader(String method, URI uri, String version) to add
//      the Cache-Control header field when the HTTP version is 1.1 and changed a if condition to
//      validate the variable version instead of the variable method.
// ZAP: 2012/03/15 Changed to use the class StringBuilder instead of StringBuffer. Reworked some
// methods.
// ZAP: 2012/04/23 Added @Override annotation to all appropriate methods.
// ZAP: 2012/06/24 Added method to add Cookies of type java.net.HttpCookie to request header
// ZAP: 2012/06/24 Added new method of getting cookies from the request header.
// ZAP: 2012/10/08 Issue 361: getHostPort on HttpRequestHeader for HTTPS CONNECT
// requests returns the wrong port
// ZAP: 2013/01/23 Clean up of exception handling/logging.
// ZAP: 2013/03/08 Improved parse error reporting
// ZAP: 2013/04/14 Issue 596: Rename the method HttpRequestHeader.getSecure to isSecure
// ZAP: 2013/05/02 Re-arranged all modifiers into Java coding standard order
// ZAP: 2013/12/09 Set Content-type only in case of POST or PUT HTTP methods
// ZAP: 2015/08/07 Issue 1768: Update to use a more recent default user agent
// ZAP: 2016/06/17 Remove redundant initialisations of instance variables
// ZAP: 2016/09/26 JavaDoc tweaks
// ZAP: 2017/02/23 Issue 3227: Limit API access to whitelisted IP addresses
// ZAP: 2017/04/24 Added more HTTP methods
// ZAP: 2017/10/19 Skip parsing of empty Cookie headers.
// ZAP: 2017/11/22 Address a NPE in isImage().
// ZAP: 2018/01/10 Tweak how cookie header is reconstructed from HtmlParameter(s).
// ZAP: 2018/02/06 Make the upper case changes locale independent (Issue 4327).
// ZAP: 2018/08/10 Allow to set the user agent used by default request headers (Issue 4846).
// ZAP: 2018/11/16 Add Accept header.
// ZAP: 2019/01/25 Add Origin header.
// ZAP: 2019/03/06 Log or include the malformed data in the exception message.
// ZAP: 2019/03/19 Changed the parse method to only parse the authority on CONNECT requests
// 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.
package org.parosproxy.paros.network;

import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.log4j.Logger;

public class HttpRequestHeader extends HttpHeader {

    /**
     * The {@code Accept} request header.
     *
     * @since 2.8.0
     */
    public static final String ACCEPT = "Accept";

    /**
     * The {@code Origin} request header.
     *
     * @since 2.8.0
     */
    public static final String ORIGIN = "Origin";

    private static final long serialVersionUID = 4156598327921777493L;
    private static final Logger log = Logger.getLogger(HttpRequestHeader.class);

    // method list
    public static final String CONNECT = "CONNECT";
    public static final String DELETE = "DELETE";
    public static final String GET = "GET";
    public static final String HEAD = "HEAD";
    public static final String OPTIONS = "OPTIONS";
    public static final String PATCH = "PATCH";
    public static final String POST = "POST";
    public static final String PUT = "PUT";
    public static final String TRACE = "TRACE";
    public static final String TRACK = "TRACK";

    // ZAP: Added method array
    public static final String[] METHODS = {
        CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE, TRACK
    };
    public static final String HOST = "Host";
    private static final Pattern patternRequestLine =
            Pattern.compile(p_METHOD + p_SP + p_URI + p_SP + p_VERSION, Pattern.CASE_INSENSITIVE);
    // private static final Pattern patternHostHeader
    //	= Pattern.compile("([^:]+)\\s*?:?\\s*?(\\d*?)");
    private static final Pattern patternImage =
            Pattern.compile("\\.(bmp|ico|jpg|jpeg|gif|tiff|tif|png)\\z", Pattern.CASE_INSENSITIVE);
    private static final Pattern patternPartialRequestLine =
            Pattern.compile(
                    "\\A *(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT)\\b",
                    Pattern.CASE_INSENSITIVE);

    /**
     * The user agent used by {@link #HttpRequestHeader(String, URI, String) default request
     * header}.
     */
    private static String defaultUserAgent;

    private String mMethod;
    private URI mUri;
    private String mHostName;
    private InetAddress senderAddress;

    /**
     * The host port number of this request message, a non-negative integer.
     *
     * 

Default is {@code 80}. * *

Note: All the modifications to the instance variable {@code mHostPort} * must be done through the method {@code setHostPort(int)}, so a valid and correct value is set * when no port number is defined (which is represented with the negative integer -1). * * @see #getHostPort() * @see #setHostPort(int) * @see URI#getPort() */ private int mHostPort; private boolean mIsSecure; /** Constructor for an empty header. */ public HttpRequestHeader() { super(); mMethod = ""; mHostName = ""; mHostPort = 80; } /** * Constructor of a request header with the string. * * @param data the request header * @param isSecure {@code true} if the request should be secure, {@code false} otherwise * @throws HttpMalformedHeaderException if the request being set is malformed * @see #setSecure(boolean) */ public HttpRequestHeader(String data, boolean isSecure) throws HttpMalformedHeaderException { setMessage(data, isSecure); } /** * Constructor of a request header with the string. Whether this is a secure header depends on * the URL given. * * @param data the request header * @throws HttpMalformedHeaderException if the request being set is malformed */ public HttpRequestHeader(String data) throws HttpMalformedHeaderException { setMessage(data); } @Override public void clear() { super.clear(); mMethod = ""; mUri = null; mHostName = ""; setHostPort(-1); } /** * Constructs a {@code HttpRequestHeader} with the given method, URI, and version. * *

The following headers are automatically added: * *

    *
  • {@code Host}, with the domain and port from the given URI. *
  • {@code User-Agent}, using the {@link #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 method the request method. * @param uri the request target. * @param version the version, for example, {@code HTTP/1.1}. * @throws HttpMalformedHeaderException if the resulting HTTP header is malformed. */ public HttpRequestHeader(String method, URI uri, String version) throws HttpMalformedHeaderException { this(method + " " + uri.toString() + " " + version.toUpperCase(Locale.ROOT) + CRLF + CRLF); try { setHeader( HOST, uri.getHost() + (uri.getPort() > 0 ? ":" + Integer.toString(uri.getPort()) : "")); } catch (URIException e) { log.error(e.getMessage(), e); } setHeader(USER_AGENT, defaultUserAgent); setHeader(PRAGMA, "no-cache"); // ZAP: added the Cache-Control header field to comply with HTTP/1.1 if (version.equalsIgnoreCase(HTTP11)) { setHeader(CACHE_CONTROL, "no-cache"); } // ZAP: set content type x-www-urlencoded only if it's a POST or a PUT if (method.equalsIgnoreCase(POST) || method.equalsIgnoreCase(PUT)) { setHeader(CONTENT_TYPE, "application/x-www-form-urlencoded"); } setHeader(ACCEPT_ENCODING, null); // ZAP: changed from method to version if (version.equalsIgnoreCase(HTTP11)) { setContentLength(0); } } /** * Constructs a {@code HttpRequestHeader} with the given method, URI, and version. * *

The following headers are automatically added: * *

    *
  • {@code Host}, with the domain and port from the given URI. *
  • {@code User-Agent}, using the {@link #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 method the request method. * @param uri the request target. * @param version the version, for example, {@code HTTP/1.1}. * @param params unused. * @throws HttpMalformedHeaderException if the resulting HTTP header is malformed. * @deprecated (2.8.0) Use {@link #HttpRequestHeader(String, URI, String)} instead. * @since 2.4.2 */ @Deprecated public HttpRequestHeader(String method, URI uri, String version, ConnectionParam params) throws HttpMalformedHeaderException { this(method, uri, version); } /** * Set this request header with the given message. * * @param data the request header * @param isSecure {@code true} if the request should be secure, {@code false} otherwise * @throws HttpMalformedHeaderException if the request being set is malformed * @see #setSecure(boolean) */ public void setMessage(String data, boolean isSecure) throws HttpMalformedHeaderException { super.setMessage(data); try { parse(isSecure); } catch (HttpMalformedHeaderException e) { mMalformedHeader = true; if (log.isDebugEnabled()) { log.debug("Malformed header: " + data, e); } throw e; } catch (Exception e) { log.error("Failed to parse:\n" + data, e); mMalformedHeader = true; throw new HttpMalformedHeaderException(e.getMessage()); } } /** * Set this request header with the given message. Whether this is a secure header depends on * the URL given. */ @Override public void setMessage(String data) throws HttpMalformedHeaderException { this.setMessage(data, false); } /** * Get the HTTP method (GET, POST, ..., etc.). * * @return the request method */ public String getMethod() { return mMethod; } /** * Set the HTTP method of this request header. * * @param method the new method, must not be {@code null}. */ public void setMethod(String method) { mMethod = method.toUpperCase(Locale.ROOT); } /** * Get the URI of this request header. * * @return the request URI */ public URI getURI() { return mUri; } /** * Sets the URI of this request header. * * @param uri the new request URI * @throws URIException if an error occurred while setting the request URI */ public void setURI(URI uri) throws URIException { if (uri.getScheme() == null || uri.getScheme().equals("")) { mUri = new URI(HTTP + "://" + getHeader(HOST) + "/" + mUri.toString(), true); } else { mUri = uri; } if (uri.getScheme().equalsIgnoreCase(HTTPS)) { mIsSecure = true; } else { mIsSecure = false; } setHostPort(mUri.getPort()); } /** * Get if this request header is under secure connection. * * @return {@code true} if the request is secure, {@code false} otherwise * @deprecated Replaced by {@link #isSecure()}. It will be removed in a future release. */ @Deprecated public boolean getSecure() { return mIsSecure; } /** * Tells whether the request is secure, or not. A request is considered secure if it's using the * HTTPS protocol. * * @return {@code true} if the request is secure, {@code false} otherwise. */ public boolean isSecure() { return mIsSecure; } /** * Sets whether or not the request is done using a secure scheme, HTTPS. * * @param isSecure {@code true} if the request should be secure, {@code false} otherwise * @throws URIException if an error occurred while rebuilding the request URI */ public void setSecure(boolean isSecure) throws URIException { mIsSecure = isSecure; if (mUri == null) { // mUri not yet set return; } URI newUri = mUri; // check if URI consistent if (isSecure() && mUri.getScheme().equalsIgnoreCase(HTTP)) { newUri = new URI(mUri.toString().replaceFirst(HTTP, HTTPS), true); } else if (!isSecure() && mUri.getScheme().equalsIgnoreCase(HTTPS)) { newUri = new URI(mUri.toString().replaceFirst(HTTPS, HTTP), true); } if (newUri != mUri) { mUri = newUri; setHostPort(mUri.getPort()); } } /** Set the HTTP version of this request header. */ @Override public void setVersion(String version) { mVersion = version.toUpperCase(Locale.ROOT); } /** * Get the content length in this request header. If the content length is undetermined, 0 will * be returned. */ @Override public int getContentLength() { if (mContentLength == -1) { return 0; } return mContentLength; } /** * Parse this request header. * * @param isSecure {@code true} if the request is secure, {@code false} otherwise * @throws URIException if failed to parse the URI * @throws HttpMalformedHeaderException if the request being parsed is malformed */ private void parse(boolean isSecure) throws URIException, HttpMalformedHeaderException { mIsSecure = isSecure; Matcher matcher = patternRequestLine.matcher(mStartLine); if (!matcher.find()) { mMalformedHeader = true; throw new HttpMalformedHeaderException( "Failed to find pattern " + patternRequestLine + " in: " + mStartLine); } mMethod = matcher.group(1); String sUri = matcher.group(2); mVersion = matcher.group(3); if (!mVersion.equalsIgnoreCase(HTTP09) && !mVersion.equalsIgnoreCase(HTTP10) && !mVersion.equalsIgnoreCase(HTTP11)) { mMalformedHeader = true; throw new HttpMalformedHeaderException("Unexpected version: " + mVersion); } if (mMethod.equalsIgnoreCase(CONNECT)) { parseHostName(sUri); mUri = parseURI(mHostName); } else { mUri = parseURI(sUri); if (mUri.getScheme() == null || mUri.getScheme().equals("")) { mUri = new URI(HTTP + "://" + getHeader(HOST) + mUri.toString(), true); } if (isSecure() && mUri.getScheme().equalsIgnoreCase(HTTP)) { mUri = new URI(mUri.toString().replaceFirst(HTTP, HTTPS), true); } if (mUri.getScheme().equalsIgnoreCase(HTTPS)) { setSecure(true); } mHostName = mUri.getHost(); setHostPort(mUri.getPort()); } } private void parseHostName(String hostHeader) { // no host header given but a valid host name already exist. if (hostHeader == null) { return; } int port = -1; int pos; if ((pos = hostHeader.indexOf(':', 2)) > -1) { mHostName = hostHeader.substring(0, pos).trim(); try { port = Integer.parseInt(hostHeader.substring(pos + 1)); } catch (NumberFormatException e) { } } else { mHostName = hostHeader.trim(); } setHostPort(port); } /** * Get the host name in this request header. * * @return Host name. */ public String getHostName() { String hostName = mHostName; try { // ZAP: fixed cases, where host name is null hostName = ((mUri.getHost() != null) ? mUri.getHost() : mHostName); } catch (URIException e) { if (log.isDebugEnabled()) { log.warn(e); } } return hostName; } /** * Gets the host port number of this request message, a non-negative integer. * *

If no port is defined the default port for the used scheme will be returned, either 80 for * HTTP or 443 for HTTPS. * * @return the host port number, a non-negative integer */ public int getHostPort() { return mHostPort; } /** * Sets the host port number of this request message. * *

If the given {@code port} number is negative (usually -1 to represent that no port number * is defined), the port number set will be the default port number for the used scheme known * using the method {@code isSecure()}, either 80 for HTTP or 443 for HTTPS. * * @param port the new port number * @see #mHostPort * @see #isSecure() * @see URI#getPort() */ private void setHostPort(int port) { if (port > -1) { mHostPort = port; } else if (this.isSecure()) { mHostPort = 443; } else { mHostPort = 80; } } /** Return if this request header is a image request basing on the path suffix. */ @Override public boolean isImage() { if (getURI() == null) { return false; } try { // ZAP: prevents a NullPointerException when no path exists final String path = getURI().getPath(); if (path != null) { return (patternImage.matcher(path).find()); } } catch (URIException e) { log.error(e.getMessage(), e); } return false; } /** * Return if the data given is a request header basing on the first start line. * * @param data the data to be checked * @return {@code true} if the data contains a request line, {@code false} otherwise. */ public static boolean isRequestLine(String data) { return patternPartialRequestLine.matcher(data).find(); } /** Return the prime header (first line). */ @Override public String getPrimeHeader() { return getMethod() + " " + getURI().toString() + " " + getVersion(); } /* * private static final char[] DELIM_UNWISE_CHAR = { '<', '>', '#', '"', ' * ', '{', '}', '|', '\\', '^', '[', ']', '`' }; */ private static final String DELIM = "<>#\""; private static final String UNWISE = "{}|\\^[]`"; private static final String DELIM_UNWISE = DELIM + UNWISE; public static URI parseURI(String sUri) throws URIException { URI uri; int len = sUri.length(); StringBuilder sb = new StringBuilder(len); char[] charray = new char[1]; String s; for (int i = 0; i < len; i++) { char ch = sUri.charAt(i); // String ch = sUri.substring(i, i+1); if (DELIM_UNWISE.indexOf(ch) >= 0) { // check if unwise or delim in RFC. If so, encode it. charray[0] = ch; s = new String(charray); try { s = URLEncoder.encode(s, "UTF8"); } catch (UnsupportedEncodingException e1) { } sb.append(s); } else if (ch == '%') { // % is exception - no encoding to be done because some server may not handle // correctly when % is invalid. // // sb.append(ch); // if % followed by hex, no encode. try { String hex = sUri.substring(i + 1, i + 3); Integer.parseInt(hex, 16); sb.append(ch); } catch (Exception e) { charray[0] = ch; s = new String(charray); try { s = URLEncoder.encode(s, "UTF8"); } catch (UnsupportedEncodingException e1) { } sb.append(s); } } else if (ch == ' ') { // if URLencode, '+' will be appended. sb.append("%20"); } else { sb.append(ch); } } uri = new URI(sb.toString(), true); return uri; } // Construct new GET url of request // Based on getParams public void setGetParams(TreeSet getParams) { if (mUri == null) { return; } if (getParams.isEmpty()) { try { mUri.setQuery(""); } catch (URIException e) { log.error(e.getMessage(), e); } return; } StringBuilder sbQuery = new StringBuilder(); for (HtmlParameter parameter : getParams) { if (parameter.getType() != HtmlParameter.Type.url) { continue; } sbQuery.append(parameter.getName()); sbQuery.append('='); sbQuery.append(parameter.getValue()); sbQuery.append('&'); } if (sbQuery.length() <= 2) { try { mUri.setQuery(""); } catch (URIException e) { log.error(e.getMessage(), e); } return; } String query = sbQuery.substring(0, sbQuery.length() - 1); try { // The previous behaviour was escaping the query, // so it is maintained with the use of setQuery. mUri.setQuery(query); } catch (URIException e) { log.error(e.getMessage(), e); } } /** * Construct new "Cookie:" line in request header based on HttpCookies. * * @param cookies the new cookies */ public void setCookies(List cookies) { if (cookies.isEmpty()) { setHeader(HttpHeader.COOKIE, null); } StringBuilder sbData = new StringBuilder(); for (HttpCookie c : cookies) { sbData.append(c.getName()); sbData.append('='); sbData.append(c.getValue()); sbData.append("; "); } if (sbData.length() <= 3) { setHeader(HttpHeader.COOKIE, null); return; } final String data = sbData.substring(0, sbData.length() - 2); setHeader(HttpHeader.COOKIE, data); } // Construct new "Cookie:" line in request header, // based on cookieParams public void setCookieParams(TreeSet cookieParams) { if (cookieParams.isEmpty()) { setHeader(HttpHeader.COOKIE, null); } StringBuilder sbData = new StringBuilder(); for (HtmlParameter parameter : cookieParams) { if (parameter.getType() != HtmlParameter.Type.cookie) { continue; } String cookieName = parameter.getName(); if (!cookieName.isEmpty()) { sbData.append(cookieName); sbData.append('='); } sbData.append(parameter.getValue()); sbData.append("; "); } if (sbData.length() <= 2) { setHeader(HttpHeader.COOKIE, null); return; } final String data = sbData.substring(0, sbData.length() - 2); setHeader(HttpHeader.COOKIE, data); } public TreeSet getCookieParams() { TreeSet set = new TreeSet<>(); for (String cookieLine : getHeaderValues(HttpHeader.COOKIE)) { // watch out for the scenario where the first cookie name starts with "cookie" // (uppercase or lowercase) if (cookieLine.toUpperCase().startsWith(HttpHeader.COOKIE.toUpperCase() + ":")) { // HttpCookie wont parse lines starting with "Cookie:" cookieLine = cookieLine.substring(HttpHeader.COOKIE.length() + 1); } if (cookieLine.isEmpty()) { // Nothing to parse. continue; } // These can be comma separated type=value String[] cookieArray = cookieLine.split(";"); for (String cookie : cookieArray) { set.add(new HtmlParameter(cookie)); } } return set; } // ZAP: Added method for working directly with HttpCookie /** * Gets a list of the http cookies from this request Header. * * @return the http cookies * @throws IllegalArgumentException if a problem is encountered while processing the "Cookie: " * header line. */ public List getHttpCookies() { List cookies = new LinkedList<>(); // Use getCookieParams to reduce the places we parse cookies TreeSet ts = getCookieParams(); Iterator it = ts.iterator(); while (it.hasNext()) { HtmlParameter htmlParameter = it.next(); if (!htmlParameter.getName().isEmpty()) { try { cookies.add(new HttpCookie(htmlParameter.getName(), htmlParameter.getValue())); } catch (IllegalArgumentException e) { // Occurs while scanning ;) log.debug(e.getMessage() + " " + htmlParameter.getName()); } } } return cookies; } /** * Sets the senders IP address. Note that this is not persisted. * * @param inetAddress the senders IP address * @since 2.6.0 */ public void setSenderAddress(InetAddress inetAddress) { this.senderAddress = inetAddress; } /** * Gets the senders IP address * * @return the senders IP address * @since 2.6.0 */ public InetAddress getSenderAddress() { return senderAddress; } /** * Sets the user agent used by {@link #HttpRequestHeader(String, URI, String) default request * header}. * *

This is expected to be called only by core code, when the corresponding option is changed. * * @param defaultUserAgent the default user agent. * @since 2.8.0 */ public static void setDefaultUserAgent(String defaultUserAgent) { HttpRequestHeader.defaultUserAgent = defaultUserAgent; } /** * Gets the user agent used by {@link #HttpRequestHeader(String, URI, String) default request * header}. * * @return the default user agent. * @since 2.8.0 */ public static String getDefaultUserAgent() { return defaultUserAgent; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy