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

org.glassfish.grizzly.http.HttpHeader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.grizzly.http;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.attributes.AttributeHolder;
import org.glassfish.grizzly.attributes.AttributeStorage;
import org.glassfish.grizzly.http.util.Constants;
import org.glassfish.grizzly.http.util.ContentType;
import org.glassfish.grizzly.http.util.ContentType.SettableContentType;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HeaderValue;
import org.glassfish.grizzly.http.util.HttpUtils;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.utils.Charsets;

/**
 * {@link HttpPacket}, which represents HTTP message header. There are 2 subtypes of this class:
 * {@link HttpRequestPacket} and {@link HttpResponsePacket}.
 *
 * @see HttpRequestPacket
 * @see HttpResponsePacket
 *
 * @author Alexey Stashok
 */
public abstract class HttpHeader extends HttpPacket implements MimeHeadersPacket, AttributeStorage {

    private final static byte[] CHUNKED_ENCODING_BYTES = Constants.CHUNKED_ENCODING.getBytes(Charsets.ASCII_CHARSET);

    protected boolean isCommitted;
    protected final MimeHeaders headers;

    protected final DataChunk protocolC = DataChunk.newInstance();
    protected Protocol parsedProtocol;

    protected boolean isChunked;
    private final byte[] tmpContentLengthBuffer = new byte[20];
    private final byte[] tmpHeaderEncodingBuffer = new byte[512];

    protected long contentLength = -1;

    protected final SettableContentType contentType = ContentType.newSettableContentType();

    protected boolean isExpectContent = true;

    protected boolean isSkipRemainder;

    /**
     * true if HTTP message payload is broken due to inappropriate Transfer-Encoding or Content-Encoding settings.
     */
    protected boolean isContentBroken;

    protected boolean secure;

    /**
     * true if parser has to ignore "Transfer-Encoding" and "Content-Encoding" headers and act as none of them were
     * specified.
     */
    private boolean isIgnoreContentModifiers;

    protected final DataChunk upgrade = DataChunk.newInstance();

    private TransferEncoding transferEncoding;
    private final List contentEncodings = new ArrayList<>(2);
    // true, if content encodings for this headers were chosen
    private boolean isContentEncodingsSelected;

    private final AttributeHolder attributes = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createUnsafeAttributeHolder();
    private AttributeHolder activeAttributes;

    Buffer headerBuffer;

    /**
     * Is chunking allowed to be used or not.
     */
    private boolean chunkingAllowed;

    public HttpHeader() {
        this(new MimeHeaders());
    }

    protected HttpHeader(final MimeHeaders headers) {
        this.headers = headers;
    }

    void setHeaderBuffer(final Buffer headerBuffer) {
        this.headerBuffer = headerBuffer;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AttributeHolder getAttributes() {
        if (activeAttributes == null) {
            activeAttributes = attributes;
        }

        return activeAttributes;
    }

    /**
     * Returns true, if the current HttpHeader represent HTTP request message, or false
     * otherwise.
     *
     * @return true, if the current HttpHeader represent HTTP request message, or false
     * otherwise.
     */
    public abstract boolean isRequest();

    /**
     * Returns true.
     * 
     * @return true.
     */
    @Override
    public final boolean isHeader() {
        return true;
    }

    /**
     * Returns this HttpHeader object.
     * 
     * @return this HttpHeader object.
     */
    @Override
    public HttpHeader getHttpHeader() {
        return this;
    }

    public abstract ProcessingState getProcessingState();

    /**
     * @return the parsing state of this HTTP header, or null if the message is complete or shouldn't be parsed at
     * all
     */
    protected HttpPacketParsing getParsingState() {
        return null;
    }

    protected void addContentEncoding(ContentEncoding contentEncoding) {
        contentEncodings.add(contentEncoding);
    }

    protected List getContentEncodings(final boolean isModifiable) {
        if (isModifiable) {
            return contentEncodings;
        } else {
            return Collections.unmodifiableList(contentEncodings);
        }
    }

    public List getContentEncodings() {
        return getContentEncodings(false);
    }

    protected final boolean isContentEncodingsSelected() {
        return isContentEncodingsSelected;
    }

    protected final void setContentEncodingsSelected(final boolean isContentEncodingsSelected) {
        this.isContentEncodingsSelected = isContentEncodingsSelected;
    }

    /**
     * Get the {@link TransferEncoding}, responsible for the parsing/serialization of the HTTP message content
     *
     * @return the {@link TransferEncoding}, responsible for the parsing/serialization of the HTTP message content
     */
    public TransferEncoding getTransferEncoding() {
        return transferEncoding;
    }

    /**
     * Set the {@link TransferEncoding}, responsible for the parsing/serialization of the HTTP message content.
     *
     * @param transferEncoding the {@link TransferEncoding}, responsible for the parsing/serialization of the HTTP message
     * content.
     */
    protected void setTransferEncoding(TransferEncoding transferEncoding) {
        this.transferEncoding = transferEncoding;
    }

    /**
     * Returns true, if this {@link HttpPacket} content will be transferred in chunking mode, or false if
     * case of fixed-length message.
     *
     * @return true, if this {@link HttpPacket} content will be transferred in chunking mode, or false if
     * case of fixed-length message.
     */
    public boolean isChunked() {
        return isChunked;
    }

    /**
     * Set true, if this {@link HttpPacket} content will be transferred in chunking mode, or false if case
     * of fixed-length message.
     *
     * NOTE: If the protocol version of this header is 1.0 or older, chunking will be disabled regardless of the value
     * passed.
     *
     * @param isChunked true, if this {@link HttpPacket} content will be transferred in chunking mode, or
     * false if case of fixed-length message.
     */
    public void setChunked(boolean isChunked) {
        if (getProtocol().compareTo(Protocol.HTTP_1_1) >= 0) { // HTTP/1.1 and later
            this.isChunked = isChunked;
            if (isChunked) {
                headers.removeHeader(Header.ContentLength);
            }
        } else {
            this.isChunked = false;
        }
    }

    /**
     * Returns true, if HTTP message, represented by this header still expects additional content basing either on
     * content-length or chunking information. false is returned if content no additional content data is expected.
     * Note: this method could be used only when we parse the HTTP message
     *
     * @return true, if HTTP message, represented by this header still expects additional content basing either on
     * content-length or chunking information. false is returned if content no additional content data is expected.
     */
    public boolean isExpectContent() {
        return isExpectContent;
    }

    public void setExpectContent(boolean isExpectContent) {
        this.isExpectContent = isExpectContent;
    }

    /**
     * Returns true, if either application or HTTP core part is not interested in parsing the rest of this HTTP
     * message content and waits for the next HTTP message to come on this {@link org.glassfish.grizzly.Connection}.
     * Otherwise returns false.
     *
     * @return true, if either application or HTTP core part is not interested in parsing the rest of this HTTP
     * message content and waits for the next HTTP message to come on this {@link org.glassfish.grizzly.Connection}.
     * Otherwise returns false.
     */
    public boolean isSkipRemainder() {
        return isSkipRemainder;
    }

    /**
     * Set flag, which is set to true, means that we're not interested in parsing the rest of this HTTP message
     * content and wait for the next HTTP message to come on this {@link org.glassfish.grizzly.Connection}.
     *
     * @param isSkipRemainder true means that we're not interested in parsing the rest of this HTTP message content
     * and wait for the next HTTP message to come on this {@link org.glassfish.grizzly.Connection}.
     */
    public void setSkipRemainder(boolean isSkipRemainder) {
        this.isSkipRemainder = isSkipRemainder;
    }

    /**
     * Returns true, if HTTP packet payload was detected as broken due to unexpected error occurred during
     * Transfer-Encoding or Content-Encoding processing. Otherwise returns false.
     *
     * @return true, if HTTP packet payload was detected as broken due to unexpected error occurred during
     * Transfer-Encoding or Content-Encoding processing. Otherwise returns false.
     */
    public boolean isContentBroken() {
        return isContentBroken;
    }

    /**
     * Set flag, which is set to true, means that HTTP packet payload was detected as broken due to unexpected
     * error occurred during Transfer-Encoding or Content-Encoding processing.
     *
     * @param isBroken true, means that HTTP packet payload was detected as broken due to unexpected error occurred
     * during Transfer-Encoding or Content-Encoding processing.
     */
    public void setContentBroken(final boolean isBroken) {
        this.isContentBroken = isBroken;
    }

    /**
     * @return the "Upgrade" header value.
     */
    public final String getUpgrade() {
        return upgrade.toString();
    }

    /**
     * @return the "Upgrade" header value.
     */
    public DataChunk getUpgradeDC() {
        return upgrade;
    }

    /**
     * Sets the "Upgrade" header value
     * 
     * @param upgrade
     */
    public final void setUpgrade(final String upgrade) {
        this.upgrade.setString(upgrade);
    }

    /**
     * @return true if this header represents an HTTP upgrade, otherwise false.
     *
     * @since 2.3.29
     */
    public boolean isUpgrade() {
        return !getUpgradeDC().isNull();
    }

    /**
     * Propagate the "Upgrade" value to headers.
     */
    protected void makeUpgradeHeader() {
        if (!upgrade.isNull()) {
            headers.setValue(Header.Upgrade).set(upgrade);
        }
    }

    /**
     * @return true if parser has to ignore "Transfer-Encoding" and "Content-Encoding" headers and act as none of
     * them were specified.
     */
    public boolean isIgnoreContentModifiers() {
        return isIgnoreContentModifiers;
    }

    /**
     * Set true if parser has to ignore "Transfer-Encoding" and "Content-Encoding" headers and act as none of them
     * were specified.
     *
     * @param isIgnoreContentModifiers
     */
    public void setIgnoreContentModifiers(boolean isIgnoreContentModifiers) {
        this.isIgnoreContentModifiers = isIgnoreContentModifiers;
    }

    /**
     * Makes sure content-length header is present.
     *
     * @param defaultLength default content-length value.
     */
    protected void makeContentLengthHeader(final long defaultLength) {
        if (contentLength != -1) {
            final int start = HttpUtils.longToBuffer(contentLength, tmpContentLengthBuffer);
            headers.setValue(Header.ContentLength).setBytes(tmpContentLengthBuffer, start, tmpContentLengthBuffer.length);
        } else if (defaultLength != -1) {
            final int start = HttpUtils.longToBuffer(defaultLength, tmpContentLengthBuffer);
            final int idx = headers.indexOf(Header.ContentLength, 0);
            if (idx == -1) {
                headers.addValue(Header.ContentLength).setBytes(tmpContentLengthBuffer, start, tmpContentLengthBuffer.length);
            } else if (headers.getValue(idx).isNull()) {
                headers.getValue(idx).setBytes(tmpContentLengthBuffer, start, tmpContentLengthBuffer.length);
            }
        }
    }

    /**
     * Get the content-length of this {@link HttpPacket}. Applicable only in case of fixed-length HTTP message.
     *
     * @return the content-length of this {@link HttpPacket}. Applicable only in case of fixed-length HTTP message.
     */
    public long getContentLength() {
        return contentLength;
    }

    /**
     * Set the length of this HTTP message.
     *
     * @param len the length of this HTTP message.
     */
    public void setContentLength(final int len) {
        setContentLengthLong(len);
    }

    /**
     * Set the content-length of this {@link HttpPacket}. Applicable only in case of fixed-length HTTP message.
     *
     * @param contentLength the content-length of this {@link HttpPacket}. Applicable only in case of fixed-length HTTP
     * message.
     */
    public void setContentLengthLong(final long contentLength) {
        this.contentLength = contentLength;
        final boolean negativeLength = contentLength < 0;
        if (negativeLength) {
            headers.removeHeader(Header.ContentLength);
        }
    }

    /**
     * Is this HttpHeader written? true, if this HttpHeader has been already serialized, and only
     * {@link HttpContent} messages might be serialized for this {@link HttpPacket}.
     *
     * @return true, if this HttpHeader has been already serialized, and only {@link HttpContent} messages
     * might be serialized for this {@link HttpPacket}.
     */
    public boolean isCommitted() {
        return isCommitted;
    }

    /**
     * Is this HttpHeader written? true, if this HttpHeader has been already serialized, and only
     * {@link HttpContent} messages might be serialized for this {@link HttpPacket}.
     *
     * @param isCommitted true, if this HttpHeader has been already serialized, and only
     * {@link HttpContent} messages might be serialized for this {@link HttpPacket}.
     */
    public void setCommitted(final boolean isCommitted) {
        this.isCommitted = isCommitted;
    }

    // -------------------- encoding/type --------------------

    /**
     * Makes sure transfer-encoding header is present.
     *
     * @param defaultValue default transfer-encoding value.
     */
    protected void makeTransferEncodingHeader(final String defaultValue) {
        final int idx = headers.indexOf(Header.TransferEncoding, 0);

        if (idx == -1) {
            headers.addValue(Header.TransferEncoding).setBytes(CHUNKED_ENCODING_BYTES);
        }
    }

    /**
     * Obtain content-encoding value and mark it as serialized.
     *
     * @param value container for the content-type value.
     */
    protected void extractContentEncoding(final DataChunk value) {
        final int idx = headers.indexOf(Header.ContentEncoding, 0);

        if (idx != -1) {
            headers.setSerialized(idx, true);
            value.set(headers.getValue(idx));
        }
    }

    /**
     * @return the character encoding of this HTTP message.
     */
    public String getCharacterEncoding() {
        return contentType.getCharacterEncoding();
    }

    /**
     * Set the character encoding of this HTTP message.
     *
     * @param charset the encoding.
     */
    public void setCharacterEncoding(final String charset) {

        if (isCommitted()) {
            return;
        }

        contentType.setCharacterEncoding(charset);
    }

    /**
     * Return true if chunking is allowed for this header.
     *
     * @return true if chunking is allowed for this header.
     * @since 3.0
     */
    public boolean isChunkingAllowed() {
        return chunkingAllowed;
    }

    /**
     * Indicate whether or not chunking may be used by this header.
     *
     * @param chunkingAllowed true if chunked transfer-encoding is allowed, otherwise returns
     * false.
     * @since 3.0
     */
    public void setChunkingAllowed(boolean chunkingAllowed) {
        this.chunkingAllowed = chunkingAllowed;
    }

    /**
     * @return true if a content type has been set.
     */
    public boolean isContentTypeSet() {
        return contentType.isMimeTypeSet() || headers.getValue(Header.ContentType) != null;
    }

    /**
     * @return the content type of this HTTP message.
     */
    public String getContentType() {
        return contentType.get();
    }

    /**
     * Sets the content type.
     *
     * This method must preserve any charset that may already have been set via a call to request/response.setContentType(),
     * request/response.setLocale(), or request/response.setCharacterEncoding().
     *
     * @param contentType the content type
     */
    public void setContentType(final String contentType) {
        this.contentType.set(contentType);
    }

    /**
     * Sets the content type.
     *
     * This method must preserve any charset that may already have been set via a call to request/response.setContentType(),
     * request/response.setLocale(), or request/response.setCharacterEncoding().
     *
     * This method copies the passed contentType state into this ContentType.
     *
     * @param contentType the content type
     */
    public void setContentType(final ContentType contentType) {
        this.contentType.set(contentType);
    }

    /**
     * @return {@link ContentType} holder
     */
    protected ContentType getContentTypeHolder() {
        return contentType;
    }

    // -------------------- Headers --------------------

    /**
     * {@inheritDoc}
     */
    @Override
    public MimeHeaders getHeaders() {
        return headers;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getHeader(final String name) {
        if (name == null || name.isEmpty()) {
            return null;
        }

        String result = handleGetSpecialHeader(name);
        return result != null ? result : headers.getHeader(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getHeader(final Header header) {
        if (header == null) {
            return null;
        }
        String result = handleGetSpecialHeader(header);
        return result != null ? result : headers.getHeader(header);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setHeader(final String name, final String value) {
        if (name == null || value == null || name.isEmpty()) {
            return;
        }
        if (handleSetSpecialHeaders(name, value)) {
            return;
        }

        headers.setValue(name).setString(value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setHeader(final String name, final HeaderValue value) {
        if (name == null || value == null || name.isEmpty() || !value.isSet()) {
            return;
        }
        if (handleSetSpecialHeaders(name, value)) {
            return;
        }

        value.serializeToDataChunk(headers.setValue(name));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setHeader(final Header header, final String value) {
        if (header == null || value == null) {
            return;
        }
        if (handleSetSpecialHeaders(header, value)) {
            return;
        }

        headers.setValue(header).setString(value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setHeader(final Header header, final HeaderValue value) {
        if (header == null || value == null || !value.isSet()) {
            return;
        }
        if (handleSetSpecialHeaders(header, value)) {
            return;
        }

        value.serializeToDataChunk(headers.setValue(header));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addHeader(final String name, final String value) {
        if (name == null || value == null || name.isEmpty()) {
            return;
        }
        if (handleSetSpecialHeaders(name, value)) {
            return;
        }

        headers.addValue(name).setString(value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addHeader(final String name, final HeaderValue value) {
        if (name == null || value == null || name.isEmpty() || !value.isSet()) {
            return;
        }
        if (handleSetSpecialHeaders(name, value)) {
            return;
        }

        value.serializeToDataChunk(headers.setValue(name));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addHeader(final Header header, final String value) {
        if (header == null || value == null) {
            return;
        }
        if (handleSetSpecialHeaders(header, value)) {
            return;
        }

        headers.addValue(header).setString(value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addHeader(final Header header, final HeaderValue value) {
        if (header == null || value == null || !value.isSet()) {
            return;
        }
        if (handleSetSpecialHeaders(header, value)) {
            return;
        }

        value.serializeToDataChunk(headers.setValue(header));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsHeader(final String name) {
        if (name == null || name.isEmpty()) {
            return false;
        }
        final String result = handleGetSpecialHeader(name);

        return result != null || headers.getHeader(name) != null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsHeader(final Header header) {
        if (header == null) {
            return false;
        }
        final String result = handleGetSpecialHeader(header);
        return result != null || headers.getHeader(header) != null;
    }

    /**
     * Get the HTTP message protocol version as {@link DataChunk} (avoiding creation of a String object). The result format
     * is "HTTP/1.x".
     *
     * @return the HTTP message protocol version as {@link DataChunk} (avoiding creation of a String object). The result
     * format is "HTTP/1.x".
     */
    public DataChunk getProtocolDC() {
        // potentially the value might be changed, so we need to parse it again
        parsedProtocol = null;
        return protocolC;
    }

    /**
     * Get the HTTP message protocol version. The result format is "HTTP/1.x".
     *
     * @return the HTTP message protocol version. The result format is "HTTP/1.x".
     */
    public String getProtocolString() {
        if (parsedProtocol == null) {
            return getProtocolDC().toString();
        }

        return parsedProtocol.getProtocolString();
    }

    /**
     * Get HTTP protocol version.
     * 
     * @return {@link Protocol}.
     */
    public Protocol getProtocol() {
        if (parsedProtocol != null) {
            return parsedProtocol;
        }

        parsedProtocol = Protocol.valueOf(protocolC);
        return parsedProtocol;
    }

    /**
     * Set the HTTP message protocol version.
     * 
     * @param protocol {@link Protocol}
     */
    public void setProtocol(Protocol protocol) {
        parsedProtocol = protocol;
    }

    /**
     * @return true if this HTTP message is being transmitted in a secure fashion, otherwise returns
     * false.
     */
    public boolean isSecure() {
        return secure;
    }

    /**
     * Sets the secure status of this HTTP message.
     *
     * @param secure true if secure, otherwise false.
     */
    public void setSecure(boolean secure) {
        this.secure = secure;
    }

    /**
     * Get the HTTP message content builder.
     *
     * @return {@link HttpContent.Builder}.
     */
    public final HttpContent.Builder httpContentBuilder() {
        return HttpContent.builder(this);
    }

    /**
     * Get the HTTP message trailer-chunk builder.
     *
     * @return {@link HttpTrailer.Builder}.
     */
    public HttpTrailer.Builder httpTrailerBuilder() {
        return HttpTrailer.builder(this);
    }

    /**
     * Reset the internal state.
     */
    protected void reset() {
        isContentEncodingsSelected = false;
        secure = false;
        isSkipRemainder = false;
        isContentBroken = false;
        if (activeAttributes != null) {
            activeAttributes.recycle();
            activeAttributes = null;
        }
        protocolC.recycle();
        parsedProtocol = null;
        contentEncodings.clear();
        headers.clear();
        isCommitted = false;
        isChunked = false;
        contentLength = -1;
        contentType.reset();
        chunkingAllowed = false;
        transferEncoding = null;
        isExpectContent = true;
        upgrade.recycle();
        isIgnoreContentModifiers = false;
        if (headerBuffer != null) {
            headerBuffer.dispose();
            headerBuffer = null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void recycle() {
        reset();
    }

    private final String handleGetSpecialHeader(final String name) {
        return isSpecialHeader(name) ? getValueBasedOnHeader(name) : null;
    }

    private final String handleGetSpecialHeader(final Header header) {
        return isSpecialHeader(header.toString()) ? getValueBasedOnHeader(header) : null;
    }

    private final boolean handleSetSpecialHeaders(final String name, final String value) {
        return isSpecialHeaderSet(name) && setValueBasedOnHeader(name, value);
    }

    private final boolean handleSetSpecialHeaders(final String name, final HeaderValue value) {
        return isSpecialHeaderSet(name) && setValueBasedOnHeader(name, value.get());
    }

    private final boolean handleSetSpecialHeaders(final Header header, final String value) {
        return isSpecialHeaderSet(header.toString()) && setValueBasedOnHeader(header, value);
    }

    private final boolean handleSetSpecialHeaders(final Header header, final HeaderValue value) {
        return isSpecialHeaderSet(header.toString()) && setValueBasedOnHeader(header, value.get());
    }

    private static boolean isSpecialHeader(final String name) {
        return isSpecialHeader(name.charAt(0));
    }

    private static boolean isSpecialHeaderSet(final String name) {
        final char c = name.charAt(0);
        return isSpecialHeader(c) || c == 'T' || c == 't';
    }

    private static boolean isSpecialHeader(final char c) {
        return c == 'C' || c == 'c' || c == 'U' || c == 'u';
    }

    public byte[] getTempHeaderEncodingBuffer() {
        return tmpHeaderEncodingBuffer;
    }

    // --------------------------------------------------------- Private Methods

    private String getValueBasedOnHeader(final Header header) {
        if (Header.ContentType.equals(header)) {
            final String value = getContentType();
            if (value != null) {
                return value;
            }
        } else if (Header.ContentLength.equals(header)) {
            final long value = getContentLength();
            if (value >= 0) {
                return Long.toString(value);
            }
        } else if (Header.Upgrade.equals(header)) {
            return getUpgrade();
        }
        return null;
    }

    private String getValueBasedOnHeader(final String name) {
        if (Header.ContentType.toString().equalsIgnoreCase(name)) {
            final String value = getContentType();
            if (value != null) {
                return value;
            }
        } else if (Header.ContentLength.toString().equalsIgnoreCase(name)) {
            final long value = getContentLength();
            if (value >= 0) {
                return Long.toString(value);
            }
        } else if (Header.Upgrade.toString().equalsIgnoreCase(name)) {
            return getUpgrade();
        }
        return null;
    }

    /**
     * Set internal fields for special header names. Called from set/addHeader. Return true if the header is special, no
     * need to set the header.
     */
    private boolean setValueBasedOnHeader(final String name, final String value) {
        if (Header.ContentType.toString().equalsIgnoreCase(name)) {
            setContentType(value);
            return true;
        } else if (Header.ContentLength.toString().equalsIgnoreCase(name)) {
            headers.removeHeader(Header.TransferEncoding);
            setChunked(false);
            return setContentLenth(value);
        } else if (Header.Upgrade.toString().equalsIgnoreCase(name)) {
            setUpgrade(value);
        } else if (Header.TransferEncoding.toString().equalsIgnoreCase(name)) {
            if ("chunked".equalsIgnoreCase(value)) {
                setContentLengthLong(-1);
                setChunked(true);
            }
            return true;
        }
        // if (name.equalsIgnoreCase("Content-Language")) {
        // // TODO XXX XXX Need to construct Locale or something else
        // }
        return false;
    }

    /**
     * Set internal fields for special header names. Called from set/addHeader. Return true if the header is special, no
     * need to set the header.
     */
    private boolean setValueBasedOnHeader(final Header header, final String value) {
        if (Header.ContentType.equals(header)) {
            setContentType(value);
            return true;
        } else if (Header.ContentLength.equals(header)) {
            headers.removeHeader(Header.TransferEncoding);
            setChunked(false);
            return setContentLenth(value);
        } else if (Header.Upgrade.equals(header)) {
            setUpgrade(value);
        } else if (Header.TransferEncoding.equals(header)) {
            if ("chunked".equalsIgnoreCase(value)) {
                setContentLengthLong(-1);
                setChunked(true);
            }
            return true;
        }
        // if (name.equalsIgnoreCase("Content-Language")) {
        // // TODO XXX XXX Need to construct Locale or something else
        // }
        return false;
    }

    private boolean setContentLenth(String value) {
        try {
            final long cLL = Long.parseLong(value);
            setContentLengthLong(cLL);
            return true;
        } catch (NumberFormatException ex) {
            // Do nothing - the spec doesn't have any "throws"
            // and the user might know what he's doing
            return false;
        }
    }

    /**
     * Flush internal fields for special header names to the headers map.
     */
    protected void flushSpecialHeaders() {
        if (contentLength >= 0) {
            headers.setValue(Header.ContentLength).setString(String.valueOf(contentLength));
        }

        final String ct = getContentType();
        if (ct != null) {
            headers.setValue(Header.ContentType).setString(String.valueOf(ct));
        }

        if (!upgrade.isNull()) {
            headers.setValue(Header.Upgrade).setString(upgrade.toString());
        }
    }

    /**
     * HttpHeader message builder.
     */
    public static abstract class Builder {

        protected Protocol protocol;
        protected String protocolString;
        protected Boolean chunked;
        protected Long contentLength;
        protected String contentType;
        protected String upgrade;
        protected MimeHeaders mimeHeaders;

        /**
         * Set the HTTP message protocol version.
         * 
         * @param protocol {@link Protocol}
         */
        @SuppressWarnings({ "unchecked" })
        public final T protocol(Protocol protocol) {
            this.protocol = protocol;
            protocolString = null;
            return (T) this;
        }

        /**
         * Set the HTTP message protocol version.
         * 
         * @param protocolString protocol version in format "HTTP/1.x".
         */
        @SuppressWarnings({ "unchecked" })
        public final T protocol(String protocolString) {
            this.protocolString = protocolString;
            protocol = null;
            return (T) this;
        }

        /**
         * Set true, if this {@link HttpPacket} content will be transferred in chunking mode, or false if case
         * of fixed-length message.
         *
         * @param chunked true, if this {@link HttpPacket} content will be transferred in chunking mode, or
         * false if case of fixed-length message.
         */
        @SuppressWarnings({ "unchecked" })
        public final T chunked(boolean chunked) {
            this.chunked = chunked;
            contentLength = null;
            return (T) this;
        }

        /**
         * Set the content-length of this {@link HttpPacket}. Applicable only in case of fixed-length HTTP message.
         *
         * @param contentLength the content-length of this {@link HttpPacket}. Applicable only in case of fixed-length HTTP
         * message.
         */
        @SuppressWarnings({ "unchecked" })
        public final T contentLength(long contentLength) {
            this.contentLength = contentLength;
            chunked = null;
            return (T) this;
        }

        /**
         * Set the content-type of this {@link HttpPacket}.
         *
         * @param contentType the content-type of this {@link HttpPacket}.
         */
        @SuppressWarnings({ "unchecked" })
        public final T contentType(String contentType) {
            this.contentType = contentType;
            return (T) this;
        }

        /**
         * Set the HTTP upgrade type.
         *
         * @param upgrade the type of upgrade.
         */
        @SuppressWarnings({ "unchecked" })
        public final T upgrade(String upgrade) {
            this.upgrade = upgrade;
            return (T) this;
        }

        /**
         * Add the HTTP mime header.
         *
         * @param name the mime header name.
         * @param value the mime header value.
         */
        @SuppressWarnings({ "unchecked" })
        public final T header(String name, String value) {
            if (mimeHeaders == null) {
                mimeHeaders = new MimeHeaders();
            }
            if (!handleSpecialHeaderAdd(Header.find(name), value)) {
                mimeHeaders.addValue(name).setString(value);
            }
            return (T) this;
        }

        /**
         * Remove the specified name from this builder. This method is only useful if using the same builder to create multiple
         * objects.
         *
         * @param name the mime header name.
         * @return this
         */
        @SuppressWarnings({ "unchecked" })
        public final T removeHeader(String name) {
            if (mimeHeaders != null) {
                mimeHeaders.removeHeader(name);
                handleSpecialHeaderRemove(Header.find(name));
            }
            return (T) this;
        }

        /**
         * Add the HTTP mime header.
         *
         * @param header the mime {@link Header}.
         * @param value the mime header value.
         */
        @SuppressWarnings({ "unchecked" })
        public final T header(Header header, String value) {
            if (mimeHeaders == null) {
                mimeHeaders = new MimeHeaders();
            }
            if (!handleSpecialHeaderAdd(header, value)) {
                mimeHeaders.addValue(header).setString(value);
            }

            return (T) this;
        }

        /**
         * Remove the specified header from this builder. This method is only useful if using the same builder to create
         * multiple objects.
         *
         * @param header the mime {@link Header}.
         * @return this
         */
        @SuppressWarnings({ "unchecked" })
        public final T removeHeader(Header header) {
            if (mimeHeaders != null) {
                mimeHeaders.removeHeader(header);
                handleSpecialHeaderRemove(header);
            }
            return (T) this;
        }

        /**
         * Sets the maximum number of headers allowed.
         */
        @SuppressWarnings({ "unchecked" })
        public final T maxNumHeaders(int maxHeaders) {
            if (mimeHeaders == null) {
                mimeHeaders = new MimeHeaders();
            }
            mimeHeaders.setMaxNumHeaders(maxHeaders);
            return (T) this;
        }

        public HttpHeader build() {
            HttpHeader httpHeader = create();
            if (protocol != null) {
                httpHeader.setProtocol(protocol);
            }
            if (protocolString != null) {
                httpHeader.protocolC.setString(protocolString);
            }
            if (chunked != null) {
                httpHeader.setChunked(chunked);
            }
            if (contentLength != null) {
                httpHeader.setContentLengthLong(contentLength);
            }
            if (contentType != null) {
                httpHeader.setContentType(contentType);
            }
            if (upgrade != null) {
                httpHeader.setUpgrade(upgrade);
            }
            if (mimeHeaders != null && mimeHeaders.size() > 0) {
                httpHeader.getHeaders().copyFrom(mimeHeaders);
            }

            return httpHeader;
        }

        public void reset() {
            protocol = null;
            protocolString = null;
            chunked = null;
            contentLength = null;
            contentType = null;
            upgrade = null;
            mimeHeaders.recycle();
        }

        protected abstract HttpHeader create();

        private boolean handleSpecialHeaderAdd(final Header header, final String value) {
            if (Header.ContentLength.equals(header)) {
                contentLength = Long.parseLong(value);
                return true;
            } else if (Header.Upgrade.equals(header)) {
                upgrade = value;
                return true;
            }
            return false;
        }

        private void handleSpecialHeaderRemove(final Header header) {
            if (Header.ContentLength.equals(header)) {
                contentLength = null;
            } else if (Header.Upgrade.equals(header)) {
                upgrade = null;
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy