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

org.glassfish.grizzly.http2.DecoderUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2014, 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.http2;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.HttpHeader;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.Protocol;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.http2.HeaderDecodingException.ErrorType;
import org.glassfish.grizzly.http2.frames.ErrorCode;
import org.glassfish.grizzly.http2.hpack.DecodingCallback;

/**
 * Http2Frames -> HTTP Packet decoder utils.
 *
 * @author Grizzly team
 */
class DecoderUtils extends EncoderDecoderUtilsBase {
    private final static Logger LOGGER = Grizzly.logger(DecoderUtils.class);

    private static final String INVALID_CHARACTER_MESSAGE = "Invalid character 0x%02x at index '%s' found in header %s [%s: %s]";

    static void decodeRequestHeaders(final Http2Session http2Session, final HttpRequestPacket request, final Map capture)
            throws IOException, HeaderDecodingException {

        final Set serviceHeaders = new HashSet<>();
        final AtomicBoolean noMoreServiceHeaders = new AtomicBoolean();
        try {
            http2Session.getHeadersDecoder().decode(new DecodingCallback() {

                @Override
                public void onDecoded(CharSequence name, CharSequence value) {
                    if (capture != null) {
                        capture.put(name.toString(), value.toString());
                    }
                    for (int i = 0, len = name.length(); i < len; i++) {
                        if (Character.isUpperCase(name.charAt(i))) {
                            throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM);
                        }
                    }
                    if (name.charAt(0) == ':') {
                        if (noMoreServiceHeaders.get()) {
                            throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM);
                        }
                        processServiceRequestHeader(request, serviceHeaders, name.toString(), value.toString());
                    } else {
                        noMoreServiceHeaders.compareAndSet(false, true);
                        processNormalHeader(request, name.toString(), value.toString());
                    }
                }

            });
            if (serviceHeaders.size() != 3) {
                throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM);
            }
        } catch (RuntimeException re) {
            if (re instanceof HeaderDecodingException) {
                throw re;
            }
            throw new IOException(re);
        } finally {
            request.setProtocol(Protocol.HTTP_2_0);
            request.getResponse().setProtocol(Protocol.HTTP_2_0);
        }
    }

    static void decodeResponseHeaders(final Http2Session http2Session, final HttpResponsePacket response, final Map capture)
            throws IOException {

        try {
            http2Session.getHeadersDecoder().decode(new DecodingCallback() {

                @Override
                public void onDecoded(final CharSequence name, final CharSequence value) {
                    if (capture != null) {
                        capture.put(name.toString(), value.toString());
                    }
                    if (name.charAt(0) == ':') {
                        processServiceResponseHeader(response, name.toString(), value.toString());
                    } else {
                        processNormalHeader(response, name.toString(), value.toString());
                    }
                }

            });
        } catch (RuntimeException re) {
            throw new IOException(re);
        } finally {
            response.setProtocol(Protocol.HTTP_2_0);
            response.getRequest().setProtocol(Protocol.HTTP_2_0);
        }

    }

    static void decodeTrailerHeaders(final Http2Session http2Session, final HttpHeader header, final Map capture) throws IOException {
        try {
            final MimeHeaders headers = header.getHeaders();
            http2Session.getHeadersDecoder().decode(new DecodingCallback() {

                @Override
                public void onDecoded(final CharSequence name, final CharSequence value) {
                    if (capture != null) {
                        capture.put(name.toString(), value.toString());
                    }
                    // TODO trailer validation
                    headers.addValue(name.toString()).setString(value.toString());
                }

            });
        } catch (RuntimeException re) {
            throw new IOException(re);
        }
    }

    private static void processServiceRequestHeader(final HttpRequestPacket request, final Set serviceHeaders, final String name, final String value) {

        final int valueLen = value.length();

        switch (name) {
        case PATH_HEADER: {
            if (!serviceHeaders.add(name)) {
                throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "Duplicate " + PATH_HEADER);
            }
            if (value.isEmpty()) {
                throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "Empty " + PATH_HEADER);
            }
            int questionIdx = value.indexOf('?');

            if (questionIdx == -1) {
                request.getRequestURIRef().init(value);
            } else {
                request.getRequestURIRef().init(value.substring(0, questionIdx));
                if (questionIdx < valueLen - 1) {
                    request.getQueryStringDC().setString(value.substring(questionIdx + 1));
                }
            }

            return;
        }
        case METHOD_HEADER: {
            if (!serviceHeaders.add(name)) {
                throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "Duplicate " + METHOD_HEADER);
            }
            request.getMethodDC().setString(value);
            return;
        }
        case SCHEMA_HEADER: {
            if (!serviceHeaders.add(name)) {
                throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "Duplicate " + SCHEMA_HEADER);
            }
            request.setSecure(valueLen == 5); // support http and https only
            return;
        }
        case AUTHORITY_HEADER: {
            request.getHeaders().setValue(Header.Host).setString(value);
            return;
        }
        }

        throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "Unknown service header: " + name);
    }

    private static void processServiceResponseHeader(final HttpResponsePacket response, final String name, final String value) {
        validateHeaderCharacters(name, value);
        final int valueLen = value.length();
        switch (name) {
        case STATUS_HEADER: {
            if (valueLen != 3) {
                throw new IllegalStateException("Unexpected status code: " + value);
            }

            response.setStatus(Integer.parseInt(value));
        }
        }

        LOGGER.log(Level.FINE, "Skipping unknown service header[{0}={1}", new Object[] { name, value });
    }

    private static void processNormalHeader(final HttpHeader httpHeader, final String name, final String value) {
        if (name.equals(Header.Host.getLowerCase())) {
            return;
        }
        final MimeHeaders mimeHeaders = httpHeader.getHeaders();

        final DataChunk valueChunk = mimeHeaders.addValue(name);

        validateHeaderCharacters(name, value);
        valueChunk.setString(value);
        finalizeKnownHeader(httpHeader, name, value);
    }

    private static void finalizeKnownHeader(final HttpHeader httpHeader, final String name, final String value) {

        switch (name) {
        case "content-length": {
            httpHeader.setContentLengthLong(Long.parseLong(value));
            return;
        }

        case "upgrade": {
            httpHeader.getUpgradeDC().setString(value);
            return;
        }

        case "expect": {
            ((Http2Request) httpHeader).requiresAcknowledgement(true);
        }

        case "connection": {
            throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "Invalid use of connection header.");
        }

        case "te": {
            if (!"trailers".equals(value)) {
                throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, "TE header only allowed a value of trailers.");
            }
        }
        }
    }

    private static void validateHeaderCharacters(final CharSequence name, final CharSequence value) {
        assert name != null;
        assert value != null;
        int idx = ensureRange(name);
        if (idx != -1) {
            final String msg = String.format(INVALID_CHARACTER_MESSAGE, (int) name.charAt(idx), idx, "name", name, value);
            throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, msg);
        }
        idx = ensureRange(value);
        if (idx != -1) {
            final String msg = String.format(INVALID_CHARACTER_MESSAGE, (int) name.charAt(idx), idx, "value", name, value);
            throw new HeaderDecodingException(ErrorCode.PROTOCOL_ERROR, ErrorType.STREAM, msg);
        }
    }

    private static int ensureRange(final CharSequence cs) {
        for (int i = 0, len = cs.length(); i < len; i++) {
            final char c = cs.charAt(i);
            if (c < 0x20 || c > 0xFF) {
                return i;
            }
        }
        return -1;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy