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

io.rivulet.PhosphorHttpRequest Maven / Gradle / Ivy

The newest version!
package io.rivulet;

import io.rivulet.internal.RestructureRequestBytesCV;
import io.rivulet.internal.TaintedStringBuilder;
import org.apache.http.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import java.io.EOFException;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

/* Represents an HTTP request. Capable of converting the request it represents into a byte array using fine-grain public
 * accessor methods which make excellent auto-taint source methods. */
@SuppressWarnings("unused")
public class PhosphorHttpRequest implements Serializable {

    private static final long serialVersionUID = -2622573790320306202L;
    // The length of randomly generated boundaries for multipart/form-data content
    private static final int BOUNDARY_LEN = 35;
    // Characters used when generating boundaries for multipart/form-data content
    private static final char[] BOUNDARY_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    // Carriage return and line feed characters used to indicate the end of a line.
    private static final String CRLF = "\r\n";
    // The string name of the cookie header
    private static final String COOKIE_HEADER = "Cookie";
    // The string name of the upgrade-insecure-requests header
    private static final String UPGRADE_INSECURE_REQUESTS_HEADER = "Upgrade-Insecure-Requests";
    // List of common HTTP header names
    private static final List COMMON_HEADERS = Arrays.asList(COOKIE_HEADER, UPGRADE_INSECURE_REQUESTS_HEADER,
            HttpHeaders.ACCEPT, HttpHeaders.ACCEPT_CHARSET, HttpHeaders.ACCEPT_LANGUAGE, HttpHeaders.ACCEPT_RANGES,
            HttpHeaders.AGE, HttpHeaders.ALLOW, HttpHeaders.AUTHORIZATION, HttpHeaders.CACHE_CONTROL, HttpHeaders.CONNECTION,
            HttpHeaders.CONTENT_LANGUAGE, HttpHeaders.CONTENT_LOCATION, HttpHeaders.CONTENT_MD5, HttpHeaders.CONTENT_RANGE,
            HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE, HttpHeaders.DAV, HttpHeaders.DEPTH, HttpHeaders.DESTINATION,
            HttpHeaders.ETAG, HttpHeaders.EXPECT, HttpHeaders.EXPIRES, HttpHeaders.FROM, HttpHeaders.HOST, HttpHeaders.IF,
            HttpHeaders.IF_MATCH, HttpHeaders.IF_MODIFIED_SINCE, HttpHeaders.IF_NONE_MATCH, HttpHeaders.IF_RANGE,
            HttpHeaders.IF_UNMODIFIED_SINCE, HttpHeaders.LAST_MODIFIED, HttpHeaders.LOCATION, HttpHeaders.LOCK_TOKEN, HttpHeaders.MAX_FORWARDS,
            HttpHeaders.OVERWRITE, HttpHeaders.PRAGMA, HttpHeaders.PROXY_AUTHENTICATE, HttpHeaders.PROXY_AUTHORIZATION,
            HttpHeaders.RANGE, HttpHeaders.REFERER, HttpHeaders.RETRY_AFTER, HttpHeaders.SERVER, HttpHeaders.STATUS_URI,
            HttpHeaders.TE, HttpHeaders.TIMEOUT, HttpHeaders.TRAILER, HttpHeaders.TRANSFER_ENCODING,
            HttpHeaders.UPGRADE, HttpHeaders.USER_AGENT, HttpHeaders.VARY, HttpHeaders.VIA, HttpHeaders.WARNING, HttpHeaders.WWW_AUTHENTICATE
    );
    // List of http header names for headers whose values are replaced
    private static final List REPLACED_HEADERS = Arrays.asList(HttpHeaders.ACCEPT_ENCODING,
            HttpHeaders.CONTENT_ENCODING, HttpHeaders.CONTENT_LENGTH);
    // Max size of chunks for chunked message bodies
    private static final int MAX_CHUNK_SIZE = 4096;
    // Hex string for MAX_CHUNK_SIZE
    private static final String MAX_CHUNK_STR = "1000";
    // Maps channels to the parser used to parse bytes read from it
    private static final Map> parserMap = new ConcurrentHashMap<>();
    // Tracks the String representations of PhosphorHttpRequest that have been returned by calls to requestFromSocket by
    // structureRequestBytes.
    private static final List interceptedRequests = new LinkedList<>();

    // Part of the request line, indicates the method to be performed
    private final String method;
    // Part of the request line, indicates the resource the request applies to
    private final URI uri;
    // Part of the request line, indicates the version of HTTP used
    private final String protocolVersion;
    // Maps commonly used HTTP message header names to their values for this request
    private final HashMap commonHeaders;
    // Maps non-common HTTP message header names to their values for this request
    private final LinkedHashMap uncommonHeaders;
    // The String entity body of the request or null if no entity body is present
    private final String entityBody;
    // Maps HTTP message trailer names to their values for this request
    private final TreeMap trailers;

    /* Constructs a new PhosphorHttpRequest containing information from the specified HttpRequest. */
    public PhosphorHttpRequest(HttpRequest request, HttpEntity entity, Map trailers) throws URISyntaxException, IOException {
        this.method = request.getRequestLine().getMethod();
        this.uri = new URI(request.getRequestLine().getUri());
        this.protocolVersion = request.getProtocolVersion().toString();
        this.commonHeaders = new HashMap<>();
        this.uncommonHeaders = new LinkedHashMap<>();
        this.trailers = new TreeMap<>(trailers);
        for(Header header : request.getAllHeaders()) {
            addHeader(header);
        }
        if(entity != null) {
            String body = EntityUtils.toString(entity);
            EntityUtils.consume(entity);
            // Try to replace the boundary to one of a fixed length if entity is has multipart/form-data content
            try {
                String contentType = commonHeaders.get(HttpHeaders.CONTENT_TYPE);
                if(contentType != null && contentType.toLowerCase().contains(ContentType.MULTIPART_FORM_DATA.getMimeType().toLowerCase())) {
                    String boundary = contentType.substring(contentType.indexOf("boundary=")+"boundary=".length()).trim();
                    String fixedBoundary = generateBoundary();
                    commonHeaders.put(HttpHeaders.CONTENT_TYPE, contentType.replace(boundary, fixedBoundary));
                    if(body != null ) {
                        body = body.replace(boundary, fixedBoundary);
                    }
                }
            } catch(Exception e) {
                //
            }
            this.entityBody = body;
        } else {
            this.entityBody = null;
        }
    }

    /* Generates a random boundary of a fixed length for multipart/form-data content. */
    private static String generateBoundary() {
        final StringBuilder builder = new StringBuilder();
        final Random rand = ThreadLocalRandom.current();
        for(int i = 0; i < BOUNDARY_LEN; i++) {
            builder.append(BOUNDARY_CHARS[rand.nextInt(BOUNDARY_CHARS.length)]);
        }
        return builder.toString();
    }

    /* Adds the specified header to either the list of common header or the list of other header. */
    private void addHeader(Header header) {
        String name = header.getName();
        String value = header.getValue();
        // Skip any header whose value is replaced
        for(String replaceHeader : REPLACED_HEADERS) {
            if(header.getName().equalsIgnoreCase(replaceHeader)) {
                return;
            }
        }
        // Check if this is a common header
        for(String commonHeader : COMMON_HEADERS) {
            if(header.getName().equalsIgnoreCase(commonHeader)) {
                commonHeaders.put(commonHeader, value);
                return;
            }
        }
        // Header name did not match any of the common headers
        uncommonHeaders.put(name, value);
    }

    /* Returns the HTTP method specifying the action to be performed by this request. */
    public String getMethod() {
        return method;
    }

    /* Returns this request's URI's scheme or null if undefined. */
    public String getScheme() {
        return uri.getScheme();
    }

    /* Returns this request's URI's fragment component or null if undefined. */
    public String getFragment() {
        return uri.getFragment();
    }

    /* Returns this request's URI's user-information or null if undefined. */
    public String getUserInfo() {
        return uri.getUserInfo();
    }

    /* Returns this request's URI's host or null if undefined. */
    public String getHost() {
        if(uri.getHost() == null && uri.getAuthority() != null) {
           String authority = uri.getAuthority();
           if(authority.contains(":")) {
               return authority.substring(0, authority.lastIndexOf(':'));
           } else {
               return authority;
           }
        } else {
            return uri.getHost();
        }
    }

    /* Returns this request's URI's port or -1 if undefined. */
    public int getPort() {
        if(uri.getHost() == null && uri.getAuthority() != null) {
            String authority = uri.getAuthority();
            if(authority.contains(":")) {
                try {
                    return Integer.parseInt(authority.substring(authority.lastIndexOf(':')+1));
                } catch(Exception e) {
                    return -1;
                }

            } else {
                return -1;
            }
        } else {
            return uri.getPort();
        }
    }

    /* Returns this request's URI's encoded path or null if undefined. */
    public String getEncodedPath() {
        return uri.getRawPath();
    }

    /* Returns this request's URI's encoded query or null if undefined. */
    public String getEncodedQuery() {
        return uri.getRawQuery();
    }

    /* Returns a string indicating the version of HTTP used for this request. */
    public String getProtocolVersion() {
        return protocolVersion;
    }

    /* Returns the map of uncommon headers for this request. */
    public Map getUncommonHeaders() {
        return uncommonHeaders;
    }

    /* Returns the value of the accept header or null if undefined. */
    public String getAcceptHeader() {
        return commonHeaders.get(HttpHeaders.ACCEPT);
    }

    /* Returns the value of the accept-charset header or null if undefined. */
    public String getAcceptCharsetHeader() {
        return commonHeaders.get(HttpHeaders.ACCEPT_CHARSET);
    }

    /* Returns the value of the accept-language header or null if undefined. */
    public String getAcceptLanguageHeader() {
        return commonHeaders.get(HttpHeaders.ACCEPT_LANGUAGE);
    }

    /* Returns the value of the accept-ranges header or null if undefined. */
    public String getAcceptRangesHeader() {
        return commonHeaders.get(HttpHeaders.ACCEPT_RANGES);
    }

    /* Returns the value of the age header or null if undefined. */
    public String getAgeHeader() {
        return commonHeaders.get(HttpHeaders.AGE);
    }

    /* Returns the value of the allow header or null if undefined. */
    public String getAllowHeader() {
        return commonHeaders.get(HttpHeaders.ALLOW);
    }

    /* Returns the value of the authorization header or null if undefined. */
    public String getAuthorizationHeader() {
        return commonHeaders.get(HttpHeaders.AUTHORIZATION);
    }

    /* Returns the value of the cache-control header or null if undefined. */
    public String getCacheControlHeader() {
        return commonHeaders.get(HttpHeaders.CACHE_CONTROL);
    }

    /* Returns the value of the connection header or null if undefined. */
    public String getConnectionHeader() {
        return commonHeaders.get(HttpHeaders.CONNECTION);
    }

    /* Returns the value of the content-language header or null if undefined. */
    public String getContentLanguageHeader() {
        return commonHeaders.get(HttpHeaders.CONTENT_LANGUAGE);
    }

    /* Returns the value of the content-location header or null if undefined. */
    public String getContentLocationHeader() {
        return commonHeaders.get(HttpHeaders.CONTENT_LOCATION);
    }

    /* Returns the value of the content-md5 header or null if undefined. */
    public String getContentMd5Header() {
        return commonHeaders.get(HttpHeaders.CONTENT_MD5);
    }

    /* Returns the value of the content-range header or null if undefined. */
    public String getContentRangeHeader() {
        return commonHeaders.get(HttpHeaders.CONTENT_RANGE);
    }

    /* Returns the value of the content-type header or null if undefined. */
    public String getContentTypeHeader() {
        return commonHeaders.get(HttpHeaders.CONTENT_TYPE);
    }

    /* Returns the value of the cookie header or null if undefined. */
    public String getCookieHeader() {
        return commonHeaders.get(COOKIE_HEADER);
    }

    /* Returns the value of the date header or null if undefined. */
    public String getDateHeader() {
        return commonHeaders.get(HttpHeaders.DATE);
    }

    /* Returns the value of the dav header or null if undefined. */
    public String getDavHeader() {
        return commonHeaders.get(HttpHeaders.DAV);
    }

    /* Returns the value of the depth header or null if undefined. */
    public String getDepthHeader() {
        return commonHeaders.get(HttpHeaders.DEPTH);
    }

    /* Returns the value of the destination header or null if undefined. */
    public String getDestinationHeader() {
        return commonHeaders.get(HttpHeaders.DESTINATION);
    }

    /* Returns the value of the ETag header or null if undefined. */
    public String getETagHeader() {
        return commonHeaders.get(HttpHeaders.ETAG);
    }

    /* Returns the value of the expect header or null if undefined. */
    public String getExpectHeader() {
        return commonHeaders.get(HttpHeaders.EXPECT);
    }

    /* Returns the value of the expires header or null if undefined. */
    public String getExpiresHeader() {
        return commonHeaders.get(HttpHeaders.EXPIRES);
    }

    /* Returns the value of the from header or null if undefined. */
    public String getFromHeader() {
        return commonHeaders.get(HttpHeaders.FROM);
    }

    /* Returns the value of the host header or null if undefined. */
    public String getHostHeader() {
        return commonHeaders.get(HttpHeaders.HOST);
    }

    /* Returns the value of the if header or null if undefined. */
    public String getIfHeader() {
        return commonHeaders.get(HttpHeaders.IF);
    }

    /* Returns the value of the if-match header or null if undefined. */
    public String getIfMatchHeader() {
        return commonHeaders.get(HttpHeaders.IF_MATCH);
    }

    /* Returns the value of the if-modified-since header or null if undefined. */
    public String getIfModifiedSinceHeader() {
        return commonHeaders.get(HttpHeaders.IF_MODIFIED_SINCE);
    }

    /* Returns the value of the if-none-match header or null if undefined. */
    public String getIfNoneMatchHeader() {
        return commonHeaders.get(HttpHeaders.IF_NONE_MATCH);
    }

    /* Returns the value of the if-range header or null if undefined. */
    public String getIfRangeHeader() {
        return commonHeaders.get(HttpHeaders.IF_RANGE);
    }

    /* Returns the value of the if-unmodified-since header or null if undefined. */
    public String getIfUnmodifiedSinceHeader() {
        return commonHeaders.get(HttpHeaders.IF_UNMODIFIED_SINCE);
    }

    /* Returns the value of the last-modified header or null if undefined. */
    public String getLastModifiedHeader() {
        return commonHeaders.get(HttpHeaders.LAST_MODIFIED);
    }

    /* Returns the value of the location header or null if undefined. */
    public String getLocationHeader() {
        return commonHeaders.get(HttpHeaders.LOCATION);
    }

    /* Returns the value of the lock-token header or null if undefined. */
    public String getLockTokenHeader() {
        return commonHeaders.get(HttpHeaders.LOCK_TOKEN);
    }

    /* Returns the value of the max-forwards header or null if undefined. */
    public String getMaxForwardsHeader() {
        return commonHeaders.get(HttpHeaders.MAX_FORWARDS);
    }

    /* Returns the value of the overwrite header or null if undefined. */
    public String getOverwriteHeader() {
        return commonHeaders.get(HttpHeaders.OVERWRITE);
    }

    /* Returns the value of the pragma header or null if undefined. */
    public String getPragmaHeader() {
        return commonHeaders.get(HttpHeaders.PRAGMA);
    }

    /* Returns the value of the proxy-authenticate header or null if undefined. */
    public String getProxyAuthenticateHeader() {
        return commonHeaders.get(HttpHeaders.PROXY_AUTHENTICATE);
    }

    /* Returns the value of the proxy-authorization header or null if undefined. */
    public String getProxyAuthorizationHeader() {
        return commonHeaders.get(HttpHeaders.PROXY_AUTHORIZATION);
    }

    /* Returns the value of the range header or null if undefined. */
    public String getRangeHeader() {
        return commonHeaders.get(HttpHeaders.RANGE);
    }

    /* Returns the value of the referer header or null if undefined. */
    public String getRefererHeader() {
        return commonHeaders.get(HttpHeaders.REFERER);
    }

    /* Returns the value of the retry-after header or null if undefined. */
    public String getRetryAfterHeader() {
        return commonHeaders.get(HttpHeaders.RETRY_AFTER);
    }

    /* Returns the value of the server header or null if undefined. */
    public String getServerHeader() {
        return commonHeaders.get(HttpHeaders.SERVER);
    }

    /* Returns the value of the status-uri header or null if undefined. */
    public String getStatusUriHeader() {
        return commonHeaders.get(HttpHeaders.STATUS_URI);
    }

    /* Returns the value of the te header or null if undefined. */
    public String getTeHeader() {
        return commonHeaders.get(HttpHeaders.TE);
    }

    /* Returns the value of the timeout header or null if undefined. */
    public String getTimeoutHeader() {
        return commonHeaders.get(HttpHeaders.TIMEOUT);
    }

    /* Returns the value of the trailer header or null if undefined. */
    public String getTrailerHeader() {
        return commonHeaders.get(HttpHeaders.TRAILER);
    }

    /* Returns the value of the transfer-encoding header or null if undefined. */
    public String getTransferEncodingHeader() {
        return commonHeaders.get(HttpHeaders.TRANSFER_ENCODING);
    }

    /* Returns the value of the upgrade header or null if undefined. */
    public String getUpgradeHeader() {
        return commonHeaders.get(HttpHeaders.UPGRADE);
    }

    /* Returns the value of the upgrade-insecure-requests header or null if undefined. */
    public String getUpgradeInsecureRequestsHeader() {
        return commonHeaders.get(UPGRADE_INSECURE_REQUESTS_HEADER);
    }

    /* Returns the value of the user-agent header or null if undefined. */
    public String getUserAgentHeader() {
        return commonHeaders.get(HttpHeaders.USER_AGENT);
    }

    /* Returns the value of the vary header or null if undefined. */
    public String getVaryHeader() {
        return commonHeaders.get(HttpHeaders.VARY);
    }

    /* Returns the value of the via header or null if undefined. */
    public String getViaHeader() {
        return commonHeaders.get(HttpHeaders.VIA);
    }

    /* Returns the value of the warning header or null if undefined. */
    public String getWarningHeader() {
        return commonHeaders.get(HttpHeaders.WARNING);
    }

    /* Returns the value of the www-authenticate header or null if undefined. */
    public String getWWWAuthenticateHeader() {
        return commonHeaders.get(HttpHeaders.WWW_AUTHENTICATE);
    }

    /* Returns the string value of this request's entity body or null if no entity body is present. */
    public String getEntityBody() {
        return entityBody;
    }

    /* Returns the trailers for the request. */
    public Map getTrailers() {
        return  trailers;
    }

    /* Adds this request's URI information to the specified StringBuilder. */
    private void addURIInfo(StringBuilder builder) throws Exception {
        String scheme = getScheme();
        String userInfo = getUserInfo();
        String host = getHost();
        String fragment = getFragment();
        int port = getPort();
        String encodedPath = getEncodedPath();
        String encodedQuery = getEncodedQuery();
        URIBuilder uriBuilder = new URIBuilder().setCharset(Consts.UTF_8);
        if(scheme != null) {
            uriBuilder.setScheme(scheme);
        }
        if(userInfo != null) {
            uriBuilder.setUserInfo(userInfo);
        }
        if(host != null) {
            uriBuilder.setHost(host);
        }
        if(fragment != null) {
            uriBuilder.setFragment(fragment);
        }
        if(port >= 0) {
            uriBuilder.setPort(port);
        }
        if(encodedPath != null) {
            addEncodedPathString(uriBuilder, encodedPath);
        }
        builder.append(uriBuilder.toString());
        if(encodedQuery != null) {
            builder.append('?').append(encodedQuery);
        }
    }

    /* Adds an encoded path string to the specified URIBuilder. */
    private static void addEncodedPathString(URIBuilder uriBuilder, String path) throws IllegalAccessException {
        Field pathField = getField(uriBuilder, "encodedPath", String.class);
        if(pathField == null) {
            throw new RuntimeException("Could not find encodedPath field for URIBuilder instance");
        } else {
            pathField.set(uriBuilder, path);
        }
    }

    /* Adds this request's header information to the specified StringBuilder. Returns whether an array of booleans indicating
     * whether there is a non-null trailer header for this request and where this request has chunked transfer encoding. */
    private boolean[] addHeaderInfo(StringBuilder builder) {
        boolean hasTrailerHeader;
        boolean hasChunkedTransferEncoding;
        // Add common headers
        String temp = getAcceptHeader();
        if(temp != null) {
            builder.append(HttpHeaders.ACCEPT).append(": ").append(temp).append(CRLF);
        }
        temp = getAcceptCharsetHeader();
        if(temp != null) {
            builder.append(HttpHeaders.ACCEPT_CHARSET).append(": ").append(temp).append(CRLF);
        }
        temp = getAcceptLanguageHeader();
        if(temp != null) {
            builder.append(HttpHeaders.ACCEPT_LANGUAGE).append(": ").append(temp).append(CRLF);
        }
        temp = getAcceptRangesHeader();
        if(temp != null) {
            builder.append(HttpHeaders.ACCEPT_RANGES).append(": ").append(temp).append(CRLF);
        }
        temp = getAgeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.AGE).append(": ").append(temp).append(CRLF);
        }
        temp = getAllowHeader();
        if(temp != null) {
            builder.append(HttpHeaders.ALLOW).append(": ").append(temp).append(CRLF);
        }
        temp = getAuthorizationHeader();
        if(temp != null) {
            builder.append(HttpHeaders.AUTHORIZATION).append(": ").append(temp).append(CRLF);
        }
        temp = getCacheControlHeader();
        if(temp != null) {
            builder.append(HttpHeaders.CACHE_CONTROL).append(": ").append(temp).append(CRLF);
        }
        temp = getConnectionHeader();
        if(temp != null) {
            builder.append(HttpHeaders.CONNECTION).append(": ").append(temp).append(CRLF);
        }
        temp = getContentLanguageHeader();
        if(temp != null) {
            builder.append(HttpHeaders.CONTENT_LANGUAGE).append(": ").append(temp).append(CRLF);
        }
        temp = getContentLocationHeader();
        if(temp != null) {
            builder.append(HttpHeaders.CONTENT_LOCATION).append(": ").append(temp).append(CRLF);
        }
        temp = getContentMd5Header();
        if(temp != null) {
            builder.append(HttpHeaders.CONTENT_MD5).append(": ").append(temp).append(CRLF);
        }
        temp = getContentRangeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.CONTENT_RANGE).append(": ").append(temp).append(CRLF);
        }
        temp = getContentTypeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.CONTENT_TYPE).append(": ").append(temp).append(CRLF);
        }
        temp = getCookieHeader();
        if(temp != null) {
            builder.append(COOKIE_HEADER).append(": ").append(temp).append(CRLF);
        }
        temp = getDateHeader();
        if(temp != null) {
            builder.append(HttpHeaders.DATE).append(": ").append(temp).append(CRLF);
        }
        temp = getDavHeader();
        if(temp != null) {
            builder.append(HttpHeaders.DAV).append(": ").append(temp).append(CRLF);
        }
        temp = getDepthHeader();
        if(temp != null) {
            builder.append(HttpHeaders.DEPTH).append(": ").append(temp).append(CRLF);
        }
        temp = getDestinationHeader();
        if(temp != null) {
            builder.append(HttpHeaders.DESTINATION).append(": ").append(temp).append(CRLF);
        }
        temp = getETagHeader();
        if(temp != null) {
            builder.append(HttpHeaders.ETAG).append(": ").append(temp).append(CRLF);
        }
        temp = getExpectHeader();
        if(temp != null) {
            builder.append(HttpHeaders.EXPECT).append(": ").append(temp).append(CRLF);
        }
        temp = getExpiresHeader();
        if(temp != null) {
            builder.append(HttpHeaders.EXPIRES).append(": ").append(temp).append(CRLF);
        }
        temp = getFromHeader();
        if(temp != null) {
            builder.append(HttpHeaders.FROM).append(": ").append(temp).append(CRLF);
        }
        temp = getHostHeader();
        if(temp != null) {
            builder.append(HttpHeaders.HOST).append(": ").append(temp).append(CRLF);
        }
        temp = getIfHeader();
        if(temp != null) {
            builder.append(HttpHeaders.IF).append(": ").append(temp).append(CRLF);
        }
        temp = getIfMatchHeader();
        if(temp != null) {
            builder.append(HttpHeaders.IF_MATCH).append(": ").append(temp).append(CRLF);
        }
        temp = getIfModifiedSinceHeader();
        if(temp != null) {
            builder.append(HttpHeaders.IF_MODIFIED_SINCE).append(": ").append(temp).append(CRLF);
        }
        temp = getIfNoneMatchHeader();
        if(temp != null) {
            builder.append(HttpHeaders.IF_NONE_MATCH).append(": ").append(temp).append(CRLF);
        }
        temp = getIfRangeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.IF_RANGE).append(": ").append(temp).append(CRLF);
        }
        temp = getIfUnmodifiedSinceHeader();
        if(temp != null) {
            builder.append(HttpHeaders.IF_UNMODIFIED_SINCE).append(": ").append(temp).append(CRLF);
        }
        temp = getLastModifiedHeader();
        if(temp != null) {
            builder.append(HttpHeaders.LAST_MODIFIED).append(": ").append(temp).append(CRLF);
        }
        temp = getLocationHeader();
        if(temp != null) {
            builder.append(HttpHeaders.LOCATION).append(": ").append(temp).append(CRLF);
        }
        temp = getLockTokenHeader();
        if(temp != null) {
            builder.append(HttpHeaders.LOCK_TOKEN).append(": ").append(temp).append(CRLF);
        }
        temp = getMaxForwardsHeader();
        if(temp != null) {
            builder.append(HttpHeaders.MAX_FORWARDS).append(": ").append(temp).append(CRLF);
        }
        temp = getOverwriteHeader();
        if(temp != null) {
            builder.append(HttpHeaders.OVERWRITE).append(": ").append(temp).append(CRLF);
        }
        temp = getPragmaHeader();
        if(temp != null) {
            builder.append(HttpHeaders.PRAGMA).append(": ").append(temp).append(CRLF);
        }
        temp = getProxyAuthenticateHeader();
        if(temp != null) {
            builder.append(HttpHeaders.PROXY_AUTHENTICATE).append(": ").append(temp).append(CRLF);
        }
        temp = getProxyAuthorizationHeader();
        if(temp != null) {
            builder.append(HttpHeaders.PROXY_AUTHORIZATION).append(": ").append(temp).append(CRLF);
        }
        temp = getRangeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.RANGE).append(": ").append(temp).append(CRLF);
        }
        temp = getRefererHeader();
        if(temp != null) {
            builder.append(HttpHeaders.REFERER).append(": ").append(temp).append(CRLF);
        }
        temp = getRetryAfterHeader();
        if(temp != null) {
            builder.append(HttpHeaders.RETRY_AFTER).append(": ").append(temp).append(CRLF);
        }
        temp = getServerHeader();
        if(temp != null) {
            builder.append(HttpHeaders.SERVER).append(": ").append(temp).append(CRLF);
        }
        temp = getStatusUriHeader();
        if(temp != null) {
            builder.append(HttpHeaders.STATUS_URI).append(": ").append(temp).append(CRLF);
        }
        temp = getTeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.TE).append(": ").append(temp).append(CRLF);
        }
        temp = getTimeoutHeader();
        if(temp != null) {
            builder.append(HttpHeaders.TIMEOUT).append(": ").append(temp).append(CRLF);
        }
        temp = getTrailerHeader();
        hasTrailerHeader = temp != null;
        if(temp != null) {
            builder.append(HttpHeaders.TRAILER).append(": ").append(temp).append(CRLF);
        }
        temp = getTransferEncodingHeader();
        hasChunkedTransferEncoding = HTTP.CHUNK_CODING.equalsIgnoreCase(temp);
        if(temp != null) {
            builder.append(HttpHeaders.TRANSFER_ENCODING).append(": ").append(temp).append(CRLF);
        }
        temp = getUpgradeHeader();
        if(temp != null) {
            builder.append(HttpHeaders.UPGRADE).append(": ").append(temp).append(CRLF);
        }
        temp = getUpgradeInsecureRequestsHeader();
        if(temp != null) {
            builder.append(UPGRADE_INSECURE_REQUESTS_HEADER).append(": ").append(temp).append(CRLF);
        }
        temp = getUserAgentHeader();
        if(temp != null) {
            builder.append(HttpHeaders.USER_AGENT).append(": ").append(temp).append(CRLF);
        }
        temp = getVaryHeader();
        if(temp != null) {
            builder.append(HttpHeaders.VARY).append(": ").append(temp).append(CRLF);
        }
        temp = getViaHeader();
        if(temp != null) {
            builder.append(HttpHeaders.VIA).append(": ").append(temp).append(CRLF);
        }
        temp = getWarningHeader();
        if(temp != null) {
            builder.append(HttpHeaders.WARNING).append(": ").append(temp).append(CRLF);
        }
        temp = getWWWAuthenticateHeader();
        if(temp != null) {
            builder.append(HttpHeaders.WWW_AUTHENTICATE).append(": ").append(temp).append(CRLF);
        }
        // Add the uncommon headers
        Map uncommonHeaders = getUncommonHeaders();
        for(String name : uncommonHeaders.keySet()) {
            if(uncommonHeaders.get(name) != null) {
                builder.append(name).append(": ").append(uncommonHeaders.get(name)).append(CRLF);
            }
        }
        // Add the replaced headers
        builder.append(HttpHeaders.ACCEPT_ENCODING).append(": ").append(HTTP.IDENTITY_CODING).append(CRLF);
        builder.append(HttpHeaders.CONTENT_ENCODING).append(": ").append(HTTP.IDENTITY_CODING).append(CRLF);
        return new boolean[]{hasTrailerHeader, hasChunkedTransferEncoding};
    }

    /* Adds this request's content-length header and entity body to the specified StringBuilder. */
    private void addEntityInfo(StringBuilder builder, boolean hasChunkedTransferEncoding) {
        String body = getEntityBody();
        if(hasChunkedTransferEncoding) {
            if(body != null && !body.isEmpty()) {
                int i = 0;
                while(i < body.length()) {
                    int chunkSize = Math.min(body.length() - i, MAX_CHUNK_SIZE);
                    String chunkStr = (chunkSize == MAX_CHUNK_SIZE) ? MAX_CHUNK_STR : Integer.toHexString(chunkSize);
                    builder.append(chunkStr).append("\r\n");
                    for(int j = 0; j < chunkSize; j++, i++) {
                        builder.append(body.charAt(i));
                    }
                    builder.append("\r\n");
                }
            }
            // Add a terminating chunk
            builder.append("0\r\n");
        } else {
            if(body != null) {
                builder.append(HttpHeaders.CONTENT_LENGTH).append(": ").append(body.length()).append(CRLF);
                builder.append(CRLF).append(body);
            } else {
                builder.append(HttpHeaders.CONTENT_LENGTH).append(": ").append("0").append(CRLF).append(CRLF);
            }
        }
    }

    /* Adds trailers for this request to the specified StringBuilder if this request's transfer-encoding is chunked and
     * it has a trailer header. */
    private void addTrailers(StringBuilder builder, boolean hasTrailerHeader, boolean hasChunkedTransferEncoding) {
        Map trailers = getTrailers();
        if(hasChunkedTransferEncoding) {
            if(hasTrailerHeader) {
                // Add the trailers
                for(String name : trailers.keySet()) {
                    if(trailers.get(name) != null) {
                        builder.append(name).append(": ").append(trailers.get(name)).append(CRLF);
                    }
                }
            }
            // Add trailer end
            builder.append("\r\n");
        }
    }

    /* Returns a text representation of this request. */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(getMethod()).append(' ');
        try {
            addURIInfo(builder);
        } catch(Exception e) {
            builder.append(uri.toASCIIString());
        }
        builder.append(' ').append(getProtocolVersion()).append(CRLF);
        boolean[] temp = addHeaderInfo(builder);
        boolean hasTrailerHeader = temp[0];
        boolean hasChunkedTransferEncoding = temp[1];
        addEntityInfo(builder, hasChunkedTransferEncoding);
        addTrailers(builder, hasTrailerHeader, hasChunkedTransferEncoding);
        return builder.toString();
    }

    /* Returns the field with the specified name and type for the specified object's class or null if the
     * field was not found. */
    private static Field getField(Object obj, String fieldName, Class fieldClass) {
        for(Class clazz = obj.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            // Iterate over superclasses to check for the field
            try {
                Field field = clazz.getDeclaredField(fieldName);
                if(field.getName().equals(fieldName) && fieldClass.isAssignableFrom(field.getType())) {
                    field.setAccessible(true);
                    return field;
                }
            } catch (Exception e) {
                //
            }
        }
        return null;
    }

    /* Reads bytes from the specified channel, structures the read bytes into a PhosphorHttpRequest, converts that
     * request back into bytes, and return a ByteBuffer wrapping those bytes. Returns null if any errors occurred. */
    private static ByteBuffer structureRequestBytes(ByteChannel channel, int bufferLength) throws IOException, URISyntaxException, HttpException {
        PhosphorHttpRequest request = requestFromSocket(channel, bufferLength);
        if(request == null) {
            return null;
        }
        String requestStr = request.toString();
        String untaintedRequestStr = TaintedStringBuilder.taintChars(new String(requestStr.toCharArray()), null);
        synchronized(PhosphorHttpRequest.class) {
            interceptedRequests.add(untaintedRequestStr);
        }
        return ByteBuffer.wrap(requestStr.getBytes());
    }

    /* Reads bytes from the specified channel into a buffer of the specified initial length. Returns a PhosphorHttpRequest
     * parsed from the read bytes. */
    private static PhosphorHttpRequest requestFromSocket(ByteChannel channel, int initialBufferLength) throws IOException, URISyntaxException, HttpException {
        parserMap.putIfAbsent(channel, BufferedHttpMessageParser.getRequestParser());
        BufferedHttpMessageParser parser = parserMap.get(channel);
        int nRead = 1;
        while(!parser.hasParsedMessage()) {
            if(nRead < 0) {
                throw new EOFException();
            } else if(nRead == 0) {
                return null;
            }
            byte[] bytes = new byte[initialBufferLength];
            ByteBuffer buf = ByteBuffer.wrap(bytes);
            nRead = channel.read(buf);
            buf.flip();
            parser.appendBytes(buf);
        }
        BufferedHttpMessageParser.ParsedMessage parsed = parser.getParsedMessage();
        return new PhosphorHttpRequest(parsed.getMessage(), parsed.getEntity(), parsed.getTrailers());
    }

    /* Reads from the specified Object's phosphor-added buffer into the specified destination buffer. If the phosphor-added
     * is exhausted,reads bytes from the specified ByteChannel until a request can be parsed from them. Parses a request
     * from the read bytes and then refills the specified source buffer with those bytes. Returns the number of bytes read
     * into the destination buffer. */
    public static int read(ByteChannel channel, ByteBuffer dest, Object obj) {
        try {
            Field bufField = getField(obj, RestructureRequestBytesCV.BYTE_BUFF_FIELD_NAME, ByteBuffer.class);
            if(bufField == null) {
                throw new IllegalStateException("Expected ByteBuffer field to be added by Phosphor to store the restructured bytes");
            }
            ByteBuffer source = (ByteBuffer) bufField.get(obj);
            if(source == null || !source.hasRemaining()) {
                int bufferLength = dest.capacity();
                source = PhosphorHttpRequest.structureRequestBytes(channel, bufferLength);
                bufField.set(obj, source);
            }
            if(source == null) {
                return 0;
            }
            int nRead = Math.min(source.remaining(), dest.remaining());
            if(nRead > 0) {
                dest.put(source.array(), source.arrayOffset() + source.position(), nRead);
                source.position(source.position() + nRead);
            }
            return nRead;
        } catch(EOFException e) {
            return -1;
        } catch(Exception e) {
            return 0;
        }
    }

    public static long read(ByteChannel channel, ByteBuffer[] dests, int offset, int length, Object obj) {
        if((offset < 0) || (length < 0) || (offset > dests.length - length)) {
            throw new IndexOutOfBoundsException();
        }
        try {
            Field bufField = getField(obj, RestructureRequestBytesCV.BYTE_BUFF_FIELD_NAME, ByteBuffer.class);
            if(bufField == null) {
                throw new IllegalStateException("Expected ByteBuffer field to be added by Phosphor to store the restructured bytes");
            }
            ByteBuffer source = (ByteBuffer) bufField.get(obj);
            if(source == null || !source.hasRemaining()) {
                int bufferLength = dests[offset].capacity();
                source = PhosphorHttpRequest.structureRequestBytes(channel, bufferLength);
                bufField.set(obj, source);
            }
            if(source == null) {
                return 0;
            }
            int totalRead = 0;
            while(source.hasRemaining() && offset < offset + length) {
                int nRead = Math.min(source.remaining(), dests[offset].remaining());
                if(nRead > 0) {
                    dests[offset].put(source.array(), source.arrayOffset() + source.position(), nRead);
                    source.position(source.position() + nRead);
                }
                totalRead += nRead;
            }
            return totalRead;
        } catch(EOFException e) {
            return -1;
        } catch(Exception e) {
            return 0;
        }
    }

    /* Creates a copy of interceptedRequests and then clears interceptedRequests. Returns the copy. */
    public static synchronized LinkedList getAndClearInterceptedRequests() {
        LinkedList copy = new LinkedList<>(interceptedRequests);
        interceptedRequests.clear();
        return copy;
    }

    /* Clears interceptedRequests. */
    public static synchronized void clearInterceptedRequests() {
        interceptedRequests.clear();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy