// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.handler.codec.http3;

import io.netty.handler.codec.Headers;
import io.netty.util.internal.InternalThreadLocalMap;
import io.netty.handler.codec.http.HttpScheme;
import java.util.List;
import io.netty.util.internal.StringUtil;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.util.ByteProcessor;
import io.netty.handler.codec.ValueConverter;
import io.netty.handler.codec.UnsupportedValueConverter;
import java.util.Iterator;
import java.net.URI;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpHeaderNames;
import java.util.Map;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.util.internal.ObjectUtil;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.jetbrains.annotations.Nullable;
import io.netty.util.AsciiString;

public final class HttpConversionUtil
{
    private static final CharSequenceMap<AsciiString> HTTP_TO_HTTP3_HEADER_BLACKLIST;
    private static final AsciiString EMPTY_REQUEST_PATH;
    
    private HttpConversionUtil() {
    }
    
    private static HttpResponseStatus parseStatus(final long streamId, @Nullable final CharSequence status) throws Http3Exception {
        HttpResponseStatus result;
        try {
            result = HttpResponseStatus.parseLine(status);
            if (result == HttpResponseStatus.SWITCHING_PROTOCOLS) {
                throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "Invalid HTTP/3 status code '" + (Object)status + "'", null);
            }
        }
        catch (final Http3Exception e) {
            throw e;
        }
        catch (final Throwable t) {
            throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "Unrecognized HTTP status code '" + (Object)status + "' encountered in translation to HTTP/1.x" + (Object)status, null);
        }
        return result;
    }
    
    static FullHttpResponse toFullHttpResponse(final long streamId, final Http3Headers http3Headers, final ByteBufAllocator alloc, final boolean validateHttpHeaders) throws Http3Exception {
        final ByteBuf content = alloc.buffer();
        final HttpResponseStatus status = parseStatus(streamId, http3Headers.status());
        final FullHttpResponse msg = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content, validateHttpHeaders);
        try {
            addHttp3ToHttpHeaders(streamId, http3Headers, msg, false);
        }
        catch (final Http3Exception e) {
            msg.release();
            throw e;
        }
        catch (final Throwable t) {
            msg.release();
            throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "HTTP/3 to HTTP/1.x headers conversion error", t);
        }
        return msg;
    }
    
    private static CharSequence extractPath(final CharSequence method, final Http3Headers headers) {
        if (HttpMethod.CONNECT.asciiName().contentEqualsIgnoreCase(method)) {
            return ObjectUtil.checkNotNull(headers.authority(), "authority header cannot be null in the conversion to HTTP/1.x");
        }
        return ObjectUtil.checkNotNull(headers.path(), "path header cannot be null in conversion to HTTP/1.x");
    }
    
    static FullHttpRequest toFullHttpRequest(final long streamId, final Http3Headers http3Headers, final ByteBufAllocator alloc, final boolean validateHttpHeaders) throws Http3Exception {
        final ByteBuf content = alloc.buffer();
        final CharSequence method = ObjectUtil.checkNotNull(http3Headers.method(), "method header cannot be null in conversion to HTTP/1.x");
        final CharSequence path = extractPath(method, http3Headers);
        final FullHttpRequest msg = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method.toString()), path.toString(), content, validateHttpHeaders);
        try {
            addHttp3ToHttpHeaders(streamId, http3Headers, msg, false);
        }
        catch (final Http3Exception e) {
            msg.release();
            throw e;
        }
        catch (final Throwable t) {
            msg.release();
            throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "HTTP/3 to HTTP/1.x headers conversion error", t);
        }
        return msg;
    }
    
    static HttpRequest toHttpRequest(final long streamId, final Http3Headers http3Headers, final boolean validateHttpHeaders) throws Http3Exception {
        final CharSequence method = ObjectUtil.checkNotNull(http3Headers.method(), "method header cannot be null in conversion to HTTP/1.x");
        final CharSequence path = extractPath(method, http3Headers);
        final HttpRequest msg = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method.toString()), path.toString(), validateHttpHeaders);
        try {
            addHttp3ToHttpHeaders(streamId, http3Headers, msg.headers(), msg.protocolVersion(), false, true);
        }
        catch (final Http3Exception e) {
            throw e;
        }
        catch (final Throwable t) {
            throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "HTTP/3 to HTTP/1.x headers conversion error", t);
        }
        return msg;
    }
    
    static HttpResponse toHttpResponse(final long streamId, final Http3Headers http3Headers, final boolean validateHttpHeaders) throws Http3Exception {
        final HttpResponseStatus status = parseStatus(streamId, http3Headers.status());
        final HttpResponse msg = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status, validateHttpHeaders);
        try {
            addHttp3ToHttpHeaders(streamId, http3Headers, msg.headers(), msg.protocolVersion(), false, false);
        }
        catch (final Http3Exception e) {
            throw e;
        }
        catch (final Throwable t) {
            throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "HTTP/3 to HTTP/1.x headers conversion error", t);
        }
        return msg;
    }
    
    private static void addHttp3ToHttpHeaders(final long streamId, final Http3Headers inputHeaders, final FullHttpMessage destinationMessage, final boolean addToTrailer) throws Http3Exception {
        addHttp3ToHttpHeaders(streamId, inputHeaders, addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers(), destinationMessage.protocolVersion(), addToTrailer, destinationMessage instanceof HttpRequest);
    }
    
    static void addHttp3ToHttpHeaders(final long streamId, final Http3Headers inputHeaders, final HttpHeaders outputHeaders, final HttpVersion httpVersion, final boolean isTrailer, final boolean isRequest) throws Http3Exception {
        final Http3ToHttpHeaderTranslator translator = new Http3ToHttpHeaderTranslator(streamId, outputHeaders, isRequest);
        try {
            translator.translateHeaders(inputHeaders);
        }
        catch (final Http3Exception ex) {
            throw ex;
        }
        catch (final Throwable t) {
            throw streamError(streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "HTTP/3 to HTTP/1.x headers conversion error", t);
        }
        outputHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
        outputHeaders.remove(HttpHeaderNames.TRAILER);
        if (!isTrailer) {
            outputHeaders.set(ExtensionHeaderNames.STREAM_ID.text(), streamId);
            HttpUtil.setKeepAlive(outputHeaders, httpVersion, true);
        }
    }
    
    static Http3Headers toHttp3Headers(final HttpMessage in, final boolean validateHeaders) {
        final HttpHeaders inHeaders = in.headers();
        final Http3Headers out = new DefaultHttp3Headers(validateHeaders, inHeaders.size());
        if (in instanceof HttpRequest) {
            final HttpRequest request = (HttpRequest)in;
            final URI requestTargetUri = URI.create(request.uri());
            out.path(toHttp3Path(requestTargetUri));
            out.method(request.method().asciiName());
            setHttp3Scheme(inHeaders, requestTargetUri, out);
            final String host = inHeaders.getAsString(HttpHeaderNames.HOST);
            if (host != null && !host.isEmpty()) {
                setHttp3Authority(host, out);
            }
            else if (!HttpUtil.isOriginForm(request.uri()) && !HttpUtil.isAsteriskForm(request.uri())) {
                setHttp3Authority(requestTargetUri.getAuthority(), out);
            }
        }
        else if (in instanceof HttpResponse) {
            final HttpResponse response = (HttpResponse)in;
            out.status(response.status().codeAsText());
        }
        toHttp3Headers(inHeaders, out);
        return out;
    }
    
    static Http3Headers toHttp3Headers(final HttpHeaders inHeaders, final boolean validateHeaders) {
        if (inHeaders.isEmpty()) {
            return new DefaultHttp3Headers();
        }
        final Http3Headers out = new DefaultHttp3Headers(validateHeaders, inHeaders.size());
        toHttp3Headers(inHeaders, out);
        return out;
    }
    
    private static CharSequenceMap<AsciiString> toLowercaseMap(final Iterator<? extends CharSequence> valuesIter, final int arraySizeHint) {
        final UnsupportedValueConverter<AsciiString> valueConverter = UnsupportedValueConverter.instance();
        final CharSequenceMap<AsciiString> result = new CharSequenceMap<AsciiString>(true, valueConverter, arraySizeHint);
        while (valuesIter.hasNext()) {
            final AsciiString lowerCased = AsciiString.of((CharSequence)valuesIter.next()).toLowerCase();
            try {
                int index = lowerCased.forEachByte(ByteProcessor.FIND_COMMA);
                if (index != -1) {
                    int start = 0;
                    do {
                        result.add(lowerCased.subSequence(start, index, false).trim(), AsciiString.EMPTY_STRING);
                        start = index + 1;
                    } while (start < lowerCased.length() && (index = lowerCased.forEachByte(start, lowerCased.length() - start, ByteProcessor.FIND_COMMA)) != -1);
                    result.add(lowerCased.subSequence(start, lowerCased.length(), false).trim(), AsciiString.EMPTY_STRING);
                }
                else {
                    result.add(lowerCased.trim(), AsciiString.EMPTY_STRING);
                }
            }
            catch (final Exception e) {
                throw new IllegalStateException(e);
            }
        }
        return result;
    }
    
    private static void toHttp3HeadersFilterTE(final Map.Entry<CharSequence, CharSequence> entry, final Http3Headers out) {
        if (AsciiString.indexOf(entry.getValue(), ',', 0) == -1) {
            if (AsciiString.contentEqualsIgnoreCase(AsciiString.trim(entry.getValue()), HttpHeaderValues.TRAILERS)) {
                ((Headers<AsciiString, AsciiString, Headers>)out).add(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
            }
        }
        else {
            final List<CharSequence> teValues = StringUtil.unescapeCsvFields(entry.getValue());
            for (final CharSequence teValue : teValues) {
                if (AsciiString.contentEqualsIgnoreCase(AsciiString.trim(teValue), HttpHeaderValues.TRAILERS)) {
                    ((Headers<AsciiString, AsciiString, Headers>)out).add(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
                    break;
                }
            }
        }
    }
    
    static void toHttp3Headers(final HttpHeaders inHeaders, final Http3Headers out) {
        final Iterator<Map.Entry<CharSequence, CharSequence>> iter = inHeaders.iteratorCharSequence();
        final CharSequenceMap<AsciiString> connectionBlacklist = toLowercaseMap(inHeaders.valueCharSequenceIterator(HttpHeaderNames.CONNECTION), 8);
        while (iter.hasNext()) {
            final Map.Entry<CharSequence, CharSequence> entry = iter.next();
            final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
            if (!HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.contains(aName) && !connectionBlacklist.contains(aName)) {
                if (aName.contentEqualsIgnoreCase(HttpHeaderNames.TE)) {
                    toHttp3HeadersFilterTE(entry, out);
                }
                else if (aName.contentEqualsIgnoreCase(HttpHeaderNames.COOKIE)) {
                    final AsciiString value = AsciiString.of(entry.getValue());
                    try {
                        int index = value.forEachByte(ByteProcessor.FIND_SEMI_COLON);
                        if (index != -1) {
                            int start = 0;
                            do {
                                ((Headers<AsciiString, AsciiString, Headers>)out).add(HttpHeaderNames.COOKIE, value.subSequence(start, index, false));
                                start = index + 2;
                            } while (start < value.length() && (index = value.forEachByte(start, value.length() - start, ByteProcessor.FIND_SEMI_COLON)) != -1);
                            if (start >= value.length()) {
                                throw new IllegalArgumentException("cookie value is of unexpected format: " + (Object)value);
                            }
                            ((Headers<AsciiString, AsciiString, Headers>)out).add(HttpHeaderNames.COOKIE, value.subSequence(start, value.length(), false));
                        }
                        else {
                            ((Headers<AsciiString, AsciiString, Headers>)out).add(HttpHeaderNames.COOKIE, value);
                        }
                    }
                    catch (final Exception e) {
                        throw new IllegalStateException(e);
                    }
                }
                else {
                    ((Headers<AsciiString, CharSequence, Headers>)out).add(aName, entry.getValue());
                }
            }
        }
    }
    
    private static AsciiString toHttp3Path(final URI uri) {
        final StringBuilder pathBuilder = new StringBuilder(StringUtil.length(uri.getRawPath()) + StringUtil.length(uri.getRawQuery()) + StringUtil.length(uri.getRawFragment()) + 2);
        if (!StringUtil.isNullOrEmpty(uri.getRawPath())) {
            pathBuilder.append(uri.getRawPath());
        }
        if (!StringUtil.isNullOrEmpty(uri.getRawQuery())) {
            pathBuilder.append('?');
            pathBuilder.append(uri.getRawQuery());
        }
        if (!StringUtil.isNullOrEmpty(uri.getRawFragment())) {
            pathBuilder.append('#');
            pathBuilder.append(uri.getRawFragment());
        }
        final String path = pathBuilder.toString();
        return path.isEmpty() ? HttpConversionUtil.EMPTY_REQUEST_PATH : new AsciiString(path);
    }
    
    static void setHttp3Authority(@Nullable final String authority, final Http3Headers out) {
        if (authority != null) {
            if (authority.isEmpty()) {
                out.authority(AsciiString.EMPTY_STRING);
            }
            else {
                final int start = authority.indexOf(64) + 1;
                final int length = authority.length() - start;
                if (length == 0) {
                    throw new IllegalArgumentException("authority: " + authority);
                }
                out.authority(new AsciiString(authority, start, length));
            }
        }
    }
    
    private static void setHttp3Scheme(final HttpHeaders in, final URI uri, final Http3Headers out) {
        final String value = uri.getScheme();
        if (value != null) {
            out.scheme(new AsciiString(value));
            return;
        }
        final CharSequence cValue = in.get(ExtensionHeaderNames.SCHEME.text());
        if (cValue != null) {
            out.scheme(AsciiString.of(cValue));
            return;
        }
        if (uri.getPort() == HttpScheme.HTTPS.port()) {
            out.scheme(HttpScheme.HTTPS.name());
        }
        else {
            if (uri.getPort() != HttpScheme.HTTP.port()) {
                throw new IllegalArgumentException(":scheme must be specified. see https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.1.1");
            }
            out.scheme(HttpScheme.HTTP.name());
        }
    }
    
    private static Http3Exception streamError(final long streamId, final Http3ErrorCode error, final String msg, @Nullable final Throwable cause) {
        return new Http3Exception(error, streamId + ": " + msg, cause);
    }
    
    static {
        (HTTP_TO_HTTP3_HEADER_BLACKLIST = new CharSequenceMap<AsciiString>()).add(HttpHeaderNames.CONNECTION, AsciiString.EMPTY_STRING);
        final AsciiString keepAlive = HttpHeaderNames.KEEP_ALIVE;
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(keepAlive, AsciiString.EMPTY_STRING);
        final AsciiString proxyConnection = HttpHeaderNames.PROXY_CONNECTION;
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(proxyConnection, AsciiString.EMPTY_STRING);
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(HttpHeaderNames.TRANSFER_ENCODING, AsciiString.EMPTY_STRING);
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(HttpHeaderNames.HOST, AsciiString.EMPTY_STRING);
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(HttpHeaderNames.UPGRADE, AsciiString.EMPTY_STRING);
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(ExtensionHeaderNames.STREAM_ID.text(), AsciiString.EMPTY_STRING);
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(ExtensionHeaderNames.SCHEME.text(), AsciiString.EMPTY_STRING);
        HttpConversionUtil.HTTP_TO_HTTP3_HEADER_BLACKLIST.add(ExtensionHeaderNames.PATH.text(), AsciiString.EMPTY_STRING);
        EMPTY_REQUEST_PATH = AsciiString.cached("/");
    }
    
    public enum ExtensionHeaderNames
    {
        STREAM_ID("x-http3-stream-id"), 
        SCHEME("x-http3-scheme"), 
        PATH("x-http3-path"), 
        STREAM_PROMISE_ID("x-http3-stream-promise-id");
        
        private final AsciiString text;
        
        private ExtensionHeaderNames(final String text) {
            this.text = AsciiString.cached(text);
        }
        
        public AsciiString text() {
            return this.text;
        }
    }
    
    private static final class Http3ToHttpHeaderTranslator
    {
        private static final CharSequenceMap<AsciiString> REQUEST_HEADER_TRANSLATIONS;
        private static final CharSequenceMap<AsciiString> RESPONSE_HEADER_TRANSLATIONS;
        private final long streamId;
        private final HttpHeaders output;
        private final CharSequenceMap<AsciiString> translations;
        
        Http3ToHttpHeaderTranslator(final long streamId, final HttpHeaders output, final boolean request) {
            this.streamId = streamId;
            this.output = output;
            this.translations = (request ? Http3ToHttpHeaderTranslator.REQUEST_HEADER_TRANSLATIONS : Http3ToHttpHeaderTranslator.RESPONSE_HEADER_TRANSLATIONS);
        }
        
        void translateHeaders(final Iterable<Map.Entry<CharSequence, CharSequence>> inputHeaders) throws Http3Exception {
            StringBuilder cookies = null;
            for (final Map.Entry<CharSequence, CharSequence> entry : inputHeaders) {
                final CharSequence name = entry.getKey();
                final CharSequence value = entry.getValue();
                final AsciiString translatedName = this.translations.get(name);
                if (translatedName != null) {
                    this.output.add(translatedName, AsciiString.of(value));
                }
                else {
                    if (Http3Headers.PseudoHeaderName.isPseudoHeader(name)) {
                        continue;
                    }
                    if (name.length() == 0 || name.charAt(0) == ':') {
                        throw streamError(this.streamId, Http3ErrorCode.H3_MESSAGE_ERROR, "Invalid HTTP/3 header '" + (Object)name + "' encountered in translation to HTTP/1.x", null);
                    }
                    if (HttpHeaderNames.COOKIE.equals(name)) {
                        if (cookies == null) {
                            cookies = InternalThreadLocalMap.get().stringBuilder();
                        }
                        else if (cookies.length() > 0) {
                            cookies.append("; ");
                        }
                        cookies.append(value);
                    }
                    else {
                        this.output.add(name, value);
                    }
                }
            }
            if (cookies != null) {
                this.output.add(HttpHeaderNames.COOKIE, cookies.toString());
            }
        }
        
        static {
            REQUEST_HEADER_TRANSLATIONS = new CharSequenceMap<AsciiString>();
            (RESPONSE_HEADER_TRANSLATIONS = new CharSequenceMap<AsciiString>()).add(Http3Headers.PseudoHeaderName.AUTHORITY.value(), HttpHeaderNames.HOST);
            Http3ToHttpHeaderTranslator.RESPONSE_HEADER_TRANSLATIONS.add(Http3Headers.PseudoHeaderName.SCHEME.value(), ExtensionHeaderNames.SCHEME.text());
            Http3ToHttpHeaderTranslator.REQUEST_HEADER_TRANSLATIONS.add((Headers<?, ?, ?>)Http3ToHttpHeaderTranslator.RESPONSE_HEADER_TRANSLATIONS);
            Http3ToHttpHeaderTranslator.RESPONSE_HEADER_TRANSLATIONS.add(Http3Headers.PseudoHeaderName.PATH.value(), ExtensionHeaderNames.PATH.text());
        }
    }
}
