Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
core/src/main/java/io/undertow/protocols/http2/HpackEncoder.java
### 代码内容：
package io.undertow.protocols.http2;

import io.undertow.util.HeaderMap;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;

import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static io.undertow.protocols.http2.Hpack.HeaderField;
import static io.undertow.protocols.http2.Hpack.STATIC_TABLE;
import static io.undertow.protocols.http2.Hpack.STATIC_TABLE_LENGTH;
import static io.undertow.protocols.http2.Hpack.encodeInteger;

/**
 * Encoder for HPACK frames.
 *
 * @author Stuart Douglas
 */
public class HpackEncoder {

    private static final Set<HttpString> SKIP;

    static {
        Set<HttpString> set = new HashSet<>();
        set.add(Headers.CONNECTION);
        set.add(Headers.TRANSFER_ENCODING);
        set.add(Headers.KEEP_ALIVE);
        set.add(Headers.UPGRADE);
        SKIP = Collections.unmodifiableSet(set);
    }

    public static final HpackHeaderFunction DEFAULT_HEADER_FUNCTION = new HpackHeaderFunction() {
        @Override
        public boolean shouldUseIndexing(HttpString headerName, String value) {
            //content length and date change all the time
            //no need to index them, or they will churn the table
            return !headerName.equals(Headers.CONTENT_LENGTH) && !headerName.equals(Headers.DATE);
        }

        @Override
        public boolean shouldUseHuffman(HttpString header, String value) {
            return value.length() > 10; //TODO: figure out a good value for this
        }

        @Override
        public boolean shouldUseHuffman(HttpString header) {
            return header.length() > 10; //TODO: figure out a good value for this
        }

    };

    private long headersIterator = -1;
    private boolean firstPass = true;

    private HeaderMap currentHeaders;

    private int entryPositionCounter;

    private int newMaxHeaderSize = -1; //if the max header size has been changed
    private int minNewMaxHeaderSize = -1; //records the smallest value of newMaxHeaderSize, as per section 4.1

    private static final Map<HttpString, TableEntry[]> ENCODING_STATIC_TABLE;

    private final Deque<TableEntry> evictionQueue = new ArrayDeque<>();
    private final Map<HttpString, List<TableEntry>> dynamicTable = new HashMap<>();

    private byte[] overflowData;
    private int overflowPos;
    private int overflowLength;

    static {
        Map<HttpString, TableEntry[]> map = new HashMap<>();
        for (int i = 1; i < STATIC_TABLE.length; ++i) {
            HeaderField m = STATIC_TABLE[i];
            TableEntry[] existing = map.get(m.name);
            if (existing == null) {
                map.put(m.name, new TableEntry[]{new TableEntry(m.name, m.value, i)});
            } else {
                TableEntry[] newEntry = new TableEntry[existing.length + 1];
                System.arraycopy(existing, 0, newEntry, 0, existing.length);
                newEntry[existing.length] = new TableEntry(m.name, m.value, i);
                map.put(m.name, newEntry);
            }
        }
        ENCODING_STATIC_TABLE = Collections.unmodifiableMap(map);
    }

    /**
     * The maximum table size
     */
    private int maxTableSize;

    /**
     * The current table size
     */
    private int currentTableSize;

    private final HpackHeaderFunction hpackHeaderFunction;

    public HpackEncoder(int maxTableSize, HpackHeaderFunction headerFunction) {
        this.maxTableSize = maxTableSize;
        this.hpackHeaderFunction = headerFunction;
    }

    public HpackEncoder(int maxTableSize) {
        this(maxTableSize, DEFAULT_HEADER_FUNCTION);
    }

    /**
     * Encodes the headers into a buffer.
     *
     * @param headers
     * @param target
     */
    public State encode(HeaderMap headers, ByteBuffer target) {
        if(overflowData != null) {
            for(int i = overflowPos; i < overflowLength; ++i) {
                if(!target.hasRemaining()) {
                    overflowPos = i;
                    return State.OVERFLOW;
                }
                target.put(overflowData[i]);
            }
            overflowData = null;
        }

        long it = headersIterator;
        if (headersIterator == -1) {
            handleTableSizeChange(target);
            //new headers map
            it = headers.fastIterate();
            currentHeaders = headers;
        } else {
            if (headers != currentHeaders) {
                throw new IllegalStateException();
            }
        }
        while (it != -1) {
            HeaderValues values = headers.fiCurrent(it);
            boolean skip = false;
            if (firstPass) {
                if (values.getHeaderName().byteAt(0) != ':') {
                    skip = true;
                }
            } else {
                if (values.getHeaderName().byteAt(0) == ':') {
                    skip = true;
                }
            }
            if(SKIP.contains(values.getHeaderName())) {
                //ignore connection specific headers
                skip = true;
            }
            if (!skip) {
                for (int i = 0; i < values.size(); ++i) {

                    HttpString headerName = values.getHeaderName();
                    int required = 11 + headerName.length(); //we use 11 to make sure we have enough room for the variable length itegers

                    String val = values.get(i);
                    for(int v = 0; v < val.length(); ++v) {
                        char c = val.charAt(v);
                        if(c == '\r' || c == '\n') {
                            val = val.replace('\r', ' ').replace('\n', ' ');
                            break;
                        }
                    }
                    TableEntry tableEntry = findInTable(headerName, val);

                    required += (1 + val.length());
                    boolean overflowing = false;

                    ByteBuffer current = target;
                    if (current.remaining() < required) {
                        overflowing = true;
                        current = ByteBuffer.wrap(overflowData = new byte[required]);
                        overflowPos = 0;
                    }
                    boolean canIndex = hpackHeaderFunction.shouldUseIndexing(headerName, val) && (headerName.length() + val.length() + 32) < maxTableSize; //only index if it will fit
                    if (tableEntry == null && canIndex) {
                        //add the entry to the dynamic table
                        current.put((byte) (1 << 6));
                        writeHuffmanEncodableName(current, headerName);
                        writeHuffmanEncodableValue(current, headerName, val);
                        addToDynamicTable(headerName, val);
                    } else if (tableEntry == null) {
                        //literal never indexed
                        current.put((byte) (1 << 4));
                        writeHuffmanEncodableName(current, headerName);
                        writeHuffmanEncodableValue(current, headerName, val);
                    } else {
                        //so we know something is already in the table
                        if (val.equals(tableEntry.value)) {
                            //the whole thing is in the table
                            current.put((byte) (1 << 7));
                            encodeInteger(current, tableEntry.getPosition(), 7);
                        } else {
                            if (canIndex) {
                                //add the entry to the dynamic table
                                current.put((byte) (1 << 6));
                                encodeInteger(current, tableEntry.getPosition(), 6);
                                writeHuffmanEncodableValue(current, headerName, val);
                                addToDynamicTable(headerName, val);

                            } else {
                                current.put((byte) (1 << 4));
                                encodeInteger(current, tableEntry.getPosition(), 4);
                                writeHuffmanEncodableValue(current, headerName, val);
                            }
                        }
                    }
                    if(overflowing) {
                        it = headers.fiNext(it);
                        this.headersIterator = it;
                        this.overflowLength = current.position();
                        return State.OVERFLOW;
                    }

                }
            }
            it = headers.fiNext(it);
            if (it == -1 && firstPass) {
                firstPass = false;
                it = headers.fastIterate();
            }
        }
        headersIterator = -1;
        firstPass = true;
        return State.COMPLETE;
    }

    private void writeHuffmanEncodableName(ByteBuffer target, HttpString headerName) {
        if (hpackHeaderFunction.shouldUseHuffman(headerName)) {
            if(HPackHuffman.encode(target, headerName.toString(), true)) {
                return;
            }
        }
        target.put((byte) 0); //to use encodeInteger we need to place the first byte in the buffer.
        encodeInteger(target, headerName.length(), 7);
        for (int j = 0; j < headerName.length(); ++j) {
            target.put(Hpack.toLower(headerName.byteAt(j)));
        }

    }

    private void writeHuffmanEncodableValue(ByteBuffer target, HttpString headerName, String val) {
        if (hpackHeaderFunction.shouldUseHuffman(headerName, val)) {
            if (!HPackHuffman.encode(target, val, false)) {
                writeValueString(target, val);
            }
        } else {
            writeValueString(target, val);
        }
    }

    private void writeValueString(ByteBuffer target, String val) {
        target.put((byte) 0); //to use encodeInteger we need to place the first byte in the buffer.
        encodeInteger(target, val.length(), 7);
        for (int j = 0; j < val.length(); ++j) {
            target.put((byte) val.charAt(j));
        }
    }

    private void addToDynamicTable(HttpString headerName, String val) {
        int pos = entryPositionCounter++;
        DynamicTableEntry d = new DynamicTableEntry(headerName, val, -pos);
        List<TableEntry> existing = dynamicTable.get(headerName);
        if (existing == null) {
            dynamicTable.put(headerName, existing = new ArrayList<>(1));
        }
        existing.add(d);
        evictionQueue.add(d);
        currentTableSize += d.size;
        runEvictionIfRequired();
        if (entryPositionCounter == Integer.MAX_VALUE) {
            //prevent rollover
            preventPositionRollover();
        }

    }

    private void preventPositionRollover() {
        //if the position counter is about to roll over we iterate all the table entries
        //and set their position to their actual position
        for (Map.Entry<HttpString, List<TableEntry>> entry : dynamicTable.entrySet()) {
            for (TableEntry t : entry.getValue()) {
                t.position = t.getPosition();
            }
        }
        entryPositionCounter = 0;
    }

    private void runEvictionIfRequired() {

        while (currentTableSize > maxTableSize) {
            TableEntry next = evictionQueue.poll();
            if (next == null) {
                return;
            }
            currentTableSize -= next.size;
            List<TableEntry> list = dynamicTable.get(next.name);
            list.remove(next);
            if (list.isEmpty()) {
                dynamicTable.remove(next.name);
            }
        }
    }

    private TableEntry findInTable(HttpString headerName, String value) {
        TableEntry[] staticTable = ENCODING_STATIC_TABLE.get(headerName);
        if (staticTable != null) {
            for (TableEntry st : staticTable) {
                if (st.value != null && st.value.equals(value)) { //todo: some form of lookup?
                    return st;
                }
            }
        }
        List<TableEntry> dynamic = dynamicTable.get(headerName);
        if (dynamic != null) {
            for (int i = 0; i < dynamic.size(); ++i) {
                TableEntry st = dynamic.get(i);
                if (st.value.equals(value)) { //todo: some form of lookup?
                    return st;
                }
            }
        }
        if (staticTable != null) {
            return staticTable[0];
        }
        return null;
    }

    public void setMaxTableSize(int newSize) {
        this.newMaxHeaderSize = newSize;
        if (minNewMaxHeaderSize == -1) {
            minNewMaxHeaderSize = newSize;
        } else {
            minNewMaxHeaderSize = Math.min(newSize, minNewMaxHeaderSize);
        }
    }

    private void handleTableSizeChange(ByteBuffer target) {
        if (newMaxHeaderSize == -1) {
            return;
        }
        if (minNewMaxHeaderSize != newMaxHeaderSize) {
            target.put((byte) (1 << 5));
            encodeInteger(target, minNewMaxHeaderSize, 5);
        }
        target.put((byte) (1 << 5));
        encodeInteger(target, newMaxHeaderSize, 5);
        maxTableSize = newMaxHeaderSize;
        runEvictionIfRequired();
        newMaxHeaderSize = -1;
        minNewMaxHeaderSize = -1;
    }

    public enum State {
        COMPLETE,
        OVERFLOW,
    }

    static class TableEntry {
        final HttpString name;
        final String value;
        final int size;
        int position;

        TableEntry(HttpString name, String value, int position) {
            this.name = name;
            this.value = value;
            this.position = position;
            if (value != null) {
                this.size = 32 + name.length() + value.length();
            } else {
                this.size = -1;
            }
        }

        public int getPosition() {
            return position;
        }
    }

    class DynamicTableEntry extends TableEntry {

        DynamicTableEntry(HttpString name, String value, int position) {
            super(name, value, position);
        }

        @Override
        public int getPosition() {
            return super.getPosition() + entryPositionCounter + STATIC_TABLE_LENGTH;
        }
    }

    public interface HpackHeaderFunction {
        boolean shouldUseIndexing(HttpString header, String value);

        /**
         * Returns true if huffman encoding should be used on the header value
         *
         * @param header The header name
         * @param value  The header value to be encoded
         * @return true if the value should be encoded
         */
        boolean shouldUseHuffman(HttpString header, String value);

        /**
         * Returns true if huffman encoding should be used on the header name
         *
         * @param header The header name to be encoded
         * @return true if the value should be encoded
         */
        boolean shouldUseHuffman(HttpString header);
    }
}

## 内容2
### 文件路径：
core/src/main/java/io/undertow/protocols/http2/Http2Channel.java
### 代码内容：
package io.undertow.protocols.http2;

import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.UndertowOptions;
import io.undertow.connector.ByteBufferPool;
import io.undertow.connector.PooledByteBuffer;
import io.undertow.server.protocol.ParseTimeoutUpdater;
import io.undertow.server.protocol.framed.AbstractFramedChannel;
import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel;
import io.undertow.server.protocol.framed.FrameHeaderData;
import io.undertow.server.protocol.http2.Http2OpenListener;
import io.undertow.util.Attachable;
import io.undertow.util.AttachmentKey;
import io.undertow.util.AttachmentList;
import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;
import org.xnio.Bits;
import org.xnio.ChannelExceptionHandler;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.IoUtils;
import org.xnio.OptionMap;
import org.xnio.StreamConnection;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.ssl.SslConnection;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import javax.net.ssl.SSLSession;

/**
 * HTTP2 channel.
 *
 * @author Stuart Douglas
 */
public class Http2Channel extends AbstractFramedChannel<Http2Channel, AbstractHttp2StreamSourceChannel, AbstractHttp2StreamSinkChannel> implements Attachable {

    public static final String CLEARTEXT_UPGRADE_STRING = "h2c";

    public static final HttpString METHOD = new HttpString(":method");
    public static final HttpString PATH = new HttpString(":path");
    public static final HttpString SCHEME = new HttpString(":scheme");
    public static final HttpString AUTHORITY = new HttpString(":authority");
    public static final HttpString STATUS = new HttpString(":status");

    static final int FRAME_TYPE_DATA = 0x00;
    static final int FRAME_TYPE_HEADERS = 0x01;
    static final int FRAME_TYPE_PRIORITY = 0x02;
    static final int FRAME_TYPE_RST_STREAM = 0x03;
    static final int FRAME_TYPE_SETTINGS = 0x04;
    static final int FRAME_TYPE_PUSH_PROMISE = 0x05;
    static final int FRAME_TYPE_PING = 0x06;
    static final int FRAME_TYPE_GOAWAY = 0x07;
    static final int FRAME_TYPE_WINDOW_UPDATE = 0x08;
    static final int FRAME_TYPE_CONTINUATION = 0x09;

    public static final int ERROR_NO_ERROR = 0x00;
    public static final int ERROR_PROTOCOL_ERROR = 0x01;
    public static final int ERROR_INTERNAL_ERROR = 0x02;
    public static final int ERROR_FLOW_CONTROL_ERROR = 0x03;
    public static final int ERROR_SETTINGS_TIMEOUT = 0x04;
    public static final int ERROR_STREAM_CLOSED = 0x05;
    public static final int ERROR_FRAME_SIZE_ERROR = 0x06;
    public static final int ERROR_REFUSED_STREAM = 0x07;
    public static final int ERROR_CANCEL = 0x08;
    public static final int ERROR_COMPRESSION_ERROR = 0x09;
    public static final int ERROR_CONNECT_ERROR = 0x0a;
    public static final int ERROR_ENHANCE_YOUR_CALM = 0x0b;
    public static final int ERROR_INADEQUATE_SECURITY = 0x0c;

    static final int DATA_FLAG_END_STREAM = 0x1;
    static final int DATA_FLAG_END_SEGMENT = 0x2;
    static final int DATA_FLAG_PADDED = 0x8;

    static final int PING_FRAME_LENGTH = 8;
    static final int PING_FLAG_ACK = 0x1;

    static final int HEADERS_FLAG_END_STREAM = 0x1;
    static final int HEADERS_FLAG_END_SEGMENT = 0x2;
    static final int HEADERS_FLAG_END_HEADERS = 0x4;
    static final int HEADERS_FLAG_PADDED = 0x8;
    static final int HEADERS_FLAG_PRIORITY = 0x20;

    static final int SETTINGS_FLAG_ACK = 0x1;

    static final int CONTINUATION_FLAG_END_HEADERS = 0x4;

    public static final int DEFAULT_INITIAL_WINDOW_SIZE = 65535;

    static final byte[] PREFACE_BYTES = {
            0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
            0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
            0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
    public static final int DEFAULT_MAX_FRAME_SIZE = 16384;
    public static final int MAX_FRAME_SIZE = 16777215;
    public static final int FLOW_CONTROL_MIN_WINDOW = 2;

    private Http2FrameHeaderParser frameParser;
    private final Map<Integer, StreamHolder> currentStreams = new ConcurrentHashMap<>();
    private final String protocol;

    //local
    private final int encoderHeaderTableSize;
    private volatile boolean pushEnabled;
    private volatile int sendMaxConcurrentStreams = -1;
    private final int receiveMaxConcurrentStreams;
    private volatile int sendConcurrentStreams = 0;
    private volatile int receiveConcurrentStreams = 0;
    private final int initialReceiveWindowSize;
    private volatile int sendMaxFrameSize = DEFAULT_MAX_FRAME_SIZE;
    private final int receiveMaxFrameSize;
    private int unackedReceiveMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; //the old max frame size, this gets updated when our setting frame is acked
    private final int maxHeaders;
    private final int maxHeaderListSize;

    private static final AtomicIntegerFieldUpdater<Http2Channel> sendConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater(
            Http2Channel.class, "sendConcurrentStreams");

    private static final AtomicIntegerFieldUpdater<Http2Channel> receiveConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater(
            Http2Channel.class, "receiveConcurrentStreams");

    private boolean thisGoneAway = false;
    private boolean peerGoneAway = false;
    private boolean lastDataRead = false;

    private int streamIdCounter;
    private int lastGoodStreamId;
    private int lastAssignedStreamOtherSide;

    private final HpackDecoder decoder;
    private final HpackEncoder encoder;
    private final int maxPadding;
    private final Random paddingRandom;

    private int prefaceCount;
    private boolean initialSettingsReceived; //settings frame must be the first frame we relieve
    private Http2HeadersParser continuationParser = null; //state for continuation frames
    /**
     * We send the settings frame lazily, which is basically a big hack to work around broken IE support for push (it
     * dies if you send a settings frame with push enabled).
     *
     * Once IE is no longer broken this should be removed.
     */
    private boolean initialSettingsSent = false;

    private final Map<AttachmentKey<?>, Object> attachments = Collections.synchronizedMap(new HashMap<AttachmentKey<?>, Object>());

    private final ParseTimeoutUpdater parseTimeoutUpdater;

    private final Object flowControlLock = new Object();

    /**
     * The initial window size for newly created channels, guarded by {@link #flowControlLock}
     */
    private volatile int initialSendWindowSize = DEFAULT_INITIAL_WINDOW_SIZE;
    /**
     * How much data we can send to the remote endpoint, at the connection level, guarded by {@link #flowControlLock}
     */
    private volatile long sendWindowSize = initialSendWindowSize;

    /**
     * How much data we have told the remote endpoint we are prepared to accept, guarded by {@link #flowControlLock}
     */
    private volatile int receiveWindowSize;

    public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, OptionMap settings) {
        this(connectedStreamChannel, protocol, bufferPool, data, clientSide, fromUpgrade, true, null, settings);
    }

    public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, boolean prefaceRequired, OptionMap settings) {
        this(connectedStreamChannel, protocol, bufferPool, data, clientSide, fromUpgrade, prefaceRequired, null, settings);
    }

    public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, boolean prefaceRequired, ByteBuffer initialOtherSideSettings, OptionMap settings) {
        super(connectedStreamChannel, bufferPool, new Http2FramePriority(clientSide ? (fromUpgrade ? 3 : 1) : 2), data, settings);
        streamIdCounter = clientSide ? (fromUpgrade ? 3 : 1) : 2;

        pushEnabled = settings.get(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH, true);
        this.initialReceiveWindowSize = settings.get(UndertowOptions.HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE);
        this.receiveWindowSize = initialReceiveWindowSize;
        this.receiveMaxConcurrentStreams = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, -1);

        this.protocol = protocol == null ? Http2OpenListener.HTTP2 : protocol;
        this.maxHeaders = settings.get(UndertowOptions.MAX_HEADERS, clientSide ? -1 : UndertowOptions.DEFAULT_MAX_HEADERS);

        encoderHeaderTableSize = settings.get(UndertowOptions.HTTP2_SETTINGS_HEADER_TABLE_SIZE, Hpack.DEFAULT_TABLE_SIZE);
        receiveMaxFrameSize = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_FRAME_SIZE, DEFAULT_MAX_FRAME_SIZE);
        maxPadding = settings.get(UndertowOptions.HTTP2_PADDING_SIZE, 0);
        maxHeaderListSize = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, settings.get(UndertowOptions.MAX_HEADER_SIZE, -1));
        if(maxPadding > 0) {
            paddingRandom = new SecureRandom();
        } else {
            paddingRandom = null;
        }

        this.decoder = new HpackDecoder(encoderHeaderTableSize);
        this.encoder = new HpackEncoder(encoderHeaderTableSize);
        if(!prefaceRequired) {
            prefaceCount = PREFACE_BYTES.length;
        }

        if (clientSide) {
            sendPreface();
            prefaceCount = PREFACE_BYTES.length;
            sendSettings();
            initialSettingsSent = true;
            if(fromUpgrade) {
                StreamHolder streamHolder = new StreamHolder((Http2StreamSinkChannel) null);
                streamHolder.sinkClosed = true;
                sendConcurrentStreamsAtomicUpdater.getAndIncrement(this);
                currentStreams.put(1, streamHolder);
            }
        } else if(fromUpgrade) {
            sendSettings();
            initialSettingsSent = true;
        }
        if (initialOtherSideSettings != null) {
            Http2SettingsParser parser = new Http2SettingsParser(initialOtherSideSettings.remaining());
            try {
                final Http2FrameHeaderParser headerParser = new Http2FrameHeaderParser(this, null);
                headerParser.length = initialOtherSideSettings.remaining();
                parser.parse(initialOtherSideSettings, headerParser);
                updateSettings(parser.getSettings());
            } catch (Throwable e) {
                IoUtils.safeClose(connectedStreamChannel);
                //should never happen
                throw new RuntimeException(e);
            }
        }
        int requestParseTimeout = settings.get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1);
        int requestIdleTimeout = settings.get(UndertowOptions.NO_REQUEST_TIMEOUT, -1);
        if(requestIdleTimeout < 0 && requestParseTimeout < 0) {
            this.parseTimeoutUpdater = null;
        } else {
            this.parseTimeoutUpdater = new ParseTimeoutUpdater(this, requestParseTimeout, requestIdleTimeout, new Runnable() {
                @Override
                public void run() {
                    sendGoAway(ERROR_NO_ERROR);
                    //just to make sure the connection is actually closed we give it 2 seconds
                    //then we forcibly kill the connection
                    getIoThread().executeAfter(new Runnable() {
                        @Override
                        public void run() {
                            IoUtils.safeClose(Http2Channel.this);
                        }
                    }, 2, TimeUnit.SECONDS);
                }
            });
            this.addCloseTask(new ChannelListener<Http2Channel>() {
                @Override
                public void handleEvent(Http2Channel channel) {
                    parseTimeoutUpdater.close();
                }
            });
        }
    }

    private void sendSettings() {
        List<Http2Setting> settings = new ArrayList<>();
        settings.add(new Http2Setting(Http2Setting.SETTINGS_HEADER_TABLE_SIZE, encoderHeaderTableSize));
        if(isClient()) {
            settings.add(new Http2Setting(Http2Setting.SETTINGS_ENABLE_PUSH, pushEnabled ? 1 : 0));
        }
        settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_FRAME_SIZE, receiveMaxFrameSize));
        settings.add(new Http2Setting(Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE, initialReceiveWindowSize));
        if(maxHeaderListSize > 0) {
            settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_HEADER_LIST_SIZE, maxHeaderListSize));
        }
        if(receiveMaxConcurrentStreams > 0) {
            settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS, receiveMaxConcurrentStreams));
        }
        Http2SettingsStreamSinkChannel stream = new Http2SettingsStreamSinkChannel(this, settings);
        flushChannelIgnoreFailure(stream);
    }

    private void sendSettingsAck() {
        if(!initialSettingsSent) {
            sendSettings();
            initialSettingsSent = true;
        }
        Http2SettingsStreamSinkChannel stream = new Http2SettingsStreamSinkChannel(this);
        flushChannelIgnoreFailure(stream);
    }

    private void flushChannelIgnoreFailure(StreamSinkChannel stream) {
        try {
            flushChannel(stream);
        } catch (IOException e) {
            UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
        } catch (Throwable t) {
            UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t);
        }
    }

    private void flushChannel(StreamSinkChannel stream) throws IOException {
        stream.shutdownWrites();
        if (!stream.flush()) {
            stream.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, writeExceptionHandler()));
            stream.resumeWrites();
        }
    }

    private void sendPreface() {
        Http2PrefaceStreamSinkChannel preface = new Http2PrefaceStreamSinkChannel(this);
        flushChannelIgnoreFailure(preface);
    }
    @Override
    protected AbstractHttp2StreamSourceChannel createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException {
        AbstractHttp2StreamSourceChannel channel = createChannelImpl(frameHeaderData, frameData);
        if(channel instanceof Http2StreamSourceChannel) {
            if (parseTimeoutUpdater != null) {
                if (channel != null) {
                    parseTimeoutUpdater.requestStarted();
                } else if (currentStreams.isEmpty()) {
                    parseTimeoutUpdater.failedParse();
                }
            }
        }
        return channel;
    }

    protected AbstractHttp2StreamSourceChannel createChannelImpl(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException {

        Http2FrameHeaderParser frameParser = (Http2FrameHeaderParser) frameHeaderData;
        AbstractHttp2StreamSourceChannel channel;
        if (frameParser.type == FRAME_TYPE_DATA) {
            //DATA frames must be already associated with a connection. If it gets here then something is wrong
            //spec explicitly calls this out as a connection error
            sendGoAway(ERROR_PROTOCOL_ERROR);
            UndertowLogger.REQUEST_LOGGER.tracef("Dropping Frame of length %s for stream %s", frameParser.getFrameLength(), frameParser.streamId);
            return null;
        }
        //note that not all frame types are covered here, as some are only relevant to already active streams
        //if which case they are handled by the existing channel support
        switch (frameParser.type) {

            case FRAME_TYPE_CONTINUATION:
            case FRAME_TYPE_PUSH_PROMISE: {
                //this is some 'clever' code to deal with both types continuation (push_promise and headers)
                //if the continuation is not a push promise it falls through to the headers code
                if(frameParser.parser instanceof Http2PushPromiseParser) {
                    if(!isClient()) {
                        sendGoAway(ERROR_PROTOCOL_ERROR);
                        throw UndertowMessages.MESSAGES.serverReceivedPushPromise();
                    }
                    Http2PushPromiseParser pushPromiseParser = (Http2PushPromiseParser) frameParser.parser;
                    channel = new Http2PushPromiseStreamSourceChannel(this, frameData, frameParser.getFrameLength(), pushPromiseParser.getHeaderMap(), pushPromiseParser.getPromisedStreamId(), frameParser.streamId);
                    break;
                }
                //fall through
            }
            case FRAME_TYPE_HEADERS: {
                if(!isIdle(frameParser.streamId)) {
                    //this is an existing stream
                    //make sure it exists
                    StreamHolder existing = currentStreams.get(frameParser.streamId);
                    if(existing == null || existing.sourceClosed) {
                        sendGoAway(ERROR_PROTOCOL_ERROR);
                        frameData.close();
                        return null;
                    } else if (existing.sourceChannel != null ){
                        //if exists
                        //make sure it has END_STREAM set
                        if(!Bits.allAreSet(frameParser.flags, HEADERS_FLAG_END_STREAM)) {
                            sendGoAway(ERROR_PROTOCOL_ERROR);
                            frameData.close();
                            return null;
                        }
                    }
                } else {
                    if(frameParser.streamId < getLastAssignedStreamOtherSide()) {
                        sendGoAway(ERROR_PROTOCOL_ERROR);
                        frameData.close();
                        return null;
                    }
                    if(frameParser.streamId % 2 == (isClient() ? 1 : 0)) {
                        sendGoAway(ERROR_PROTOCOL_ERROR);
                        frameData.close();
                        return null;
                    }
                }
                Http2HeadersParser parser = (Http2HeadersParser) frameParser.parser;

                channel = new Http2StreamSourceChannel(this, frameData, frameHeaderData.getFrameLength(), parser.getHeaderMap(), frameParser.streamId);

                updateStreamIdsCountersInHeaders(frameParser.streamId);

                StreamHolder holder = currentStreams.get(frameParser.streamId);
                if(holder == null) {
                    receiveConcurrentStreamsAtomicUpdater.getAndIncrement(this);
                    currentStreams.put(frameParser.streamId, holder = new StreamHolder((Http2StreamSourceChannel) channel));
                } else {
                    holder.sourceChannel = (Http2StreamSourceChannel) channel;
                }
                if (parser.isHeadersEndStream() && Bits.allAreSet(frameParser.flags, HEADERS_FLAG_END_HEADERS)) {
                    channel.lastFrame();
                    holder.sourceChannel = null;
                    //this is yuck
                    if(!isClient() || !"100".equals(parser.getHeaderMap().getFirst(STATUS))) {
                        holder.sourceClosed = true;
                        if(holder.sinkClosed) {
                            receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this);
                            currentStreams.remove(frameParser.streamId);
                        }
                    }
                }
                if(parser.isInvalid()) {
                    channel.rstStream(ERROR_PROTOCOL_ERROR);
                    sendRstStream(frameParser.streamId, Http2Channel.ERROR_PROTOCOL_ERROR);
                    channel = null;
                }
                if(parser.getDependentStreamId() == frameParser.streamId) {
                    sendRstStream(frameParser.streamId, ERROR_PROTOCOL_ERROR);
                    frameData.close();
                    return null;
                }
                break;
            }
            case FRAME_TYPE_RST_STREAM: {
                Http2RstStreamParser parser = (Http2RstStreamParser) frameParser.parser;
                if (frameParser.streamId == 0) {
                    if(frameData != null) {
                        frameData.close();
                    }
                    throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(FRAME_TYPE_RST_STREAM));
                }
                channel = new Http2RstStreamStreamSourceChannel(this, frameData, parser.getErrorCode(), frameParser.streamId);
                handleRstStream(frameParser.streamId);
                if(isIdle(frameParser.streamId)) {
                    sendGoAway(ERROR_PROTOCOL_ERROR);
                }
                break;
            }
            case FRAME_TYPE_SETTINGS: {
                if (!Bits.anyAreSet(frameParser.flags, SETTINGS_FLAG_ACK)) {
                    if(updateSettings(((Http2SettingsParser) frameParser.parser).getSettings())) {
                        sendSettingsAck();
                    }
                } else if (frameHeaderData.getFrameLength() != 0) {
                    sendGoAway(ERROR_FRAME_SIZE_ERROR);
                    frameData.close();
                    return null;
                }
                channel = new Http2SettingsStreamSourceChannel(this, frameData, frameParser.getFrameLength(), ((Http2SettingsParser) frameParser.parser).getSettings());
                unackedReceiveMaxFrameSize = receiveMaxFrameSize;
                break;
            }
            case FRAME_TYPE_PING: {
                Http2PingParser pingParser = (Http2PingParser) frameParser.parser;
                frameData.close();
                boolean ack = Bits.anyAreSet(frameParser.flags, PING_FLAG_ACK);
                channel = new Http2PingStreamSourceChannel(this, pingParser.getData(), ack);
                if(!ack) { //not an ack from one of our pings, so send it back
                    sendPing(pingParser.getData(),  new Http2ControlMessageExceptionHandler(), true);
                }
                break;
            }
            case FRAME_TYPE_GOAWAY: {
                Http2GoAwayParser http2GoAwayParser = (Http2GoAwayParser) frameParser.parser;
                channel = new Http2GoAwayStreamSourceChannel(this, frameData, frameParser.getFrameLength(), http2GoAwayParser.getStatusCode(), http2GoAwayParser.getLastGoodStreamId());
                peerGoneAway = true;
                //the peer is going away
                //everything is broken
                for(StreamHolder holder : currentStreams.values()) {
                    if(holder.sourceChannel != null) {
                        holder.sourceChannel.rstStream();
                    }
                    if(holder.sinkChannel != null) {
                        holder.sinkChannel.rstStream();
                    }
                }
                frameData.close();
                sendGoAway(ERROR_NO_ERROR);
                break;
            }
            case FRAME_TYPE_WINDOW_UPDATE: {
                Http2WindowUpdateParser parser = (Http2WindowUpdateParser) frameParser.parser;
                handleWindowUpdate(frameParser.streamId, parser.getDeltaWindowSize());
                frameData.close();
                //we don't return window update notifications, they are handled internally
                return null;
            }
            case FRAME_TYPE_PRIORITY: {
                Http2PriorityParser parser = (Http2PriorityParser) frameParser.parser;
                if(parser.getStreamDependency() == frameParser.streamId) {
                    //according to the spec this is a stream error
                    sendRstStream(frameParser.streamId, ERROR_PROTOCOL_ERROR);
                    return null;
                }
                frameData.close();
                //we don't return priority notifications, they are handled internally
                return null;
            }
            default: {
                UndertowLogger.REQUEST_LOGGER.tracef("Dropping frame of length %s and type %s for stream %s as we do not understand this type of frame", frameParser.getFrameLength(), frameParser.type, frameParser.streamId);
                frameData.close();
                return null;
            }
        }
        return channel;
    }

    @Override
    protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException {
        if (prefaceCount < PREFACE_BYTES.length) {
            while (data.hasRemaining() && prefaceCount < PREFACE_BYTES.length) {
                if (data.get() != PREFACE_BYTES[prefaceCount]) {
                    IoUtils.safeClose(getUnderlyingConnection());
                    throw UndertowMessages.MESSAGES.incorrectHttp2Preface();
                }
                prefaceCount++;
            }
        }
        Http2FrameHeaderParser frameParser = this.frameParser;
        if (frameParser == null) {
            this.frameParser = frameParser = new Http2FrameHeaderParser(this, continuationParser);
            this.continuationParser = null;
        }
        if (!frameParser.handle(data)) {
            return null;
        }
        if (!initialSettingsReceived) {
            if (frameParser.type != FRAME_TYPE_SETTINGS) {
                UndertowLogger.REQUEST_IO_LOGGER.remoteEndpointFailedToSendInitialSettings(frameParser.type);
                //StringBuilder sb = new StringBuilder();
                //while (data.hasRemaining()) {
                //    sb.append((char)data.get());
                //    sb.append(" ");
                //}
                markReadsBroken(new IOException());
            } else {
                initialSettingsReceived = true;
            }
        }
        this.frameParser = null;
        if (frameParser.getActualLength() > receiveMaxFrameSize && frameParser.getActualLength() > unackedReceiveMaxFrameSize) {
            sendGoAway(ERROR_FRAME_SIZE_ERROR);
            throw UndertowMessages.MESSAGES.http2FrameTooLarge();
        }
        if (frameParser.getContinuationParser() != null) {
            this.continuationParser = frameParser.getContinuationParser();
            return null;
        }
        return frameParser;

    }

    protected void lastDataRead() {
        lastDataRead = true;
        if(!peerGoneAway) {
            //we just close the connection, as the peer has performed an unclean close
            IoUtils.safeClose(this);
        } else {
            peerGoneAway = true;
            if(!thisGoneAway) {
                //we send a goaway message, and then close
                sendGoAway(ERROR_CONNECT_ERROR);
            }
        }
    }

    @Override
    protected boolean isLastFrameReceived() {
        return lastDataRead;
    }

    @Override
    protected boolean isLastFrameSent() {
        return thisGoneAway;
    }

    @Override
    protected void handleBrokenSourceChannel(Throwable e) {
        UndertowLogger.REQUEST_LOGGER.debugf(e, "Closing HTTP2 channel to %s due to broken read side", getPeerAddress());
        if (e instanceof ConnectionErrorException) {
            sendGoAway(((ConnectionErrorException) e).getCode(), new Http2ControlMessageExceptionHandler());
        } else {
            sendGoAway(e instanceof ClosedChannelException ? Http2Channel.ERROR_CONNECT_ERROR : Http2Channel.ERROR_PROTOCOL_ERROR, new Http2ControlMessageExceptionHandler());
        }
    }

    @Override
    protected void handleBrokenSinkChannel(Throwable e) {
        UndertowLogger.REQUEST_LOGGER.debugf(e, "Closing HTTP2 channel to %s due to broken write side", getPeerAddress());
        //the write side is broken, so we can't even send GO_AWAY
        //just tear down the TCP connection
        IoUtils.safeClose(this);
    }

    @Override
    protected void closeSubChannels() {

        for (Map.Entry<Integer, StreamHolder> e : currentStreams.entrySet()) {
            StreamHolder holder = e.getValue();
            AbstractHttp2StreamSourceChannel receiver = holder.sourceChannel;
            if(receiver != null) {
                receiver.markStreamBroken();
            }
            Http2StreamSinkChannel sink = holder.sinkChannel;
            if(sink != null) {
                if (sink.isWritesShutdown()) {
                    ChannelListeners.invokeChannelListener(sink.getIoThread(), sink, ((ChannelListener.SimpleSetter) sink.getWriteSetter()).get());
                }
                IoUtils.safeClose(sink);
            }

        }
    }

    @Override
    protected Collection<AbstractFramedStreamSourceChannel<Http2Channel, AbstractHttp2StreamSourceChannel, AbstractHttp2StreamSinkChannel>> getReceivers() {
        List<AbstractFramedStreamSourceChannel<Http2Channel, AbstractHttp2StreamSourceChannel, AbstractHttp2StreamSinkChannel>> channels = new ArrayList<>(currentStreams.size());
        for(Map.Entry<Integer, StreamHolder> entry : currentStreams.entrySet()) {
            if(!entry.getValue().sourceClosed) {
                channels.add(entry.getValue().sourceChannel);
            }
        }
        return channels;
    }

    /**
     * Setting have been received from the client
     *
     * @param settings
     */
    boolean updateSettings(List<Http2Setting> settings) {
        for (Http2Setting setting : settings) {
            if (setting.getId() == Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE) {
                synchronized (flowControlLock) {
                    if (setting.getValue() > Integer.MAX_VALUE) {
                        sendGoAway(ERROR_FLOW_CONTROL_ERROR);
                        return false;
                    }
                    initialSendWindowSize = (int) setting.getValue();
                }

            } else if (setting.getId() == Http2Setting.SETTINGS_MAX_FRAME_SIZE) {
                if(setting.getValue() > MAX_FRAME_SIZE || setting.getValue() < DEFAULT_MAX_FRAME_SIZE) {
                    UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid value received for SETTINGS_MAX_FRAME_SIZE " + setting.getValue());
                    sendGoAway(ERROR_PROTOCOL_ERROR);
                    return false;
                }
                sendMaxFrameSize = (int) setting.getValue();
            } else if (setting.getId() == Http2Setting.SETTINGS_HEADER_TABLE_SIZE) {
                synchronized (this) {
                    encoder.setMaxTableSize((int) setting.getValue());
                }
            } else if (setting.getId() == Http2Setting.SETTINGS_ENABLE_PUSH) {

                int result = (int) setting.getValue();
                //we allow the remote endpoint to disable push
                //but not enable it if it has been explictly disabled on this side
                if(result == 0) {
                    pushEnabled = false;
                } else if(result != 1) {
                    //invalid value
                    UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid value received for SETTINGS_ENABLE_PUSH " + result);
                    sendGoAway(ERROR_PROTOCOL_ERROR);
                    return false;
                }
            } else if (setting.getId() == Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS) {
                sendMaxConcurrentStreams = (int) setting.getValue();
            }
            //ignore the rest for now
        }
        return true;
    }

    public int getHttp2Version() {
        return 3;
    }

    public int getInitialSendWindowSize() {
        return initialSendWindowSize;
    }

    public int getInitialReceiveWindowSize() {
        return initialReceiveWindowSize;
    }

    public int getSendMaxConcurrentStreams() {
        return sendMaxConcurrentStreams;
    }

    public void setSendMaxConcurrentStreams(int sendMaxConcurrentStreams) {
        this.sendMaxConcurrentStreams = sendMaxConcurrentStreams;
        sendSettings();
    }

    public int getReceiveMaxConcurrentStreams() {
        return receiveMaxConcurrentStreams;
    }

    public void handleWindowUpdate(int streamId, int deltaWindowSize) throws IOException {
        if (streamId == 0) {
            if (deltaWindowSize == 0) {
                UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid flow-control window increment of 0 received with WINDOW_UPDATE frame for connection");
                sendGoAway(ERROR_PROTOCOL_ERROR);
                return;
            }

            synchronized (flowControlLock) {
                boolean exhausted = sendWindowSize <= FLOW_CONTROL_MIN_WINDOW; //

                sendWindowSize += deltaWindowSize;
                if (exhausted) {
                    notifyFlowControlAllowed();
                }
                if (sendWindowSize > Integer.MAX_VALUE) {
                    sendGoAway(ERROR_FLOW_CONTROL_ERROR);
                }
            }
        } else {
            if (deltaWindowSize == 0) {
                UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid flow-control window increment of 0 received with WINDOW_UPDATE frame for stream " + streamId);
                sendRstStream(streamId, ERROR_PROTOCOL_ERROR);
                return;
            }
            StreamHolder holder = currentStreams.get(streamId);
            Http2StreamSinkChannel stream = holder != null ? holder.sinkChannel : null;
            if (stream == null) {
                if(isIdle(streamId)) {
                    sendGoAway(ERROR_PROTOCOL_ERROR);
                }
            } else {
                stream.updateFlowControlWindow(deltaWindowSize);
            }
        }
    }

    synchronized void notifyFlowControlAllowed() throws IOException {
        super.recalculateHeldFrames();
    }

    public void sendPing(byte[] data) {
        sendPing(data, new Http2ControlMessageExceptionHandler());
    }

    public void sendPing(byte[] data, final ChannelExceptionHandler<AbstractHttp2StreamSinkChannel> exceptionHandler) {
        sendPing(data, exceptionHandler, false);
    }

    void sendPing(byte[] data, final ChannelExceptionHandler<AbstractHttp2StreamSinkChannel> exceptionHandler, boolean ack) {
        Http2PingStreamSinkChannel ping = new Http2PingStreamSinkChannel(this, data, ack);
        try {
            ping.shutdownWrites();
            if (!ping.flush()) {
                ping.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, exceptionHandler));
                ping.resumeWrites();
            }
        } catch (IOException e) {
            if(exceptionHandler != null) {
                exceptionHandler.handleException(ping, e);
            } else {
                UndertowLogger.REQUEST_LOGGER.debug("Failed to send ping and no exception handler set", e);
            }
        } catch (Throwable t) {
            if(exceptionHandler != null) {
                exceptionHandler.handleException(ping, new IOException(t));
            } else {
                UndertowLogger.REQUEST_LOGGER.debug("Failed to send ping and no exception handler set", t);
            }
        }
    }

    public void sendGoAway(int status) {
        sendGoAway(status, new Http2ControlMessageExceptionHandler());
    }

    public void sendGoAway(int status, final ChannelExceptionHandler<AbstractHttp2StreamSinkChannel> exceptionHandler) {
        if (thisGoneAway) {
            return;
        }
        thisGoneAway = true;
        if(UndertowLogger.REQUEST_IO_LOGGER.isTraceEnabled()) {
            UndertowLogger.REQUEST_IO_LOGGER.tracef(new ClosedChannelException(), "Sending goaway on channel %s", this);
        }
        Http2GoAwayStreamSinkChannel goAway = new Http2GoAwayStreamSinkChannel(this, status, getLastGoodStreamId());
        try {
            goAway.shutdownWrites();
            if (!goAway.flush()) {
                goAway.getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener<Channel>() {
                    @Override
                    public void handleEvent(Channel channel) {
                        IoUtils.safeClose(Http2Channel.this);
                    }
                }, exceptionHandler));
                goAway.resumeWrites();
            } else {
                IoUtils.safeClose(this);
            }
        } catch (IOException e) {
            exceptionHandler.handleException(goAway, e);
        } catch (Throwable t) {
            exceptionHandler.handleException(goAway, new IOException(t));
        }
    }

    public void sendUpdateWindowSize(int streamId, int delta) throws IOException {
        Http2WindowUpdateStreamSinkChannel windowUpdateStreamSinkChannel = new Http2WindowUpdateStreamSinkChannel(this, streamId, delta);
        flushChannel(windowUpdateStreamSinkChannel);

    }

    public SSLSession getSslSession() {
        StreamConnection con = getUnderlyingConnection();
        if (con instanceof SslConnection) {
            return ((SslConnection) con).getSslSession();
        }
        return null;
    }

    public void updateReceiveFlowControlWindow(int read) throws IOException {
        if (read <= 0) {
            return;
        }
        int delta = -1;
        synchronized (flowControlLock) {
            receiveWindowSize -= read;
            //TODO: make this configurable, we should be able to set the policy that is used to determine when to update the window size
            int initialWindowSize = this.initialReceiveWindowSize;
            if (receiveWindowSize < (initialWindowSize / 2)) {
                delta = initialWindowSize - receiveWindowSize;
                receiveWindowSize += delta;
            }
        }
        if(delta > 0) {
            sendUpdateWindowSize(0, delta);
        }
    }

    /**
     * Creates a strema using a HEADERS frame
     *
     * @param requestHeaders
     * @return
     * @throws IOException
     */
    public synchronized Http2HeadersStreamSinkChannel createStream(HeaderMap requestHeaders) throws IOException {
        if (!isClient()) {
            throw UndertowMessages.MESSAGES.headersStreamCanOnlyBeCreatedByClient();
        }
        if (!isOpen()) {
            throw UndertowMessages.MESSAGES.channelIsClosed();
        }
        sendConcurrentStreamsAtomicUpdater.incrementAndGet(this);
        if(sendMaxConcurrentStreams > 0 && sendConcurrentStreams > sendMaxConcurrentStreams) {
            throw UndertowMessages.MESSAGES.streamLimitExceeded();
        }
        int streamId = streamIdCounter;
        streamIdCounter += 2;
        Http2HeadersStreamSinkChannel http2SynStreamStreamSinkChannel = new Http2HeadersStreamSinkChannel(this, streamId, requestHeaders);
        currentStreams.put(streamId, new StreamHolder(http2SynStreamStreamSinkChannel));

        return http2SynStreamStreamSinkChannel;
    }

    /**
     * Adds a received pushed stream into the current streams for a client. The
     * stream is added into the currentStream and lastAssignedStreamOtherSide is incremented.
     *
     * @param pushedStreamId The pushed stream returned by the server
     * @return true if pushedStreamId can be added, false if invalid
     * @throws IOException General error like not being a client or odd stream id
     */
    public synchronized boolean addPushPromiseStream(int pushedStreamId) throws IOException {
        if (!isClient() || pushedStreamId % 2 != 0) {
            throw UndertowMessages.MESSAGES.pushPromiseCanOnlyBeCreatedByServer();
        }
        if (!isOpen()) {
            throw UndertowMessages.MESSAGES.channelIsClosed();
        }
        if (!isIdle(pushedStreamId)) {
            UndertowLogger.REQUEST_IO_LOGGER.debugf("Non idle streamId %d received from the server as a pushed stream.", pushedStreamId);
            return false;
        }
        StreamHolder holder = new StreamHolder((Http2HeadersStreamSinkChannel) null);
        holder.sinkClosed = true;
        lastAssignedStreamOtherSide = Math.max(lastAssignedStreamOtherSide, pushedStreamId);
        currentStreams.put(pushedStreamId, holder);
        return true;
    }

    private synchronized int getLastAssignedStreamOtherSide() {
        return lastAssignedStreamOtherSide;
    }

    private synchronized int getLastGoodStreamId() {
        return lastGoodStreamId;
    }

    /**
     * Updates the lastGoodStreamId (last request ID to send in goaway frames),
     * and lastAssignedStreamOtherSide (the last received streamId from the other
     * side to check if it's idle). The lastAssignedStreamOtherSide in a server
     * is the same as lastGoodStreamId but in a client push promises can be
     * received and check for idle is different.
     *
     * @param streamNo The received streamId for the client or the server
     */
    private synchronized void updateStreamIdsCountersInHeaders(int streamNo) {
        if (streamNo % 2 != 0) {
            // the last good stream is always the last client ID sent by the client or received by the server
            lastGoodStreamId = Math.max(lastGoodStreamId, streamNo);
            if (!isClient()) {
                 // server received client request ID => update the last assigned for the server
                 lastAssignedStreamOtherSide = lastGoodStreamId;
            }
        } else if (isClient()) {
            // client received push promise => update the last assigned for the client
            lastAssignedStreamOtherSide = Math.max(lastAssignedStreamOtherSide, streamNo);
        }
    }

    public synchronized Http2HeadersStreamSinkChannel sendPushPromise(int associatedStreamId, HeaderMap requestHeaders, HeaderMap responseHeaders) throws IOException {
        if (!isOpen()) {
            throw UndertowMessages.MESSAGES.channelIsClosed();
        }
        if (isClient()) {
            throw UndertowMessages.MESSAGES.pushPromiseCanOnlyBeCreatedByServer();
        }
        sendConcurrentStreamsAtomicUpdater.incrementAndGet(this);
        if(sendMaxConcurrentStreams > 0 && sendConcurrentStreams > sendMaxConcurrentStreams) {
            throw UndertowMessages.MESSAGES.streamLimitExceeded();
        }
        int streamId = streamIdCounter;
        streamIdCounter += 2;
        Http2PushPromiseStreamSinkChannel pushPromise = new Http2PushPromiseStreamSinkChannel(this, requestHeaders, associatedStreamId, streamId);
        flushChannel(pushPromise);

        Http2HeadersStreamSinkChannel http2SynStreamStreamSinkChannel = new Http2HeadersStreamSinkChannel(this, streamId, responseHeaders);
        currentStreams.put(streamId, new StreamHolder(http2SynStreamStreamSinkChannel));
        return http2SynStreamStreamSinkChannel;
    }

    /**
     * Try and decrement the send window by the given amount of bytes.
     *
     * @param bytesToGrab The amount of bytes the sender is trying to send
     * @return The actual amount of bytes the sender can send
     */
    int grabFlowControlBytes(int bytesToGrab) {
        if(bytesToGrab <= 0) {
            return 0;
        }
        int min;
        synchronized (flowControlLock) {
            min = (int) Math.min(bytesToGrab, sendWindowSize);
            if (bytesToGrab > FLOW_CONTROL_MIN_WINDOW && min <= FLOW_CONTROL_MIN_WINDOW) {
                //this can cause problems with padding, so we just return 0
                return 0;
            }
            min = Math.min(sendMaxFrameSize, min);
            sendWindowSize -= min;
        }
        return min;
    }

    void registerStreamSink(Http2HeadersStreamSinkChannel synResponse) {
        StreamHolder existing = currentStreams.get(synResponse.getStreamId());
        if(existing == null) {
            throw UndertowMessages.MESSAGES.streamNotRegistered();
        }
        existing.sinkChannel = synResponse;
    }

    void removeStreamSink(int streamId) {
        StreamHolder existing = currentStreams.get(streamId);
        if(existing == null) {
            return;
        }
        existing.sinkClosed = true;
        existing.sinkChannel = null;
        if(existing.sourceClosed) {
            if(streamId % 2 == (isClient() ? 1 : 0)) {
                sendConcurrentStreamsAtomicUpdater.getAndDecrement(this);
            } else {
                receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this);
            }
            currentStreams.remove(streamId);
        }
        if(isLastFrameReceived() && currentStreams.isEmpty()) {
            sendGoAway(ERROR_NO_ERROR);
        } else if(parseTimeoutUpdater != null && currentStreams.isEmpty()) {
            parseTimeoutUpdater.connectionIdle();
        }
    }

    public boolean isClient() {
        return streamIdCounter % 2 == 1;
    }

    HpackEncoder getEncoder() {
        return encoder;
    }

    HpackDecoder getDecoder() {
        return decoder;
    }

    int getMaxHeaders() {
        return maxHeaders;
    }

    int getPaddingBytes() {
        if(paddingRandom == null) {
            return 0;
        }
        return paddingRandom.nextInt(maxPadding);
    }

    @Override
    public <T> T getAttachment(AttachmentKey<T> key) {
        if (key == null) {
            throw UndertowMessages.MESSAGES.argumentCannotBeNull("key");
        }
        return (T) attachments.get(key);
    }

    @Override
    public <T> List<T> getAttachmentList(AttachmentKey<? extends List<T>> key) {
        if (key == null) {
            throw UndertowMessages.MESSAGES.argumentCannotBeNull("key");
        }
        Object o = attachments.get(key);
        if (o == null) {
            return Collections.emptyList();
        }
        return (List) o;
    }

    @Override
    public <T> T putAttachment(AttachmentKey<T> key, T value) {
        if (key == null) {
            throw UndertowMessages.MESSAGES.argumentCannotBeNull("key");
        }
        return key.cast(attachments.put(key, key.cast(value)));
    }

    @Override
    public <T> T removeAttachment(AttachmentKey<T> key) {
        return key.cast(attachments.remove(key));
    }

    @Override
    public <T> void addToAttachmentList(AttachmentKey<AttachmentList<T>> key, T value) {

        if (key == null) {
            throw UndertowMessages.MESSAGES.argumentCannotBeNull("key");
        }
        final Map<AttachmentKey<?>, Object> attachments = this.attachments;
        synchronized (attachments) {
            final List<T> list = key.cast(attachments.get(key));
            if (list == null) {
                final AttachmentList<T> newList = new AttachmentList<>((Class<T>) Object.class);
                attachments.put(key, newList);
                newList.add(value);
            } else {
                list.add(value);
            }
        }
    }

    public void sendRstStream(int streamId, int statusCode) {
        if(!isOpen()) {
            //no point sending if the channel is closed
            return;
        }
        handleRstStream(streamId);
        if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) {
            UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Sending rststream on channel %s stream %s", this, streamId);
        }
        Http2RstStreamSinkChannel channel = new Http2RstStreamSinkChannel(this, streamId, statusCode);
        flushChannelIgnoreFailure(channel);
    }

    private void handleRstStream(int streamId) {
        StreamHolder holder = currentStreams.remove(streamId);
        if(holder != null) {
            if(streamId % 2 == (isClient() ? 1 : 0)) {
                sendConcurrentStreamsAtomicUpdater.getAndDecrement(this);
            } else {
                receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this);
            }
            if (holder.sinkChannel != null) {
                holder.sinkChannel.rstStream();
            }
            if (holder.sourceChannel != null) {
                holder.sourceChannel.rstStream();
            }
        }
    }

    /**
     * Creates a response stream to respond to the initial HTTP upgrade
     *
     * @return
     */
    public synchronized Http2HeadersStreamSinkChannel createInitialUpgradeResponseStream() {
        if (lastGoodStreamId != 0) {
            throw new IllegalStateException();
        }
        updateStreamIdsCountersInHeaders(1);
        Http2HeadersStreamSinkChannel stream = new Http2HeadersStreamSinkChannel(this, 1);
        StreamHolder streamHolder = new StreamHolder(stream);
        streamHolder.sourceClosed = true;
        currentStreams.put(1, streamHolder);
        receiveConcurrentStreamsAtomicUpdater.getAndIncrement(this);
        return stream;

    }

    public boolean isPushEnabled() {
        return pushEnabled;
    }

    public boolean isPeerGoneAway() {
        return peerGoneAway;
    }

    public boolean isThisGoneAway() {
        return thisGoneAway;
    }

    Http2StreamSourceChannel removeStreamSource(int streamId) {
        StreamHolder existing = currentStreams.get(streamId);
        if(existing == null){
            return null;
        }
        existing.sourceClosed = true;
        Http2StreamSourceChannel ret = existing.sourceChannel;
        existing.sourceChannel = null;
        if(existing.sinkClosed) {
            if(streamId % 2 == (isClient() ? 1 : 0)) {
                sendConcurrentStreamsAtomicUpdater.getAndDecrement(this);
            } else {
                receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this);
            }
            currentStreams.remove(streamId);
        }
        return ret;
    }

    Http2StreamSourceChannel getIncomingStream(int streamId) {
        StreamHolder existing = currentStreams.get(streamId);
        if(existing == null){
            return null;
        }
        return existing.sourceChannel;
    }

    private class Http2ControlMessageExceptionHandler implements ChannelExceptionHandler<AbstractHttp2StreamSinkChannel> {
        @Override
        public void handleException(AbstractHttp2StreamSinkChannel channel, IOException exception) {
            IoUtils.safeClose(channel);
            handleBrokenSinkChannel(exception);
        }
    }

    public int getReceiveMaxFrameSize() {
        return receiveMaxFrameSize;
    }

    public int getSendMaxFrameSize() {
        return sendMaxFrameSize;
    }

    public String getProtocol() {
        return protocol;
    }

    private synchronized boolean isIdle(int streamNo) {
        if(streamNo % 2 == streamIdCounter % 2) {
            // our side is controlled by us in the generated streamIdCounter
            return streamNo >= streamIdCounter;
        } else {
            // the other side should increase lastAssignedStreamOtherSide all the time
            return streamNo > lastAssignedStreamOtherSide;
        }
    }

    int getMaxHeaderListSize() {
        return maxHeaderListSize;
    }

    private static final class StreamHolder {
        boolean sourceClosed = false;
        boolean sinkClosed = false;
        Http2StreamSourceChannel sourceChannel;
        Http2StreamSinkChannel sinkChannel;

        StreamHolder(Http2StreamSourceChannel sourceChannel) {
            this.sourceChannel = sourceChannel;
        }

        StreamHolder(Http2StreamSinkChannel sinkChannel) {
            this.sinkChannel = sinkChannel;
        }
    }
}

Output:
【漏洞类型】HTTP2 Continuation Frame Parsing Issue

【严重性】High

【漏洞摘要】此代码在处理HTTP2协议的Continuation Frame时存在严重问题。具体表现为在解析Continuation Frame时，如果没有正确处理所有续帧，可能会导致后续的帧被错误地解析或忽略，从而可能引发协议错误或拒绝服务攻击。根本原因在于`parseFrame`方法中，当`frameParser.getContinuationParser() != null`时，代码会立即返回`null`，而不继续处理剩余的数据。这一行为违反了HTTP2协议规范，导致续帧未被正确读取和处理。

【关键代码模式】
- `parseFrame`方法
- `frameParser.getContinuationParser() != null`条件
- `Http2FrameHeaderParser`类
- `Http2Channel`类

【修复方法说明】修复方法通过添加`parseFrameNoContinuation`方法来确保在处理Continuation Frame时，即使当前帧需要续帧，也会在有剩余数据的情况下继续解析，从而保证所有续帧都被正确读取和处理。这一修复方法遵循了HTTP2协议规范，确保了协议的正确性和安全性。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnPasswordlessAuthenticatorFactory.java
### 代码内容：
package org.keycloak.authentication.authenticators.browser;

import org.keycloak.authentication.Authenticator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.credential.WebAuthnCredentialModel;

/**
 * @author Marek Posolda
 */
public class WebAuthnPasswordlessAuthenticatorFactory extends WebAuthnAuthenticatorFactory {

    public static final String PROVIDER_ID = "webauthn-authenticator-passwordless";

    @Override
    public String getReferenceCategory() {
        return WebAuthnCredentialModel.TYPE_PASSWORDLESS;
    }

    @Override
    public String getDisplayType() {
        return "WebAuthn Passwordless Authenticator";
    }

    @Override
    public String getHelpText() {
        return "Authenticator for Passwordless WebAuthn authentication";
    }

    @Override
    public Authenticator create(KeycloakSession session) {
        return new WebAuthnPasswordlessAuthenticator(session);
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
}

Output:
【漏洞类型】业务逻辑安全漏洞
【严重性】High
【漏洞摘要】此代码存在一个安全漏洞，因为用户可以在没有密码或令牌的情况下被劫持。具体表现为`WebAuthnPasswordlessAuthenticatorFactory`类没有实现`isUserSetupAllowed`方法，这意味着默认情况下允许用户设置，从而可能导致账户在注册或删除令牌时被未经授权的用户访问。
【关键代码模式】`WebAuthnPasswordlessAuthenticatorFactory`类未实现`isUserSetupAllowed`方法。
【修复方法说明】修复方法通过添加`isUserSetupAllowed`方法并返回`false`，禁止用户设置，从而防止账户在注册或删除令牌时被未经授权的用户访问。

## 根本原因分析

在原始代码中，`WebAuthnPasswordlessAuthenticatorFactory`类继承自`WebAuthnAuthenticatorFactory`类，并未重写`isUserSetupAllowed`方法。根据Keycloak的认证器设计，如果`isUserSetupAllowed`方法未被实现或返回`true`，则允许用户在注册或删除令牌时进行设置。这可能导致在特定条件下（例如，用户注册时没有密码或删除了最后一个令牌），账户可以被未经授权的用户访问和劫持。

## 修复方法说明

修复方法通过在`WebAuthnPasswordlessAuthenticatorFactory`类中添加`isUserSetupAllowed`方法并返回`false`，禁止用户进行设置。这意味着即使在注册或删除令牌时，也不会允许用户更改密码或令牌，从而防止账户被未经授权的用户访问和劫持。

## 最佳实践或安全编码技术

1. **遵循认证器设计规范**：在实现认证器时，确保遵循Keycloak或其他认证框架的认证器设计规范，特别是与用户设置和认证流程相关的部分。
2. **最小权限原则**：确保用户只能进行必要的操作，避免允许不必要的用户设置，从而降低安全风险。
3. **输入验证**：在处理用户输入时，始终进行严格的验证和清理，以防止注入攻击和其他安全问题。
4. **日志记录和监控**：在关键操作和认证流程中，记录详细的日志，并部署监控系统以检测和响应异常行为。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
services/src/main/java/org/keycloak/authentication/authenticators/browser/WebAuthnPasswordlessAuthenticatorFactory.java
### 代码内容：
package org.keycloak.authentication.authenticators.browser;

import org.keycloak.authentication.Authenticator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.credential.WebAuthnCredentialModel;

/**
 * @author Marek Posolda
 */
public class WebAuthnPasswordlessAuthenticatorFactory extends WebAuthnAuthenticatorFactory {

    public static final String PROVIDER_ID = "webauthn-authenticator-passwordless";

    @Override
    public String getReferenceCategory() {
        return WebAuthnCredentialModel.TYPE_PASSWORDLESS;
    }

    @Override
    public String getDisplayType() {
        return "WebAuthn Passwordless Authenticator";
    }

    @Override
    public String getHelpText() {
        return "Authenticator for Passwordless WebAuthn authentication";
    }

    @Override
    public Authenticator create(KeycloakSession session) {
        return new WebAuthnPasswordlessAuthenticator(session);
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
}

Output:
【漏洞类型】安全配置问题
【严重性】High
【漏洞摘要】这是由于未正确配置密码less认证机制导致的安全漏洞。具体表现为，用户注册时可能不设置密码，或者删除了最后一个认证令牌，从而使得攻击者能够在该时刻登录并获取账户信息。关键问题是未验证用户在注册或登录过程中的状态，导致潜在的风险。
【关键代码模式】与认证机制相关的代码，如`create`方法和认证令牌的管理。
【修复方法说明】通过覆盖`isUserSetupAllowed`方法并返回`false`，防止用户在没有密码或认证令牌的情况下进行注册或登录，从而解决了安全配置问题。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
controller/src/main/java/org/jboss/as/controller/access/constraint/SensitiveVaultExpressionConstraint.java
### 代码内容：
package org.jboss.as.controller.access.constraint;

import java.util.regex.Pattern;

import org.jboss.as.controller.access.Action;
import org.jboss.as.controller.access.JmxAction;
import org.jboss.as.controller.access.JmxTarget;
import org.jboss.as.controller.access.TargetAttribute;
import org.jboss.as.controller.access.TargetResource;
import org.jboss.as.controller.access.rbac.StandardRole;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;

/**
 * {@link Constraint} related to whether an attribute is considered security sensitive
 * because it contains a vault expression.
 *
 * @author Brian Stansberry (c) 2013 Red Hat Inc.
 */
public class SensitiveVaultExpressionConstraint extends AllowAllowNotConstraint {

    public static final ConstraintFactory FACTORY = new Factory();

    private static final Pattern VAULT_EXPRESSION_PATTERN = Pattern.compile(".*\\$\\{VAULT::.*::.*::.*}.*");

    private static final SensitiveVaultExpressionConstraint SENSITIVE = new SensitiveVaultExpressionConstraint(true);
    private static final SensitiveVaultExpressionConstraint NOT_SENSITIVE = new SensitiveVaultExpressionConstraint(false);
    private static final SensitiveVaultExpressionConstraint ALLOWS = new SensitiveVaultExpressionConstraint(true, true);
    private static final SensitiveVaultExpressionConstraint DISALLOWS = new SensitiveVaultExpressionConstraint(false, true);

    private SensitiveVaultExpressionConstraint(boolean sensitive) {
        super(sensitive);
    }

    private SensitiveVaultExpressionConstraint(boolean allowsSensitive, boolean allowsNonSensitive) {
        super(allowsSensitive, allowsNonSensitive);
    }

    private static class Factory extends AbstractConstraintFactory {

        @Override
        public Constraint getStandardUserConstraint(StandardRole role, Action.ActionEffect actionEffect) {
            if (role == StandardRole.ADMINISTRATOR
                    || role == StandardRole.SUPERUSER
                    || role == StandardRole.AUDITOR) {
                return ALLOWS;
            }
            return DISALLOWS;
        }

        @Override
        public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetAttribute target) {
            return isSensitiveAction(action, actionEffect, target) ? SENSITIVE : NOT_SENSITIVE;
        }

        @Override
        public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetResource target) {
            return isSensitiveAction(action, actionEffect) ? SENSITIVE : NOT_SENSITIVE;
        }

        private boolean isSensitiveAction(Action action, Action.ActionEffect actionEffect) {
            if (VaultExpressionSensitivityConfig.INSTANCE.isSensitive(actionEffect)) {
                if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
                    ModelNode operation = action.getOperation();
                    for (Property property : operation.asPropertyList()) {
                        if (isSensitiveValue(property.getValue())) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private boolean isSensitiveAction(Action action, Action.ActionEffect actionEffect, TargetAttribute targetAttribute) {
            if (VaultExpressionSensitivityConfig.INSTANCE.isSensitive(actionEffect)) {
                if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
                    ModelNode operation = action.getOperation();
                    if (operation.hasDefined(targetAttribute.getAttributeName())) {
                        if (isSensitiveValue(operation.get(targetAttribute.getAttributeName()))) {
                            return true;
                        }
                    }
                    if (ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION.equals(operation.get(ModelDescriptionConstants.OP).asString())
                            && operation.hasDefined(ModelDescriptionConstants.VALUE)) {
                        if (isSensitiveValue(operation.get(ModelDescriptionConstants.VALUE))) {
                            return true;
                        }
                    }
                }
                if (actionEffect != Action.ActionEffect.ADDRESS) {
                    if (isSensitiveValue(targetAttribute.getCurrentValue())) {
                        return true;
                    }
                }
            }
            return false;
        }

        private boolean isSensitiveValue(ModelNode value) {
            if (value.getType() == ModelType.EXPRESSION
                    || value.getType() == ModelType.STRING) {
                String valueString = value.asString();
                return VAULT_EXPRESSION_PATTERN.matcher(valueString).matches();
            }
            return false;
        }

        @Override
        protected int internalCompare(AbstractConstraintFactory other) {
            // We have no preference
            return 0;
        }

        @Override
        public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, JmxAction action, JmxTarget target) {
            //TODO We could do something like this if the action provided the new value and the target
            // provided the current value. But right now that data isn't provided.
//            if (VaultExpressionSensitivityConfig.INSTANCE.isSensitive(actionEffect)) {
//                if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
//                    if (action.getNewValue() instanceof String && isSensitiveValue(new ModelNode(action.getNewValue().toString()))) {
//                        return SENSITIVE;
//                    }
//                }
//                if (actionEffect != Action.ActionEffect.ADDRESS) {
//                    if (target.getCurrentValue() instanceof String && isSensitiveValue(new ModelNode(target.getCurrentValue().toString()))) {
//                        return SENSITIVE;
//                    }
//                }
//            }
            return NOT_SENSITIVE;
        }
    }
}


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
controller/src/main/java/org/jboss/as/controller/access/constraint/SensitiveVaultExpressionConstraint.java
### 代码内容：
package org.jboss.as.controller.access.constraint;

import java.util.regex.Pattern;

import org.jboss.as.controller.access.Action;
import org.jboss.as.controller.access.JmxAction;
import org.jboss.as.controller.access.JmxTarget;
import org.jboss.as.controller.access.TargetAttribute;
import org.jboss.as.controller.access.TargetResource;
import org.jboss.as.controller.access.rbac.StandardRole;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;

/**
 * {@link Constraint} related to whether an attribute is considered security sensitive
 * because it contains a vault expression.
 *
 * @author Brian Stansberry (c) 2013 Red Hat Inc.
 */
public class SensitiveVaultExpressionConstraint extends AllowAllowNotConstraint {

    public static final ConstraintFactory FACTORY = new Factory();

    private static final Pattern VAULT_EXPRESSION_PATTERN = Pattern.compile(".*\\$\\{VAULT::.*::.*::.*}.*");

    private static final SensitiveVaultExpressionConstraint SENSITIVE = new SensitiveVaultExpressionConstraint(true);
    private static final SensitiveVaultExpressionConstraint NOT_SENSITIVE = new SensitiveVaultExpressionConstraint(false);
    private static final SensitiveVaultExpressionConstraint ALLOWS = new SensitiveVaultExpressionConstraint(true, true);
    private static final SensitiveVaultExpressionConstraint DISALLOWS = new SensitiveVaultExpressionConstraint(false, true);

    private SensitiveVaultExpressionConstraint(boolean sensitive) {
        super(sensitive);
    }

    private SensitiveVaultExpressionConstraint(boolean allowsSensitive, boolean allowsNonSensitive) {
        super(allowsSensitive, allowsNonSensitive);
    }

    private static class Factory extends AbstractConstraintFactory {

        @Override
        public Constraint getStandardUserConstraint(StandardRole role, Action.ActionEffect actionEffect) {
            if (role == StandardRole.ADMINISTRATOR
                    || role == StandardRole.SUPERUSER
                    || role == StandardRole.AUDITOR) {
                return ALLOWS;
            }
            return DISALLOWS;
        }

        @Override
        public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetAttribute target) {
            return isSensitiveAction(action, actionEffect, target) ? SENSITIVE : NOT_SENSITIVE;
        }

        @Override
        public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, Action action, TargetResource target) {
            return isSensitiveAction(action, actionEffect) ? SENSITIVE : NOT_SENSITIVE;
        }

        private boolean isSensitiveAction(Action action, Action.ActionEffect actionEffect) {
            if (VaultExpressionSensitivityConfig.INSTANCE.isSensitive(actionEffect)) {
                if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
                    ModelNode operation = action.getOperation();
                    for (Property property : operation.asPropertyList()) {
                        if (isSensitiveValue(property.getValue())) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private boolean isSensitiveAction(Action action, Action.ActionEffect actionEffect, TargetAttribute targetAttribute) {
            if (VaultExpressionSensitivityConfig.INSTANCE.isSensitive(actionEffect)) {
                if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
                    ModelNode operation = action.getOperation();
                    if (operation.hasDefined(targetAttribute.getAttributeName())) {
                        if (isSensitiveValue(operation.get(targetAttribute.getAttributeName()))) {
                            return true;
                        }
                    }
                    if (ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION.equals(operation.get(ModelDescriptionConstants.OP).asString())
                            && operation.hasDefined(ModelDescriptionConstants.VALUE)) {
                        if (isSensitiveValue(operation.get(ModelDescriptionConstants.VALUE))) {
                            return true;
                        }
                    }
                }
                if (actionEffect != Action.ActionEffect.ADDRESS) {
                    if (isSensitiveValue(targetAttribute.getCurrentValue())) {
                        return true;
                    }
                }
            }
            return false;
        }

        private boolean isSensitiveValue(ModelNode value) {
            if (value.getType() == ModelType.EXPRESSION
                    || value.getType() == ModelType.STRING) {
                String valueString = value.asString();
                return VAULT_EXPRESSION_PATTERN.matcher(valueString).matches();
            }
            return false;
        }

        @Override
        protected int internalCompare(AbstractConstraintFactory other) {
            // We have no preference
            return 0;
        }

        @Override
        public Constraint getRequiredConstraint(Action.ActionEffect actionEffect, JmxAction action, JmxTarget target) {
            //TODO We could do something like this if the action provided the new value and the target
            // provided the current value. But right now that data isn't provided.
//            if (VaultExpressionSensitivityConfig.INSTANCE.isSensitive(actionEffect)) {
//                if (actionEffect == Action.ActionEffect.WRITE_RUNTIME || actionEffect == Action.ActionEffect.WRITE_CONFIG) {
//                    if (action.getNewValue() instanceof String && isSensitiveValue(new ModelNode(action.getNewValue().toString()))) {
//                        return SENSITIVE;
//                    }
//                }
//                if (actionEffect != Action.ActionEffect.ADDRESS) {
//                    if (target.getCurrentValue() instanceof String && isSensitiveValue(new ModelNode(target.getCurrentValue().toString()))) {
//                        return SENSITIVE;
//                    }
//                }
//            }
            return NOT_SENSITIVE;
        }
    }
}

Output:
【漏洞类型】安全逻辑漏洞
【严重性】High
【漏洞摘要】原始代码中的`isSensitiveValue`函数使用了复杂的正则表达式匹配和字符串操作来判断一个值是否包含敏感的Vault表达式。然而，修复后的代码简单地使用了一个静态的正则表达式`VAULT_EXPRESSION_PATTERN`来匹配字符串，这可能会导致无法正确识别所有类型的Vault表达式，从而导致安全敏感信息的误判或漏判。

在修复后的代码中，问题主要在于`isSensitiveValue`函数的简化。原始代码中，首先通过`ExpressionResolver.EXPRESSION_PATTERN`匹配整个字符串，然后提取表达式的内容，再使用`VaultReader.STANDARD_VAULT_PATTERN`进一步判断提取的内容是否符合Vault表达式的标准。而修复后的代码直接使用`VAULT_EXPRESSION_PATTERN`来匹配字符串，这可能会忽略某些复杂的Vault表达式或误判某些非Vault表达式。

根本原因在于修复过程中，对Vault表达式识别的逻辑被简化，导致了识别的不全面和可能的误判。

【关键代码模式】
- `isSensitiveValue`函数
- `ExpressionResolver.EXPRESSION_PATTERN`
- `VaultReader.STANDARD_VAULT_PATTERN`
- `Pattern`类和正则表达式匹配

【修复方法说明】修复方法通过直接使用一个静态的正则表达式来匹配Vault表达式，简化了识别逻辑。然而，这种方法可能会导致漏判或误判安全敏感信息。最佳实践是确保所有可能的Vault表达式格式都被正确识别，可以考虑使用更复杂的匹配逻辑或增加更多的测试用例来验证。

【修复方法说明】修复方法简化了Vault表达式的识别逻辑，但可能需要进一步验证以确保所有安全敏感信息都被正确识别。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
services/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
### 代码内容：
package org.keycloak.theme;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Properties;

/**
 * @author Stian Thorgersen
 */
public class ClassLoaderTheme implements Theme {

    private String name;

    private String parentName;

    private String importName;

    private Type type;

    private ClassLoader classLoader;

    private String templateRoot;

    private String resourceRoot;

    private String messageRoot;

    private Properties properties;

    public ClassLoaderTheme(String name, Type type, ClassLoader classLoader) throws IOException {
        init(name, type, classLoader);
    }

    public void init(String name, Type type, ClassLoader classLoader) throws IOException {
        this.name = name;
        this.type = type;
        this.classLoader = classLoader;

        String themeRoot = "theme/" + name + "/" + type.toString().toLowerCase() + "/";

        this.templateRoot = themeRoot;
        this.resourceRoot = themeRoot + "resources/";
        this.messageRoot = themeRoot + "messages/";
        this.properties = new Properties();

        URL p = classLoader.getResource(themeRoot + "theme.properties");
        if (p != null) {
            Charset encoding = PropertiesUtil.detectEncoding(p.openStream());
            try (Reader reader = new InputStreamReader(p.openStream(), encoding)) {
                properties.load(reader);
            }
            this.parentName = properties.getProperty("parent");
            this.importName = properties.getProperty("import");
        } else {
            this.parentName = null;
            this.importName = null;
        }
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getParentName() {
        return parentName;
    }

    @Override
    public String getImportName() {
        return importName;
    }

    @Override
    public Type getType() {
        return type;
    }

    @Override
    public URL getTemplate(String name) {
        return classLoader.getResource(templateRoot + name);
    }

    @Override
    public InputStream getResourceAsStream(String path) {
        return classLoader.getResourceAsStream(resourceRoot + path);
    }

    @Override
    public Properties getMessages(Locale locale) throws IOException {
        return getMessages("messages", locale);
    }

    @Override
    public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
        if(locale == null){
            return null;
        }
        Properties m = new Properties();

        URL url = classLoader.getResource(this.messageRoot + baseBundlename + "_" + locale.toString() + ".properties");
        if (url != null) {
            Charset encoding = PropertiesUtil.detectEncoding(url.openStream());
            try (Reader reader = new InputStreamReader(url.openStream(), encoding)) {
                m.load(reader);
            }
        }
        return m;
    }

    @Override
    public Properties getProperties() {
        return properties;
    }

}

## 内容2
### 文件路径：
services/src/main/java/org/keycloak/theme/ClasspathThemeResourceProviderFactory.java
### 代码内容：
package org.keycloak.theme;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Properties;

import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

public class ClasspathThemeResourceProviderFactory implements ThemeResourceProviderFactory, ThemeResourceProvider {

    public static final String THEME_RESOURCES_TEMPLATES = "theme-resources/templates/";
    public static final String THEME_RESOURCES_RESOURCES = "theme-resources/resources/";
    public static final String THEME_RESOURCES_MESSAGES = "theme-resources/messages/";

    private final String id;
    private final ClassLoader classLoader;

    public ClasspathThemeResourceProviderFactory(String id, ClassLoader classLoader) {
        this.id = id;
        this.classLoader = classLoader;
    }

    @Override
    public ThemeResourceProvider create(KeycloakSession session) {
        return this;
    }

    @Override
    public URL getTemplate(String name) throws IOException {
        return classLoader.getResource(THEME_RESOURCES_TEMPLATES + name);
    }

    @Override
    public InputStream getResourceAsStream(String path) throws IOException {
        return classLoader.getResourceAsStream(THEME_RESOURCES_RESOURCES + path);
    }

    @Override
    public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
        Properties m = new Properties();
        InputStream in = classLoader.getResourceAsStream(THEME_RESOURCES_MESSAGES + baseBundlename + "_" + locale.toString() + ".properties");
        if(in != null){
            Charset encoding = PropertiesUtil.detectEncoding(in);
            // detectEncoding closes the stream
            try (Reader reader = new InputStreamReader(
                        classLoader.getResourceAsStream(THEME_RESOURCES_MESSAGES + baseBundlename + "_" + locale.toString() + ".properties"), encoding)) {
                m.load(reader);
            }
        }
        return m;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void init(Config.Scope config) {
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
    }

    @Override
    public void close() {
    }

}

Output:
【漏洞类型】路径遍历漏洞

【严重性】High

【漏洞摘要】路径遍历漏洞允许攻击者通过构造特定的输入路径来读取或访问服务器上的任意文件。在原始代码中，`ClassLoaderTheme` 和 `ClasspathThemeResourceProviderFactory` 类的 `getResourceAsStream` 方法直接将用户输入的路径拼接到 `resourceRoot` 或 `THEME_RESOURCES_RESOURCES` 上，然后通过类加载器获取资源流。如果攻击者能够控制 `path` 参数，他们可以利用路径遍历攻击读取任意文件。

具体表现为：
```java
@Override
public InputStream getResourceAsStream(String path) {
    return classLoader.getResourceAsStream(resourceRoot + path);
}
```

根本原因分析：
- 用户输入的 `path` 参数未被验证，攻击者可以构造包含 `..` 或其他路径遍历字符的路径，从而访问任意文件。
- 类加载器的 `getResourceAsStream` 方法会直接解析拼接后的路径，导致路径遍历漏洞。

关键代码模式与关键词：
- `getResourceAsStream` 方法
- 用户输入的路径参数
- 类加载器的 `getResourceAsStream` 方法
- 路径遍历攻击

【修复方法说明】修复方法通过检查资源 URL 的路径是否以根路径开头来防止路径遍历攻击。
- 检查 `resourceRoot` 或 `THEME_RESOURCES_RESOURCES` 是否为空。
- 获取根路径并存储在 `rootPath` 变量中。
- 检查拼接后的资源路径是否以根路径开头，如果不是，则返回 `null`。
- 如果路径有效，则通过 `openConnection().getInputStream()` 获取资源流。

适用的最佳实践或安全编码技术：
- 对用户输入进行严格的路径验证。
- 避免直接拼接用户输入的路径，尤其是在处理文件资源时。
- 使用安全的文件访问方法来限制访问范围。