/*
 * Copyright (c) 2014, 2017 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.Map;

import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.util.Ascii;
import org.glassfish.grizzly.http.util.BufferChunk;
import org.glassfish.grizzly.http.util.ByteChunk;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.MimeHeaders;

import static org.glassfish.grizzly.http.util.DataChunk.Type.Buffer;
import static org.glassfish.grizzly.http.util.DataChunk.Type.Bytes;

import org.glassfish.grizzly.memory.Buffers;
import org.glassfish.grizzly.ssl.SSLUtils;
import org.glassfish.grizzly.utils.Charsets;

HTTP Packet -> HTTP/2 frames encoder utils.
Author:Grizzly team
/** * HTTP Packet -> HTTP/2 frames encoder utils. * * @author Grizzly team */
class EncoderUtils extends EncoderDecoderUtilsBase { private static final String HTTP = "http"; private static final String HTTPS = "https"; @SuppressWarnings("unchecked") static Buffer encodeResponseHeaders(final Http2Session http2Session, final HttpResponsePacket response, final Map<String,String> capture) throws IOException { assert http2Session.getDeflaterLock().isLocked(); final MimeHeaders headers = response.getHeaders(); headers.removeHeader(Header.Connection); headers.removeHeader(Header.KeepAlive); headers.removeHeader(Header.ProxyConnection); headers.removeHeader(Header.TransferEncoding); headers.removeHeader(Header.Upgrade); final HeadersEncoder encoder = http2Session.getHeadersEncoder(); // encoder.encodeHeader(Constants.STATUS_HEADER_BYTES, // response.getHttpStatus().getStatusBytes(), false); encoder.encodeHeader(STATUS_HEADER, String.valueOf(response.getHttpStatus().getStatusCode()), capture); encodeUserHeaders(headers, encoder, capture); return encoder.flushHeaders(); } @SuppressWarnings("unchecked") static Buffer encodeRequestHeaders( final Http2Session http2Session, final HttpRequestPacket request, final Map<String,String> capture) throws IOException { assert http2Session.getDeflaterLock().isLocked(); // ----------------- Parse URI scheme and path ---------------- // int schemeStart = -1; // int schemeLen = -1; // // final byte[] requestURI = // request.getRequestURI().getBytes(DEFAULT_HTTP_CHARACTER_ENCODING); // final int len = requestURI.length; // // final int nonSpaceIdx = skipSpaces(requestURI, 0, len, len); // final int idx = ByteChunk.indexOf(requestURI, nonSpaceIdx, len, '/'); // // if (idx > 0 && idx < len - 1 && // requestURI[idx - 1] == ':' && requestURI[idx + 1] == '/') { // schemeStart = nonSpaceIdx; // schemeLen = idx - schemeStart - 1; // } // // // final int pathStart = schemeStart == -1 ? // idx : // ByteChunk.indexOf(requestURI, idx + 2, len, '/'); // final int pathLen = len - pathStart; // // if (pathStart == -1) { // throw new IllegalStateException("Request URI path is not set"); // } int schemeStart = -1; int schemeLen = -1; final String requestURI = request.getRequestURI().trim(); final int len = requestURI.length(); final int idx = requestURI.indexOf('/'); if (idx > 0 && idx < len - 1 && requestURI.charAt(idx - 1) == ':' && requestURI.charAt(idx + 1) == '/') { schemeStart = 0; schemeLen = idx - 1; } final int pathStart = schemeStart == -1 ? idx : requestURI.indexOf('/', idx + 2); final int pathLen = len - pathStart; if (pathStart == -1) { throw new IllegalStateException("Request URI path is not set"); } // --------------------------------------------------------------- final MimeHeaders headers = request.getHeaders(); String hostHeader = headers.getHeader(Header.Host); if (hostHeader == null) { if (schemeStart == -1) { throw new IllegalStateException("Missing the Host header"); } hostHeader = requestURI.substring(schemeStart + schemeLen + 3, pathStart); } headers.removeHeader(Header.Connection); //headers.removeHeader(Header.Host); headers.removeHeader(Header.KeepAlive); headers.removeHeader(Header.ProxyConnection); headers.removeHeader(Header.TransferEncoding); headers.removeHeader(Header.Upgrade); final HeadersEncoder encoder = http2Session.getHeadersEncoder(); encoder.encodeHeader(METHOD_HEADER, request.getMethod().toString(), capture); if (schemeLen > 0) { encoder.encodeHeader(SCHEMA_HEADER, requestURI.substring(0, schemeLen), capture); } else { // guess encoder.encodeHeader(SCHEMA_HEADER, ((SSLUtils.getSSLEngine(http2Session.getConnection()) == null) ? HTTP : HTTPS), capture); } encoder.encodeHeader(AUTHORITY_HEADER, hostHeader, capture); String path = (pathLen == requestURI.length()) ? requestURI : requestURI.substring(pathStart, pathStart + pathLen); final DataChunk query = request.getQueryStringDC(); if (!query.isNull()) { path += '?' + query.toString(Charsets.UTF8_CHARSET); } encoder.encodeHeader(PATH_HEADER, path, capture); encodeUserHeaders(headers, encoder, capture); return encoder.flushHeaders(); } static Buffer encodeTrailerHeaders(final Http2Session http2Session, final MimeHeaders trailers, final Map<String,String> capture) { assert http2Session.getDeflaterLock().isLocked(); if (trailers == null || trailers.size() == 0) { return Buffers.EMPTY_BUFFER; } final HeadersEncoder encoder = http2Session.getHeadersEncoder(); for (final String name : trailers.names()) { encoder.encodeHeader(name, trailers.getHeader(name), capture); } return encoder.flushHeaders(); } @SuppressWarnings("unchecked") private static void encodeUserHeaders(final MimeHeaders headers, final HeadersEncoder encoder, final Map<String,String> capture) throws IOException { final int mimeHeadersCount = headers.size(); for (int i = 0; i < mimeHeadersCount; i++) { if (!headers.setSerialized(i, true)) { final String nameStr = nameToLowerCase(headers.getName(i)); final DataChunk value = headers.getValue(i); if (!value.isNull()) { encoder.encodeHeader(nameStr, value.toString(), capture); } } } } @SuppressWarnings("unused") private static byte[] nameToLowerCaseByteArray(final DataChunk name) { final int length = name.getLength(); final byte[] lowercase = new byte[length]; if (name.getType() == Bytes) { final ByteChunk byteChunk = name.getByteChunk(); final byte[] bytes = byteChunk.getBuffer(); final int offs = byteChunk.getStart(); for (int i = 0; i < length; i++) { lowercase[i] = (byte) Ascii.toLower(bytes[i + offs]); } } else if (name.getType() == Buffer) { final BufferChunk bufferChunk = name.getBufferChunk(); final Buffer buffer = bufferChunk.getBuffer(); final int offs = bufferChunk.getStart(); for (int i = 0; i < length; i++) { lowercase[i] = (byte) Ascii.toLower(buffer.get(i + offs)); } } else { final String s = name.toString(); for (int i = 0; i < length; i++) { lowercase[i] = (byte) Ascii.toLower(s.charAt(i)); } } return lowercase; } private static String nameToLowerCase(final DataChunk name) { final int length = name.getLength(); final StringBuilder sb = new StringBuilder(length); if (name.getType() == Bytes) { final ByteChunk byteChunk = name.getByteChunk(); final byte[] bytes = byteChunk.getBuffer(); final int offs = byteChunk.getStart(); for (int i = 0; i < length; i++) { sb.append((char) Ascii.toLower(bytes[i + offs])); } } else if (name.getType() == Buffer) { final BufferChunk bufferChunk = name.getBufferChunk(); final Buffer buffer = bufferChunk.getBuffer(); final int offs = bufferChunk.getStart(); for (int i = 0; i < length; i++) { sb.append((char) Ascii.toLower(buffer.get(i + offs))); } } else { final String s = name.toString(); for (int i = 0; i < length; i++) { sb.append((char) Ascii.toLower(s.charAt(i))); } } return sb.toString(); } @SuppressWarnings("unused") private static int valueToByteArray(final DataChunk value, final byte[] dstArray, int arrayOffs) { final int length = value.getLength(); if (value.getType() == Bytes) { final ByteChunk byteChunk = value.getByteChunk(); final byte[] bytes = byteChunk.getBuffer(); final int offs = byteChunk.getStart(); System.arraycopy(bytes, offs, dstArray, arrayOffs, length); } else if (value.getType() == Buffer) { final BufferChunk bufferChunk = value.getBufferChunk(); final Buffer buffer = bufferChunk.getBuffer(); final int offs = bufferChunk.getStart(); final int oldPos = buffer.position(); final int oldLim = buffer.limit(); Buffers.setPositionLimit(buffer, offs, offs + length); buffer.get(dstArray, arrayOffs, length); Buffers.setPositionLimit(buffer, oldPos, oldLim); } else { final String s = value.toString(); for (int i = 0; i < length; i++) { dstArray[arrayOffs + i] = (byte) s.charAt(i); } } return length; } @SuppressWarnings("unused") private static byte[] valueToByteArray(final DataChunk value) { final int length = value.getLength(); if (value.getType() == Bytes) { final ByteChunk byteChunk = value.getByteChunk(); final byte[] bytes = byteChunk.getBuffer(); final int offs = byteChunk.getStart(); if (bytes.length == length) { return bytes; } final byte[] dstArray = new byte[length]; System.arraycopy(bytes, offs, dstArray, 0, length); return dstArray; } else if (value.getType() == Buffer) { final BufferChunk bufferChunk = value.getBufferChunk(); final Buffer buffer = bufferChunk.getBuffer(); if (buffer.hasArray() && buffer.array().length == length) { return buffer.array(); } final byte[] dstArray = new byte[length]; final int offs = bufferChunk.getStart(); final int oldPos = buffer.position(); final int oldLim = buffer.limit(); Buffers.setPositionLimit(buffer, offs, offs + length); buffer.get(dstArray); Buffers.setPositionLimit(buffer, oldPos, oldLim); return dstArray; } else { final byte[] dstArray = new byte[length]; final String s = value.toString(); for (int i = 0; i < length; i++) { dstArray[i] = (byte) s.charAt(i); } return dstArray; } } }