HttpRequestEncoder.java

/*
 * Copyright (c) 2015, 2019 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.jersey.jdk.connector.internal;

import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.Buffer;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

/**
 * @author Petr Janouch
 */
class HttpRequestEncoder {

    private static final String ENCODING = "ISO-8859-1";
    private static final String LINE_SEPARATOR = "\r\n";
    private static final byte[] LINE_SEPARATOR_BYTES = LINE_SEPARATOR.getBytes(Charset.forName(ENCODING));
    private static final byte[] LAST_CHUNK = "0\r\n\r\n".getBytes(Charset.forName(ENCODING));
    private static final String HTTP_VERSION = "HTTP/1.1";

    private static void appendUpgradeHeaders(StringBuilder request, Map<String, List<String>> headers) {
        for (Map.Entry<String, List<String>> header : headers.entrySet()) {
            StringBuilder value = new StringBuilder();
            for (String valuePart : header.getValue()) {
                if (value.length() != 0) {
                    value.append(",");
                }
                value.append(valuePart);
            }
            appendHeader(request, header.getKey(), value.toString());
        }

        request.append(LINE_SEPARATOR);
    }

    private static void appendHeader(StringBuilder request, String key, String value) {
        request.append(key);
        request.append(": ");
        request.append(value);
        request.append(LINE_SEPARATOR);
    }

    private static void appendFirstLine(StringBuilder request, HttpRequest httpRequest) {
        request.append(httpRequest.getMethod());
        request.append(" ");
        if (httpRequest.getMethod().equals(Constants.CONNECT)) {
            request.append(httpRequest.getUri().toString());
        } else {
            URI uri = httpRequest.getUri();
            String path = uri.getRawPath();
            if (path == null || path.isEmpty()) {
                path = "/";
            }

            if (uri.getRawQuery() != null) {
                path += "?" + uri.getRawQuery();
            }

            request.append(path);
        }
        request.append(" ");
        request.append(HTTP_VERSION);
        request.append(LINE_SEPARATOR);
    }

    static ByteBuffer encodeHeader(HttpRequest httpRequest) {
        StringBuilder request = new StringBuilder();
        appendFirstLine(request, httpRequest);
        appendUpgradeHeaders(request, httpRequest.getHeaders());
        String requestStr = request.toString();
        byte[] bytes = requestStr.getBytes(Charset.forName(ENCODING));
        return ByteBuffer.wrap(bytes);
    }

    static ByteBuffer encodeChunk(ByteBuffer data) {
        if (data.remaining() == 0) {
            return ByteBuffer.wrap(LAST_CHUNK);
        }

        byte[] startBytes = getChunkHeaderBytes(data.remaining());
        ByteBuffer chunkBuffer = ByteBuffer.allocate(startBytes.length + data.remaining() + 2);
        chunkBuffer.put(startBytes);
        chunkBuffer.put(data);
        chunkBuffer.put(LINE_SEPARATOR_BYTES);
        ((Buffer) chunkBuffer).flip();

        return chunkBuffer;
    }

    private static byte[] getChunkHeaderBytes(int dataLength) {
        String chunkStart = Integer.toHexString(dataLength) + LINE_SEPARATOR;
        return chunkStart.getBytes(Charset.forName(ENCODING));
    }

    static int getChunkSize(int dataLength) {
        if (dataLength == 0) {
            return LAST_CHUNK.length;
        }

        return getChunkHeaderBytes(dataLength).length + dataLength + 2;
    }
}