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

com.pdd.pop.ext.glassfish.grizzly.http.HttpHeader Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.pdd.pop.ext.glassfish.grizzly.http;

import com.pdd.pop.ext.glassfish.grizzly.http.util.ContentType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.pdd.pop.ext.glassfish.grizzly.Buffer;
import com.pdd.pop.ext.glassfish.grizzly.Grizzly;
import com.pdd.pop.ext.glassfish.grizzly.attributes.AttributeHolder;
import com.pdd.pop.ext.glassfish.grizzly.attributes.AttributeStorage;
import com.pdd.pop.ext.glassfish.grizzly.http.util.Constants;
import com.pdd.pop.ext.glassfish.grizzly.http.util.ContentType.SettableContentType;
import com.pdd.pop.ext.glassfish.grizzly.http.util.DataChunk;
import com.pdd.pop.ext.glassfish.grizzly.http.util.Header;
import com.pdd.pop.ext.glassfish.grizzly.http.util.HeaderValue;
import com.pdd.pop.ext.glassfish.grizzly.http.util.HttpUtils;
import com.pdd.pop.ext.glassfish.grizzly.http.util.MimeHeaders;
import com.pdd.pop.ext.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(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 com.pdd.pop.ext.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 com.pdd.pop.ext.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 com.pdd.pop.ext.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 com.pdd.pop.ext.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);
    }

    /**
     * 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;
//                || (!upgrade.isNull() &&
//                !upgrade.startsWith("h2c", 0)); // don't ignore content modifiers for HTTP2 upgrade
    }

    /**
     * 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);
        setChunked(negativeLength);
        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) {
            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) {
            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 || !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) {
            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 || !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) {
            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();
    }

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

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

    protected final boolean handleSetSpecialHeaders(final String name, final String value) {
        return isSpecialHeader(name) && setValueBasedOnHeader(name, value);
    }

    protected final boolean handleSetSpecialHeaders(final String name, final HeaderValue value) {
        return isSpecialHeader(name) && setValueBasedOnHeader(name, value.get());
    }
    
    protected final boolean handleSetSpecialHeaders(final Header header, final String value) {
        return isSpecialHeader(header.toString()) && setValueBasedOnHeader(header, value);
    }

    protected final boolean handleSetSpecialHeaders(final Header header, final HeaderValue value) {
        return isSpecialHeader(header.toString()) && setValueBasedOnHeader(header, value.get());
    }
    
    protected static boolean isSpecialHeader(final String name) {
        final char c = name.charAt(0);
        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)) {
            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;
            }
        } else if (Header.Upgrade.toString().equalsIgnoreCase(name)) {
            setUpgrade(value);
        }
        //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)) {
            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;
            }
        } else if (Header.Upgrade.equals(header)) {
            setUpgrade(value);
        }
        //if (name.equalsIgnoreCase("Content-Language")) {
        //    // TODO XXX XXX Need to construct Locale or something else
        //}
        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();
            }
            mimeHeaders.addValue(name).setString(value);
            handleSpecialHeaderAdd(name, 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 header.
         * @return this
         */
        @SuppressWarnings({"unchecked"})
        public final T removeHeader(String header) {
            if (mimeHeaders != null) {
                mimeHeaders.removeHeader(header);
                handleSpecialHeaderRemove(header);
            }
            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();
            }
            mimeHeaders.addValue(header).setString(value);
            handleSpecialHeaderAdd(header.toString(), 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.toString());
            }
            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 void handleSpecialHeaderAdd(final String name,
                                            final String value) {
            final char c = name.charAt(0);
            boolean isC = (c == 'c' || c == 'C');
            if (isC && Header.ContentLength.toString().equals(name)) {
                contentLength = Long.parseLong(value);
                return;
            }
            boolean isU = (c == 'u' || c == 'U');
            if (isU && Header.Upgrade.toString().equals(name)) {
                upgrade = value;
            }
        }

        private void handleSpecialHeaderRemove(final String name) {
            final char c = name.charAt(0);
            boolean isC = (c == 'c' || c == 'C');
            if (isC && Header.ContentLength.toString().equals(name)) {
                contentLength = null;
                return;
            }
            boolean isU = (c == 'u' || c == 'U');
            if (isU && Header.Upgrade.toString().equals(name)) {
                upgrade = null;
            }
        }
        
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy