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

org.parosproxy.paros.network.HttpHeader 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/01/12 Changed the method parse to use only CRLF as line separator.
// ZAP: 2012/03/15 Removed an unnecessary try catch block and unnecessary casting.
// Reworked the method getCharset.
// ZAP: 2012/04/23 Added @Override annotation to the appropriate method.
// ZAP: 2012/10/04 Changed to initialise the instance variable mVersion with a
// valid version (HttpHeader.HTTP10).
// ZAP: 2012/11/01 Issue 410: charset wrapped in quotation marks
// ZAP: 2013/04/08 Issue 605: Force intercepts via header
// ZAP: 2013/05/02 Re-arranged all modifiers into Java coding standard order
// ZAP: 2013/09/02 Resolved header value setting on setHeader() which manage wrongly the "-" char
// ZAP: 2013/11/16 Issue 867: HttpMessage#getFormParams should return an empty TreeSet if
// the request body is not "x-www-form-urlencoded"
// ZAP: 2015/03/26 Issue 1573: Add option to inject plugin ID in header for all ascan requests
// ZAP: 2016/06/17 Be lenient when parsing charset and accept single quote chars around the value
// ZAP: 2016/06/17 Remove redundant initialisations of instance variables
// ZAP: 2017/02/08 Change isEmpty to check start line instead of headers (if it has the
// status/request line it's not empty).
// ZAP: 2017/03/02 Issue 3226: Added API Key and Nonce headers
// ZAP: 2018/02/06 Make the lower/upper case changes locale independent (Issue 4327).
// ZAP: 2018/04/24 Add JSON Content-Type.
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
// ZAP: 2019/12/09 Added getHeaderValues(String) method (returning List) and deprecated
// getHeaders(String) method (returning Vector).
// ZAP: 2022/03/11 Added headers: Content-Location, Link, Refresh
// ZAP: 2022/09/12 Allow only major HTTP version.
// ZAP: 2022/11/17 Add HTTP/2 constant.
// ZAP: 2022/11/22 Lower case the HTTP field names for compatibility with HTTP/2.
package org.parosproxy.paros.network;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public abstract class HttpHeader implements java.io.Serializable {

    private static final long serialVersionUID = 7922279497679304778L;
    public static final String CRLF = "\r\n";
    public static final String LF = "\n";
    public static final String CONTENT_LENGTH = "content-length";
    public static final String TRANSFER_ENCODING = "transfer-encoding";
    public static final String CONTENT_ENCODING = "content-encoding";
    public static final String CONTENT_LOCATION = "content-location";
    public static final String CONTENT_TYPE = "content-type";
    public static final String PROXY_CONNECTION = "proxy-connection";
    public static final String PROXY_AUTHENTICATE = "proxy-authenticate";
    public static final String CONNECTION = "connection";
    public static final String AUTHORIZATION = "authorization";
    public static final String WWW_AUTHENTICATE = "www-authenticate";
    public static final String LOCATION = "location";
    public static final String IF_MODIFIED_SINCE = "if-modified-since";
    public static final String IF_NONE_MATCH = "if-none-match";
    public static final String USER_AGENT = "user-agent";
    public static final String ACCEPT_ENCODING = "accept-encoding";
    public static final String CACHE_CONTROL = "cache-control";
    public static final String PRAGMA = "pragma";
    public static final String REFERER = "referer";
    public static final String X_ZAP_REQUESTID = "x-zap-requestid";
    public static final String X_SECURITY_PROXY = "x-security-proxy";
    public static final String COOKIE = "cookie";
    public static final String SET_COOKIE = "set-cookie";
    public static final String SET_COOKIE2 = "set-cookie2";
    public static final String X_XSS_PROTECTION = "x-xss-protection";
    public static final String X_FRAME_OPTION = "x-frame-options";
    public static final String X_CONTENT_TYPE_OPTIONS = "x-content-type-options";
    public static final String HTTP09 = "HTTP/0.9";
    public static final String HTTP10 = "HTTP/1.0";
    public static final String HTTP11 = "HTTP/1.1";
    public static final String HTTP2 = "HTTP/2";
    public static final String _CLOSE = "Close";
    public static final String _KEEP_ALIVE = "Keep-Alive";
    public static final String _CHUNKED = "Chunked";
    public static final String FORM_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded";
    public static final String JSON_CONTENT_TYPE = "application/json";
    public static final String SCHEME_HTTP = "http://";
    public static final String SCHEME_HTTPS = "https://";
    public static final String HTTP = "http";
    public static final String HTTPS = "https";
    public static final String DEFLATE = "deflate";
    public static final String GZIP = "gzip";
    public static final String IDENTITY = "identity";
    public static final String SEC_PROXY_INTERCEPT = "intercept";
    public static final String SEC_PROXY_RECORD = "record";
    public static final String LINK = "link";
    public static final String REFRESH = "refresh";
    public static final Pattern patternCRLF = Pattern.compile("\\r\\n", Pattern.MULTILINE);
    public static final Pattern patternLF = Pattern.compile("\\n", Pattern.MULTILINE);
    // ZAP: Issue 410: charset wrapped in quotation marks
    private static final Pattern patternCharset =
            Pattern.compile(
                    "charset *= *(?:(?:'([^';\\s]+))|(?:\"?([^\";\\s]+)\"?))",
                    Pattern.CASE_INSENSITIVE);
    protected static final String p_TEXT = "[^\\x00-\\x1f\\r\\n]*";
    protected static final String p_METHOD = "(\\w+)";
    protected static final String p_SP = " +";
    // protected static final String p_URI			= "(\\S+)";
    // allow space in URI for encoding to %20
    protected static final String p_URI = "([^\\r\\n]+)";
    protected static final String p_VERSION = "(HTTP/\\d+(?:\\.\\d+)?)";
    protected static final String p_STATUS_CODE = "(\\d{3})";
    protected static final String p_REASON_PHRASE = "(" + p_TEXT + ")";
    protected String mStartLine;
    protected String mMsgHeader;
    protected boolean mMalformedHeader;
    protected Hashtable> mHeaderFields;
    protected int mContentLength;
    protected String mLineDelimiter;
    protected String mVersion;
    // ZAP: added CORS headers
    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin";
    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers";
    public static final String ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods";
    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers";
    // ZAP: added "Allow" and "Public" Headers, for response to "OPTIONS" method
    public static final String METHODS_ALLOW = "Allow";
    public static final String METHODS_PUBLIC = "Public"; // IIS specific?
    public static final String X_ZAP_SCAN_ID = "x-zap-scan-id";
    public static final String X_ZAP_API_KEY = "x-zap-api-key";
    public static final String X_ZAP_API_NONCE = "x-zap-api-nonce";
    // ZAP: additional standard/defacto headers
    public static final String PROXY_AUTHORIZATION = "proxy-authorization";
    public static final String X_CSRF_TOKEN = "x-csrf-token";
    public static final String X_CSRFTOKEN = "x-csrftoken";
    public static final String X_XSRF_TOKEN = "x-xsrf-token";

    public HttpHeader() {
        init();
    }

    /**
     * Construct a HttpHeader from a given String.
     *
     * @param data
     * @throws HttpMalformedHeaderException
     */
    public HttpHeader(String data) throws HttpMalformedHeaderException {
        setMessage(data);
    }

    /** Inititialization. */
    private void init() {
        mHeaderFields = new Hashtable<>();
        mStartLine = "";
        mMsgHeader = "";
        mMalformedHeader = false;
        mContentLength = -1;
        mLineDelimiter = CRLF;
        mVersion = HttpHeader.HTTP10;
    }

    /**
     * Set and parse this HTTP header with the string given.
     *
     * @param data
     * @throws HttpMalformedHeaderException
     */
    public void setMessage(String data) throws HttpMalformedHeaderException {
        clear();
        try {
            if (!this.parse(data)) {
                mMalformedHeader = true;
            }
        } catch (Exception e) {
            mMalformedHeader = true;
        }

        if (mMalformedHeader) {
            throw new HttpMalformedHeaderException();
        }
    }

    public void clear() {
        init();
    }

    /**
     * Get the first header value using the name given. If there are multiple occurrence, only the
     * first one will be returned as String.
     *
     * @param name
     * @return the header value. null if not found.
     */
    public String getHeader(String name) {
        List headers = getHeaderValues(name);
        if (headers.isEmpty()) {
            return null;
        }

        return headers.get(0);
    }

    /**
     * Get headers with the name. Multiple value can be returned.
     *
     * @param name
     * @return a vector holding the value as string.
     * @deprecated since 2.9.0. See {@link #getHeaderValues(String)} instead
     */
    @Deprecated
    public Vector getHeaders(String name) {
        return mHeaderFields.get(normalisedHeaderName(name));
    }

    /**
     * Get header(s) with the name. Multiple values can be returned.
     *
     * @param name the name of the header(s) to return.
     * @return a {@code List} holding the value(s) as String(s).
     * @since 2.9.0
     */
    public List getHeaderValues(String name) {
        List values = mHeaderFields.get(normalisedHeaderName(name));
        return values == null ? Collections.emptyList() : Collections.unmodifiableList(values);
    }

    public List getHeaders() {
        List headerFields = new ArrayList<>();
        String[] headers = mMsgHeader.split(Pattern.quote(mLineDelimiter));

        for (int i = 0; i < headers.length; ++i) {
            String[] headerField = headers[i].split(":", 2);
            if (headerField.length == 2) {
                headerFields.add(new HttpHeaderField(headerField[0].trim(), headerField[1].trim()));
            }
        }
        return headerFields;
    }

    /**
     * Add a header with the name and value given. It will be appended to the header string.
     *
     * @param name
     * @param val
     */
    public void addHeader(String name, String val) {
        mMsgHeader = mMsgHeader + name + ": " + val + mLineDelimiter;
        addInternalHeaderFields(name, val);
    }

    /**
     * Set a header name and value. If the name is not found, it will be added. If the value is
     * null, the header will be removed.
     *
     * @param name
     * @param value
     */
    public void setHeader(String name, String value) {
        //		int pos = 0;
        //		int crlfpos = 0;
        Pattern pattern = null;

        if (getHeaderValues(name).isEmpty() && value != null) {
            // header value not found, append to end
            addHeader(name, value);
        } else {
            pattern = getHeaderRegex(name);
            Matcher matcher = pattern.matcher(mMsgHeader);
            if (value == null) {
                // delete header
                mMsgHeader = matcher.replaceAll("");
            } else {
                // replace header
                String newString = name + ": " + value + mLineDelimiter;
                mMsgHeader = matcher.replaceAll(Matcher.quoteReplacement(newString));
            }

            // set into hashtable
            replaceInternalHeaderFields(name, value);
        }
    }

    private Pattern getHeaderRegex(String name) throws PatternSyntaxException {
        // Added character quoting to avoid troubles with "-" char or similar
        return Pattern.compile(
                "^ *\\Q" + name + "\\E *: *[^\\r\\n]*" + mLineDelimiter,
                Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
    }

    /**
     * Return the HTTP version (e.g. HTTP/1.0, HTTP/1.1)
     *
     * @return
     */
    public String getVersion() {
        return mVersion;
    }

    /**
     * Set the HTTP version of this header.
     *
     * @param version
     */
    public abstract void setVersion(String version);

    /**
     * Get the content length of this header.
     *
     * @return content length. -1 means content length not set.
     */
    public int getContentLength() {
        return mContentLength;
    }

    /**
     * Set the content length of this header.
     *
     * @param len
     */
    public void setContentLength(int len) {
        if (mContentLength != len) {
            setHeader(CONTENT_LENGTH, Integer.toString(len));
            mContentLength = len;
        }
    }

    /**
     * Check if this header expect connection to be closed. HTTP/1.0 default to close. HTTP/1.1
     * default to keep-alive.
     *
     * @return
     */
    public boolean isConnectionClose() {
        boolean result = true;
        if (mMalformedHeader) {
            return true;
        }

        if (isHttp10()) {
            // HTTP 1.0 default to close unless keep alive.
            result = true;
            try {
                if (getHeader(CONNECTION).equalsIgnoreCase(_KEEP_ALIVE)
                        || getHeader(PROXY_CONNECTION).equalsIgnoreCase(_KEEP_ALIVE)) {
                    return false;
                }
            } catch (NullPointerException e) {
            }

        } else if (isHttp11()) {
            // HTTP 1.1 default to keep alive unless close.
            result = false;
            try {
                if (getHeader(CONNECTION).equalsIgnoreCase(_CLOSE)) {
                    return true;
                } else if (getHeader(PROXY_CONNECTION).equalsIgnoreCase(_CLOSE)) {
                    return true;
                }
            } catch (NullPointerException e) {
            }
        }
        return result;
    }

    /**
     * Check if this header is HTTP 1.0.
     *
     * @return true if HTTP 1.0.
     */
    public boolean isHttp10() {
        if (mVersion.equalsIgnoreCase(HTTP10)) {
            return true;
        }
        return false;
    }

    /**
     * Check if this header is HTTP 1.1.
     *
     * @return true if HTTP 1.0.
     */
    public boolean isHttp11() {
        if (mVersion.equalsIgnoreCase(HTTP11)) {
            return true;
        }
        return false;
    }

    /**
     * Check if Transfer Encoding Chunked is set in this header.
     *
     * @return true if transfer encoding chunked is set.
     */
    public boolean isTransferEncodingChunked() {
        String transferEncoding = getHeader(TRANSFER_ENCODING);
        if (transferEncoding != null && transferEncoding.equalsIgnoreCase(_CHUNKED)) {
            return true;
        }
        return false;
    }

    /**
     * Parse this Http header using the String given.
     *
     * @param data String to be parsed to form this header.
     * @return
     * @throws Exception
     */
    protected boolean parse(String data) throws Exception {
        if (data == null || data.isEmpty()) {
            return true;
        }

        // ZAP: Replace all "\n" with "\r\n" to parse correctly
        String newData = data.replaceAll("(? v = getHeaders(key);
        if (v == null) {
            v = new Vector<>();
            mHeaderFields.put(key, v);
        }

        if (value != null) {
            v.clear();
            v.add(value);
        } else {
            mHeaderFields.remove(key);
        }
    }

    /**
     * Add the header stored in internal hashtable
     *
     * @param name
     * @param value
     */
    private void addInternalHeaderFields(String name, String value) {
        String key = normalisedHeaderName(name);
        Vector v = getHeaders(key);
        if (v == null) {
            v = new Vector<>();
            mHeaderFields.put(key, v);
        }

        if (value != null) {
            v.add(value);
        } else {
            mHeaderFields.remove(key);
        }
    }

    /**
     * Gets the header name normalised, to obtain the value(s) from {@link #mHeaderFields}.
     *
     * 

The normalisation is done by changing all characters to upper case. * * @param name the name of the header to normalise. * @return the normalised header name. */ private static String normalisedHeaderName(String name) { return name.toUpperCase(Locale.ROOT); } /** * Get if this is a malformed header. * * @return */ public boolean isMalformedHeader() { return mMalformedHeader; } /** Get a string representation of this header. */ @Override public String toString() { return getPrimeHeader() + mLineDelimiter + mMsgHeader + mLineDelimiter; } /** * Get the prime header. * * @return startline for request, statusline for response. */ public abstract String getPrimeHeader(); /** * Get if this is a image header. * * @return true if image. */ public boolean isImage() { return false; } /** * Get if this is a text header. * * @return true if text. */ public boolean isText() { return true; } /** * Tells whether or not the HTTP header contains any of the given {@code Content-Type} values. * *

The values are expected to be in lower case. * * @param contentTypes the values to check. * @return {@code true} if any of the given values is contained in the (first) {@code * Content-Type} header, {@code false} otherwise. * @since 2.8.0 * @see #getNormalisedContentTypeValue() */ public boolean hasContentType(String... contentTypes) { if (contentTypes == null || contentTypes.length == 0) { return true; } String normalisedContentType = getNormalisedContentTypeValue(); if (normalisedContentType == null) { return false; } for (String contentType : contentTypes) { if (normalisedContentType.contains(contentType)) { return true; } } return false; } /** * Gets the normalised value of the (first) {@code Content-Type} header. * *

The normalisation is done by changing all characters to lower case. * * @return the value normalised, might be {@code null}. * @since 2.8.0 * @see #hasContentType(String...) */ public String getNormalisedContentTypeValue() { String contentType = getHeader(CONTENT_TYPE); if (contentType != null) { return contentType.toLowerCase(Locale.ROOT); } return null; } /** * Get the line delimiter of this header. * * @return */ public String getLineDelimiter() { return mLineDelimiter; } /** * Get the headers as string. All the headers name value pair is concatenated and delimited. * * @return Eg "Host: www.example.com\r\nUser-agent: some agent\r\n" */ public String getHeadersAsString() { return mMsgHeader; } /** * Tells whether or not the header is empty. * *

A header is empty if it has no content (for example, no start line nor headers). * * @return {@code true} if the header is empty, {@code false} otherwise. */ public boolean isEmpty() { if (mStartLine == null || mStartLine.isEmpty()) { return true; } return false; } public String getCharset() { String contentType = getHeader(CONTENT_TYPE); if (contentType == null) { return null; } Matcher matcher = patternCharset.matcher(contentType); if (matcher.find()) { String charset = matcher.group(2); if (charset == null) { return matcher.group(1); } return charset; } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy