Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java
### 代码内容：
package io.netty.handler.codec.compression;

import static io.netty.handler.codec.compression.Bzip2Constants.*;

/**
 * Reads and decompresses a single Bzip2 block.

 *
 * Block decoding consists of the following stages:

 * 1. Read block header

 * 2. Read Huffman tables

 * 3. Read and decode Huffman encoded data - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder)}

 * 4. Run-Length Decoding[2] - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder)}

 * 5. Inverse Move To Front Transform - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder)}

 * 6. Inverse Burrows Wheeler Transform - {@link #initialiseInverseBWT()}

 * 7. Run-Length Decoding[1] - {@link #read()}

 * 8. Optional Block De-Randomisation - {@link #read()} (through {@link #decodeNextBWTByte()})
 */
final class Bzip2BlockDecompressor {
    /**
     * A reader that provides bit-level reads.
     */
    private final Bzip2BitReader reader;

    /**
     * Calculates the block CRC from the fully decoded bytes of the block.
     */
    private final Crc32 crc = new Crc32();

    /**
     * The CRC of the current block as read from the block header.
     */
    private final int blockCRC;

    /**
     * {@code true} if the current block is randomised, otherwise {@code false}.
     */
    private final boolean blockRandomised;

    /* Huffman Decoding stage */
    /**
     * The end-of-block Huffman symbol. Decoding of the block ends when this is encountered.
     */
    int huffmanEndOfBlockSymbol;

    /**
     * Bitmap, of ranges of 16 bytes, present/not present.
     */
    int huffmanInUse16;

    /**
     * A map from Huffman symbol index to output character. Some types of data (e.g. ASCII text)
     * may contain only a limited number of byte values; Huffman symbols are only allocated to
     * those values that actually occur in the uncompressed data.
     */
    final byte[] huffmanSymbolMap = new byte[256];

    /* Move To Front stage */
    /**
     * Counts of each byte value within the {@link Bzip2BlockDecompressor#huffmanSymbolMap} data.
     * Collected at the Move To Front stage, consumed by the Inverse Burrows Wheeler Transform stage.
     */
    private final int[] bwtByteCounts = new int[256];

    /**
     * The Burrows-Wheeler Transform processed data. Read at the Move To Front stage, consumed by the
     * Inverse Burrows Wheeler Transform stage.
     */
    private final byte[] bwtBlock;

    /**
     * Starting pointer into BWT for after untransform.
     */
    private final int bwtStartPointer;

    /* Inverse Burrows-Wheeler Transform stage */
    /**
     * At each position contains the union of :-
     *   An output character (8 bits)
     *   A pointer from each position to its successor (24 bits, left shifted 8 bits)
     * As the pointer cannot exceed the maximum block size of 900k, 24 bits is more than enough to
     * hold it; Folding the character data into the spare bits while performing the inverse BWT,
     * when both pieces of information are available, saves a large number of memory accesses in
     * the final decoding stages.
     */
    private int[] bwtMergedPointers;

    /**
     * The current merged pointer into the Burrow-Wheeler Transform array.
     */
    private int bwtCurrentMergedPointer;

    /**
     * The actual length in bytes of the current block at the Inverse Burrows Wheeler Transform
     * stage (before final Run-Length Decoding).
     */
    private int bwtBlockLength;

    /**
     * The number of output bytes that have been decoded up to the Inverse Burrows Wheeler Transform stage.
     */
    private int bwtBytesDecoded;

    /* Run-Length Encoding and Random Perturbation stage */
    /**
     * The most recently RLE decoded byte.
     */
    private int rleLastDecodedByte = -1;

    /**
     * The number of previous identical output bytes decoded. After 4 identical bytes, the next byte
     * decoded is an RLE repeat count.
     */
    private int rleAccumulator;

    /**
     * The RLE repeat count of the current decoded byte. When this reaches zero, a new byte is decoded.
     */
    private int rleRepeat;

    /**
     * If the current block is randomised, the position within the RNUMS randomisation array.
     */
    private int randomIndex;

    /**
     * If the current block is randomised, the remaining count at the current RNUMS position.
     */
    private int randomCount = Bzip2Rand.rNums(0) - 1;

    /**
     * Table for Move To Front transformations.
     */
    private final Bzip2MoveToFrontTable symbolMTF = new Bzip2MoveToFrontTable();

    // This variables is used to save current state if we haven't got enough readable bits
    private int repeatCount;
    private int repeatIncrement = 1;
    private int mtfValue;

    Bzip2BlockDecompressor(final int blockSize, final int blockCRC, final boolean blockRandomised,
                           final int bwtStartPointer, final Bzip2BitReader reader) {

        bwtBlock = new byte[blockSize];

        this.blockCRC = blockCRC;
        this.blockRandomised = blockRandomised;
        this.bwtStartPointer = bwtStartPointer;

        this.reader = reader;
    }

    /**
     * Reads the Huffman encoded data from the input stream, performs Run-Length Decoding and
     * applies the Move To Front transform to reconstruct the Burrows-Wheeler Transform array.
     */
    boolean decodeHuffmanData(final Bzip2HuffmanStageDecoder huffmanDecoder) {
        final Bzip2BitReader reader = this.reader;
        final byte[] bwtBlock = this.bwtBlock;
        final byte[] huffmanSymbolMap = this.huffmanSymbolMap;
        final int streamBlockSize = this.bwtBlock.length;
        final int huffmanEndOfBlockSymbol = this.huffmanEndOfBlockSymbol;
        final int[] bwtByteCounts = this.bwtByteCounts;
        final Bzip2MoveToFrontTable symbolMTF = this.symbolMTF;

        int bwtBlockLength = this.bwtBlockLength;
        int repeatCount = this.repeatCount;
        int repeatIncrement = this.repeatIncrement;
        int mtfValue = this.mtfValue;

        for (;;) {
            if (!reader.hasReadableBits(HUFFMAN_DECODE_MAX_CODE_LENGTH)) {
                this.bwtBlockLength = bwtBlockLength;
                this.repeatCount = repeatCount;
                this.repeatIncrement = repeatIncrement;
                this.mtfValue = mtfValue;
                return false;
            }
            final int nextSymbol = huffmanDecoder.nextSymbol();

            if (nextSymbol == HUFFMAN_SYMBOL_RUNA) {
                repeatCount += repeatIncrement;
                repeatIncrement <<= 1;
            } else if (nextSymbol == HUFFMAN_SYMBOL_RUNB) {
                repeatCount += repeatIncrement << 1;
                repeatIncrement <<= 1;
            } else {
                if (repeatCount > 0) {
                    if (bwtBlockLength + repeatCount > streamBlockSize) {
                        throw new DecompressionException("block exceeds declared block size");
                    }
                    final byte nextByte = huffmanSymbolMap[mtfValue];
                    bwtByteCounts[nextByte & 0xff] += repeatCount;
                    while (--repeatCount >= 0) {
                        bwtBlock[bwtBlockLength++] = nextByte;
                    }

                    repeatCount = 0;
                    repeatIncrement = 1;
                }

                if (nextSymbol == huffmanEndOfBlockSymbol) {
                    break;
                }

                if (bwtBlockLength >= streamBlockSize) {
                    throw new DecompressionException("block exceeds declared block size");
                }

                mtfValue = symbolMTF.indexToFront(nextSymbol - 1) & 0xff;

                final byte nextByte = huffmanSymbolMap[mtfValue];
                bwtByteCounts[nextByte & 0xff]++;
                bwtBlock[bwtBlockLength++] = nextByte;
            }
        }
        this.bwtBlockLength = bwtBlockLength;
        initialiseInverseBWT();
        return true;
    }

    /**
     * Set up the Inverse Burrows-Wheeler Transform merged pointer array.
     */
    private void initialiseInverseBWT() {
        final int bwtStartPointer = this.bwtStartPointer;
        final byte[] bwtBlock  = this.bwtBlock;
        final int[] bwtMergedPointers = new int[bwtBlockLength];
        final int[] characterBase = new int[256];

        if (bwtStartPointer < 0 || bwtStartPointer >= bwtBlockLength) {
            throw new DecompressionException("start pointer invalid");
        }

        // Cumulative character counts
        System.arraycopy(bwtByteCounts, 0, characterBase, 1, 255);
        for (int i = 2; i <= 255; i++) {
            characterBase[i] += characterBase[i - 1];
        }

        // Merged-Array Inverse Burrows-Wheeler Transform
        // Combining the output characters and forward pointers into a single array here, where we
        // have already read both of the corresponding values, cuts down on memory accesses in the
        // final walk through the array
        for (int i = 0; i < bwtBlockLength; i++) {
            int value = bwtBlock[i] & 0xff;
            bwtMergedPointers[characterBase[value]++] = (i << 8) + value;
        }

        this.bwtMergedPointers = bwtMergedPointers;
        bwtCurrentMergedPointer = bwtMergedPointers[bwtStartPointer];
    }

    /**
     * Decodes a byte from the final Run-Length Encoding stage, pulling a new byte from the
     * Burrows-Wheeler Transform stage when required.
     * @return The decoded byte, or -1 if there are no more bytes
     */
    public int read() {
        while (rleRepeat < 1) {
            if (bwtBytesDecoded == bwtBlockLength) {
                return -1;
            }

            int nextByte = decodeNextBWTByte();
            if (nextByte != rleLastDecodedByte) {
                // New byte, restart accumulation
                rleLastDecodedByte = nextByte;
                rleRepeat = 1;
                rleAccumulator = 1;
                crc.updateCRC(nextByte);
            } else {
                if (++rleAccumulator == 4) {
                    // Accumulation complete, start repetition
                    int rleRepeat = decodeNextBWTByte() + 1;
                    this.rleRepeat = rleRepeat;
                    rleAccumulator = 0;
                    crc.updateCRC(nextByte, rleRepeat);
                } else {
                    rleRepeat = 1;
                    crc.updateCRC(nextByte);
                }
            }
        }
        rleRepeat--;

        return rleLastDecodedByte;
    }

    /**
     * Decodes a byte from the Burrows-Wheeler Transform stage. If the block has randomisation
     * applied, reverses the randomisation.
     * @return The decoded byte
     */
    private int decodeNextBWTByte() {
        int mergedPointer = bwtCurrentMergedPointer;
        int nextDecodedByte =  mergedPointer & 0xff;
        bwtCurrentMergedPointer = bwtMergedPointers[mergedPointer >>> 8];

        if (blockRandomised) {
            if (--randomCount == 0) {
                nextDecodedByte ^= 1;
                randomIndex = (randomIndex + 1) % 512;
                randomCount = Bzip2Rand.rNums(randomIndex);
            }
        }
        bwtBytesDecoded++;

        return nextDecodedByte;
    }

    public int blockLength() {
        return bwtBlockLength;
    }

    /**
     * Verify and return the block CRC. This method may only be called
     * after all of the block's bytes have been read.
     * @return The block CRC
     */
    int checkCRC() {
        final int computedBlockCRC = crc.getCRC();
        if (blockCRC != computedBlockCRC) {
            throw new DecompressionException("block CRC error");
        }
        return computedBlockCRC;
    }
}

## 内容2
### 文件路径：
codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java
### 代码内容：
package io.netty.handler.codec.compression;

/**
 * Constants for both the {@link Bzip2Encoder} and the {@link Bzip2Decoder}.
 */
final class Bzip2Constants {

    /**
     * Magic number of Bzip2 stream.
     */
    static final int MAGIC_NUMBER = 'B' << 16 | 'Z' << 8 | 'h';

    /**
     * Block header magic number. Equals to BCD (pi).
     */
    static final int BLOCK_HEADER_MAGIC_1 = 0x314159;
    static final int BLOCK_HEADER_MAGIC_2 = 0x265359;

    /**
     * End of stream magic number. Equals to BCD sqrt(pi).
     */
    static final int END_OF_STREAM_MAGIC_1 = 0x177245;
    static final int END_OF_STREAM_MAGIC_2 = 0x385090;

    /**
     * Base block size.
     */
    static final int BASE_BLOCK_SIZE = 100000;

    /**
     * Minimum and maximum size of one block.
     * Must be multiplied by {@link Bzip2Constants#BASE_BLOCK_SIZE}.
     */
    static final int MIN_BLOCK_SIZE = 1;
    static final int MAX_BLOCK_SIZE = 9;

    /**
     * Maximum possible Huffman alphabet size.
     */
    static final int HUFFMAN_MAX_ALPHABET_SIZE = 258;

    /**
     * The longest Huffman code length created by the encoder.
     */
    static final int HUFFMAN_ENCODE_MAX_CODE_LENGTH = 20;

    /**
     * The longest Huffman code length accepted by the decoder.
     */
    static final int HUFFMAN_DECODE_MAX_CODE_LENGTH = 23;

    /**
     * Huffman symbols used for run-length encoding.
     */
    static final int HUFFMAN_SYMBOL_RUNA = 0;
    static final int HUFFMAN_SYMBOL_RUNB = 1;

    /**
     * Huffman symbols range size for Huffman used map.
     */
    static final int HUFFMAN_SYMBOL_RANGE_SIZE = 16;

    /**
     * Maximum length of zero-terminated bit runs of MTF'ed Huffman table.
     */
    static final int HUFFMAN_SELECTOR_LIST_MAX_LENGTH = 6;

    /**
     * Number of symbols decoded after which a new Huffman table is selected.
     */
    static final int HUFFMAN_GROUP_RUN_LENGTH = 50;

    /**
     * Maximum possible number of Huffman table selectors.
     */
    static final int MAX_SELECTORS = 2 + 900000 / HUFFMAN_GROUP_RUN_LENGTH; // 18002

    /**
     * Minimum number of alternative Huffman tables.
     */
    static final int HUFFMAN_MINIMUM_TABLES = 2;

    /**
     * Maximum number of alternative Huffman tables.
     */
    static final int HUFFMAN_MAXIMUM_TABLES = 6;

    private Bzip2Constants() { }
}

## 内容3
### 文件路径：
codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java
### 代码内容：
package io.netty.handler.codec.compression;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

import static io.netty.handler.codec.compression.Bzip2Constants.*;

/**
 * Uncompresses a {@link ByteBuf} encoded with the Bzip2 format.
 *
 * See Bzip2.
 */
public class Bzip2Decoder extends ByteToMessageDecoder {
    /**
     * Current state of stream.
     */
    private enum State {
        INIT,
        INIT_BLOCK,
        INIT_BLOCK_PARAMS,
        RECEIVE_HUFFMAN_USED_MAP,
        RECEIVE_HUFFMAN_USED_BITMAPS,
        RECEIVE_SELECTORS_NUMBER,
        RECEIVE_SELECTORS,
        RECEIVE_HUFFMAN_LENGTH,
        DECODE_HUFFMAN_DATA,
        EOF
    }
    private State currentState = State.INIT;

    /**
     * A reader that provides bit-level reads.
     */
    private final Bzip2BitReader reader = new Bzip2BitReader();

    /**
     * The decompressor for the current block.
     */
    private Bzip2BlockDecompressor blockDecompressor;

    /**
     * Bzip2 Huffman coding stage.
     */
    private Bzip2HuffmanStageDecoder huffmanStageDecoder;

    /**
     * Always: in the range 0 .. 9. The current block size is 100000 * this number.
     */
    private int blockSize;

    /**
     * The CRC of the current block as read from the block header.
     */
    private int blockCRC;

    /**
     * The merged CRC of all blocks decompressed so far.
     */
    private int streamCRC;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (!in.isReadable()) {
            return;
        }

        final Bzip2BitReader reader = this.reader;
        reader.setByteBuf(in);

        for (;;) {
            switch (currentState) {
            case INIT:
                if (in.readableBytes() < 4) {
                    return;
                }
                int magicNumber = in.readUnsignedMedium();
                if (magicNumber != MAGIC_NUMBER) {
                    throw new DecompressionException("Unexpected stream identifier contents. Mismatched bzip2 " +
                            "protocol version?");
                }
                int blockSize = in.readByte() - '0';
                if (blockSize < MIN_BLOCK_SIZE || blockSize > MAX_BLOCK_SIZE) {
                    throw new DecompressionException("block size is invalid");
                }
                this.blockSize = blockSize * BASE_BLOCK_SIZE;

                streamCRC = 0;
                currentState = State.INIT_BLOCK;
                 // fall through
            case INIT_BLOCK:
                if (!reader.hasReadableBytes(10)) {
                    return;
                }
                // Get the block magic bytes.
                final int magic1 = reader.readBits(24);
                final int magic2 = reader.readBits(24);
                if (magic1 == END_OF_STREAM_MAGIC_1 && magic2 == END_OF_STREAM_MAGIC_2) {
                    // End of stream was reached. Check the combined CRC.
                    final int storedCombinedCRC = reader.readInt();
                    if (storedCombinedCRC != streamCRC) {
                        throw new DecompressionException("stream CRC error");
                    }
                    currentState = State.EOF;
                    break;
                }
                if (magic1 != BLOCK_HEADER_MAGIC_1 || magic2 != BLOCK_HEADER_MAGIC_2) {
                    throw new DecompressionException("bad block header");
                }
                blockCRC = reader.readInt();
                currentState = State.INIT_BLOCK_PARAMS;
                // fall through
            case INIT_BLOCK_PARAMS:
                if (!reader.hasReadableBits(25)) {
                    return;
                }
                final boolean blockRandomised = reader.readBoolean();
                final int bwtStartPointer = reader.readBits(24);

                blockDecompressor = new Bzip2BlockDecompressor(this.blockSize, blockCRC,
                                                                blockRandomised, bwtStartPointer, reader);
                currentState = State.RECEIVE_HUFFMAN_USED_MAP;
                // fall through
            case RECEIVE_HUFFMAN_USED_MAP:
                if (!reader.hasReadableBits(16)) {
                    return;
                }
                blockDecompressor.huffmanInUse16 = reader.readBits(16);
                currentState = State.RECEIVE_HUFFMAN_USED_BITMAPS;
                // fall through
            case RECEIVE_HUFFMAN_USED_BITMAPS:
                Bzip2BlockDecompressor blockDecompressor = this.blockDecompressor;
                final int inUse16 = blockDecompressor.huffmanInUse16;
                final int bitNumber = Integer.bitCount(inUse16);
                final byte[] huffmanSymbolMap = blockDecompressor.huffmanSymbolMap;

                if (!reader.hasReadableBits(bitNumber * HUFFMAN_SYMBOL_RANGE_SIZE + 3)) {
                    return;
                }

                int huffmanSymbolCount = 0;
                if (bitNumber > 0) {
                    for (int i = 0; i < 16; i++) {
                        if ((inUse16 & 1 << 15 >>> i) != 0) {
                            for (int j = 0, k = i << 4; j < HUFFMAN_SYMBOL_RANGE_SIZE; j++, k++) {
                                if (reader.readBoolean()) {
                                    huffmanSymbolMap[huffmanSymbolCount++] = (byte) k;
                                }
                            }
                        }
                    }
                }
                blockDecompressor.huffmanEndOfBlockSymbol = huffmanSymbolCount + 1;

                int totalTables = reader.readBits(3);
                if (totalTables < HUFFMAN_MINIMUM_TABLES || totalTables > HUFFMAN_MAXIMUM_TABLES) {
                    throw new DecompressionException("incorrect huffman groups number");
                }
                int alphaSize = huffmanSymbolCount + 2;
                if (alphaSize > HUFFMAN_MAX_ALPHABET_SIZE) {
                    throw new DecompressionException("incorrect alphabet size");
                }
                huffmanStageDecoder = new Bzip2HuffmanStageDecoder(reader, totalTables, alphaSize);
                currentState = State.RECEIVE_SELECTORS_NUMBER;
                // fall through
            case RECEIVE_SELECTORS_NUMBER:
                if (!reader.hasReadableBits(15)) {
                    return;
                }
                int totalSelectors = reader.readBits(15);
                if (totalSelectors < 1 || totalSelectors > MAX_SELECTORS) {
                    throw new DecompressionException("incorrect selectors number");
                }
                huffmanStageDecoder.selectors = new byte[totalSelectors];

                currentState = State.RECEIVE_SELECTORS;
                // fall through
            case RECEIVE_SELECTORS:
                Bzip2HuffmanStageDecoder huffmanStageDecoder = this.huffmanStageDecoder;
                byte[] selectors = huffmanStageDecoder.selectors;
                totalSelectors = selectors.length;
                final Bzip2MoveToFrontTable tableMtf = huffmanStageDecoder.tableMTF;

                int currSelector;
                // Get zero-terminated bit runs (0..62) of MTF'ed Huffman table. length = 1..6
                for (currSelector = huffmanStageDecoder.currentSelector;
                            currSelector < totalSelectors; currSelector++) {
                    if (!reader.hasReadableBits(HUFFMAN_SELECTOR_LIST_MAX_LENGTH)) {
                        // Save state if end of current ByteBuf was reached
                        huffmanStageDecoder.currentSelector = currSelector;
                        return;
                    }
                    int index = 0;
                    while (reader.readBoolean()) {
                        index++;
                    }
                    selectors[currSelector] = tableMtf.indexToFront(index);
                }

                currentState = State.RECEIVE_HUFFMAN_LENGTH;
                // fall through
            case RECEIVE_HUFFMAN_LENGTH:
                huffmanStageDecoder = this.huffmanStageDecoder;
                totalTables = huffmanStageDecoder.totalTables;
                final byte[][] codeLength = huffmanStageDecoder.tableCodeLengths;
                alphaSize = huffmanStageDecoder.alphabetSize;

                /* Now the coding tables */
                int currGroup;
                int currLength = huffmanStageDecoder.currentLength;
                int currAlpha = 0;
                boolean modifyLength = huffmanStageDecoder.modifyLength;
                boolean saveStateAndReturn = false;
                loop: for (currGroup = huffmanStageDecoder.currentGroup; currGroup < totalTables; currGroup++) {
                    // start_huffman_length
                    if (!reader.hasReadableBits(5)) {
                        saveStateAndReturn = true;
                        break;
                    }
                    if (currLength < 0) {
                        currLength = reader.readBits(5);
                    }
                    for (currAlpha = huffmanStageDecoder.currentAlpha; currAlpha < alphaSize; currAlpha++) {
                        // delta_bit_length: 1..40
                        if (!reader.isReadable()) {
                            saveStateAndReturn = true;
                            break loop;
                        }
                        while (modifyLength || reader.readBoolean()) {  // 0=>next symbol; 1=>alter length
                            if (!reader.isReadable()) {
                                modifyLength = true;
                                saveStateAndReturn = true;
                                break loop;
                            }
                            // 1=>decrement length;  0=>increment length
                            currLength += reader.readBoolean() ? -1 : 1;
                            modifyLength = false;
                            if (!reader.isReadable()) {
                                saveStateAndReturn = true;
                                break loop;
                            }
                        }
                        codeLength[currGroup][currAlpha] = (byte) currLength;
                    }
                    currLength = -1;
                    currAlpha = huffmanStageDecoder.currentAlpha = 0;
                    modifyLength = false;
                }
                if (saveStateAndReturn) {
                    // Save state if end of current ByteBuf was reached
                    huffmanStageDecoder.currentGroup = currGroup;
                    huffmanStageDecoder.currentLength = currLength;
                    huffmanStageDecoder.currentAlpha = currAlpha;
                    huffmanStageDecoder.modifyLength = modifyLength;
                    return;
                }

                // Finally create the Huffman tables
                huffmanStageDecoder.createHuffmanDecodingTables();
                currentState = State.DECODE_HUFFMAN_DATA;
                // fall through
            case DECODE_HUFFMAN_DATA:
                blockDecompressor = this.blockDecompressor;
                final int oldReaderIndex = in.readerIndex();
                final boolean decoded = blockDecompressor.decodeHuffmanData(this.huffmanStageDecoder);
                if (!decoded) {
                    return;
                }
                // It used to avoid "Bzip2Decoder.decode() did not read anything but decoded a message" exception.
                // Because previous operation may read only a few bits from Bzip2BitReader.bitBuffer and
                // don't read incoming ByteBuf.
                if (in.readerIndex() == oldReaderIndex && in.isReadable()) {
                    reader.refill();
                }

                final int blockLength = blockDecompressor.blockLength();
                final ByteBuf uncompressed = ctx.alloc().buffer(blockLength);
                boolean success = false;
                try {
                    int uncByte;
                    while ((uncByte = blockDecompressor.read()) >= 0) {
                        uncompressed.writeByte(uncByte);
                    }

                    int currentBlockCRC = blockDecompressor.checkCRC();
                    streamCRC = (streamCRC << 1 | streamCRC >>> 31) ^ currentBlockCRC;

                    out.add(uncompressed);
                    success = true;
                } finally {
                    if (!success) {
                        uncompressed.release();
                    }
                }
                currentState = State.INIT_BLOCK;
                break;
            case EOF:
                in.skipBytes(in.readableBytes());
                return;
            default:
                throw new IllegalStateException();
            }
        }
    }

    /**
     * Returns {@code true} if and only if the end of the compressed stream
     * has been reached.
     */
    public boolean isClosed() {
        return currentState == State.EOF;
    }
}

Output:
【漏洞类型】缓冲区溢出
【严重性】High
【漏洞摘要】原始代码中存在一个缓冲区溢出漏洞，具体表现为在`decodeHuffmanData`函数中，如果解码后的数据长度超过预先声明的块大小（`streamBlockSize`），则会抛出`DecompressionException`。然而，如果输入数据被恶意篡改或损坏，可能会导致`bwtBlockLength`超过`MAX_BLOCK_LENGTH`，从而潜在地导致缓冲区溢出。根本原因是缺少对解码后块长度的上限检查。
【关键代码模式】
- `decodeHuffmanData`函数中的`bwtBlockLength`变量。
- `Bzip2Constants`类中的`MIN_BLOCK_SIZE`和`MAX_BLOCK_SIZE`常量。
- `Bzip2Decoder`类中的`decode`函数。
【修复方法说明】修复方法通过在`Bzip2Constants`类中添加`MAX_BLOCK_LENGTH`常量，并在`decodeHuffmanData`函数中增加对`bwtBlockLength`的上限检查，防止解码后的数据长度超过最大块大小，从而避免缓冲区溢出。此外，修改了`Bzip2Decoder`类中的`decode`函数，以确保在读取完所有数据后立即重置状态并进行CRC校验，从而提高了数据处理的流式特性。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/edu/stanford/nlp/time/XMLUtils.java
### 代码内容：
package edu.stanford.nlp.time;

import edu.stanford.nlp.io.StringOutputStream;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * XML Utility functions for use with dealing with Timex expressions
 *
 * @author Angel Chang
 */
public class XMLUtils {

  private static final Document document = createDocument();
  private static final TransformerFactory tFactory = TransformerFactory.newInstance();
  // todo: revert: According to the docs, neither TransformerFactory nor DocumentBuilderFactory is guaranteed threadsafe.
  // todo: A good application might make one of these per thread, but maybe easier just to revert to creating one each time, sigh.

  private XMLUtils() {} // static class

  public static String documentToString(Document document) {
    StringOutputStream s = new StringOutputStream();
    printNode(s, document, true, true);
    return s.toString();
  }

  public static String nodeToString(Node node, boolean prettyPrint) {
    StringOutputStream s = new StringOutputStream();
    printNode(s, node, prettyPrint, false);
    return s.toString();
  }

  public static void printNode(OutputStream out, Node node, boolean prettyPrint, boolean includeXmlDeclaration) {
    try {
      Transformer serializer = tFactory.newTransformer();
      if (prettyPrint) {
        //Setup indenting to "pretty print"
        serializer.setOutputProperty(OutputKeys.INDENT, "yes");
        serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
      }
      if ( ! includeXmlDeclaration) {
        serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
      }

      DOMSource xmlSource = new DOMSource(node);
      StreamResult outputTarget = new StreamResult(out);
      serializer.transform(xmlSource, outputTarget);
    } catch (TransformerException e) {
      throw new RuntimeException(e);
    }
  }

  public static Document createDocument() {
    try {
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = dbFactory.newDocumentBuilder();
      Document doc = docBuilder.newDocument();
      return doc;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public static Text createTextNode(String text) {
    return document.createTextNode(text);
  }

  public static Element createElement(String tag) {
    return document.createElement(tag);
  }

  public static Element parseElement(String xml) {
    try {
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = dbFactory.newDocumentBuilder();
      Document doc = docBuilder.parse(new ByteArrayInputStream(xml.getBytes()));
      return doc.getDocumentElement();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  // Like element.getAttribute except returns null if attribute not present
  public static String getAttribute(Element element, String name) {
    Attr attr = element.getAttributeNode(name);
    return (attr != null)? attr.getValue(): null;
  }

  public static void removeChildren(Node e) {
    NodeList list = e.getChildNodes();
    for (int i = 0; i < list.getLength(); i++) {
      Node n = list.item(i);
      e.removeChild(n);
    }
  }
  private static void getMatchingNodes(Node node, Pattern[] nodePath, int cur, List<Node> res) {
    if (cur < 0 || cur >= nodePath.length) return;
    boolean last = (cur == nodePath.length-1);
    Pattern pattern = nodePath[cur];
    NodeList children = node.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node c = children.item(i);
      if (pattern.matcher(c.getNodeName()).matches()) {
        if (last) {
          res.add(c);
        } else {
          getMatchingNodes(c, nodePath, cur+1, res);
        }
      }
    }
  }

  public static List<Node> getNodes(Node node, Pattern... nodePath) {
    List<Node> res = new ArrayList<>();
    getMatchingNodes(node, nodePath, 0, res);
    return res;
  }

  public static String getNodeText(Node node, Pattern... nodePath) {
    List<Node> nodes = getNodes(node, nodePath);
    if (nodes != null && nodes.size() > 0) {
      return nodes.get(0).getTextContent();
    } else {
      return null;
    }
  }

  public static Node getNode(Node node, Pattern... nodePath) {
    List<Node> nodes = getNodes(node, nodePath);
    if (nodes != null && nodes.size() > 0) {
      return nodes.get(0);
    } else {
      return null;
    }
  }

  private static void getMatchingNodes(Node node, String[] nodePath, int cur, List<Node> res) {
    if (cur < 0 || cur >= nodePath.length) return;
    boolean last = (cur == nodePath.length-1);
    String name = nodePath[cur];
    if (node.hasChildNodes()) {
      NodeList children = node.getChildNodes();
      for (int i = 0; i < children.getLength(); i++) {
        Node c = children.item(i);
        if (name.equals(c.getNodeName())) {
          if (last) {
            res.add(c);
          } else {
            getMatchingNodes(c, nodePath, cur+1, res);
          }
        }
      }
    }
  }

  public static List<Node> getNodes(Node node, String... nodePath) {
    List<Node> res = new ArrayList<>();
    getMatchingNodes(node, nodePath, 0, res);
    return res;
  }

  public static List<String> getNodeTexts(Node node, String... nodePath) {
    List<Node> nodes = getNodes(node, nodePath);
    if (nodes != null) {
      List<String> strs = new ArrayList<>(nodes.size());
      for (Node n:nodes) {
        strs.add(n.getTextContent());
      }
      return strs;
    } else {
      return null;
    }
  }

  public static String getNodeText(Node node, String... nodePath) {
    List<Node> nodes = getNodes(node, nodePath);
    if (nodes != null && nodes.size() > 0) {
      return nodes.get(0).getTextContent();
    } else {
      return null;
    }
  }

  public static String getAttributeValue(Node node, String name) {
    Node attr = getAttribute(node, name);
    return (attr != null)? attr.getNodeValue():null;
  }

  public static Node getAttribute(Node node, String name) {
    return node.getAttributes().getNamedItem(name);
  }

  public static Node getNode(Node node, String... nodePath) {
    List<Node> nodes = getNodes(node, nodePath);
    if (nodes != null && nodes.size() > 0) {
      return nodes.get(0);
    } else {
      return null;
    }
  }

}

## 内容2
### 文件路径：
src/edu/stanford/nlp/util/XMLUtils.java
### 代码内容：
package edu.stanford.nlp.util;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import edu.stanford.nlp.io.IOUtils;
import edu.stanford.nlp.util.logging.Redwood;

/**
 * Provides some utilities for dealing with XML files, both by properly
 * parsing them and by using the methods of a desperate Perl hacker.
 *
 * @author Teg Grenager
 * @author Grace Muzny
 */
public class XMLUtils  {

  /** A logger for this class */
  private static final Redwood.RedwoodChannels log = Redwood.channels(XMLUtils.class);

  private XMLUtils() {} // only static methods

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   *
   * @return List of String text contents of tags.
   */
  public static List<String> getTextContentFromTagsFromFile(File f, String tag) {
    List<String> sents = Generics.newArrayList();
    try {
      sents = getTextContentFromTagsFromFileSAXException(f, tag);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   * If the text contents contains embedded tags, strips the embedded tags out
   * of the returned text. E.g., {@code <s>This is a <s>sentence</s> with embedded tags
   * </s>} would return the list containing ["This is a sentence with embedded
   * tags", "sentence"].
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of String text contents of tags.
   */
  private static List<String> getTextContentFromTagsFromFileSAXException(
          File f, String tag) throws SAXException {
    List<String> sents = Generics.newArrayList();
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(f);
      doc.getDocumentElement().normalize();

      NodeList nodeList=doc.getElementsByTagName(tag);
      for (int i = 0; i < nodeList.getLength(); i++) {
        // Get element
        Element element = (Element)nodeList.item(i);
        String raw = element.getTextContent();
        StringBuilder builtUp = new StringBuilder();
        boolean inTag = false;
        for (int j = 0; j < raw.length(); j++) {
          if (raw.charAt(j) == '<') {
            inTag = true;
          }
          if (!inTag) {
            builtUp.append(raw.charAt(j));
          }
          if (raw.charAt(j) == '>') {
            inTag = false;
          }
        }
        sents.add(builtUp.toString());
      }
    } catch (IOException | ParserConfigurationException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   *
   * @return List of String text contents of tags.
   */
  public static List<Element> getTagElementsFromFile(File f, String tag) {
    List<Element> sents = Generics.newArrayList();
    try {
      sents = getTagElementsFromFileSAXException(f, tag);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   * If the text contents contains embedded tags, strips the embedded tags out
   * of the returned text. E.g., {@code <s>This is a <s>sentence</s> with embedded tags
   * </s>} would return the list containing ["This is a sentence with embedded
   * tags", "sentence"].
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of String text contents of tags.
   */
  private static List<Element> getTagElementsFromFileSAXException(
          File f, String tag) throws SAXException {
    List<Element> sents = Generics.newArrayList();
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(f);
      doc.getDocumentElement().normalize();

      NodeList nodeList=doc.getElementsByTagName(tag);
      for (int i = 0; i < nodeList.getLength(); i++) {
        // Get element
        Element element = (Element)nodeList.item(i);
        sents.add(element);
      }
    } catch (IOException | ParserConfigurationException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the two previous siblings and two next siblings.
   *
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFile(File f, String tag) {
    List<Triple<String, Element, String>> sents = Generics.newArrayList();
    try {
      sents = getTagElementTriplesFromFileSAXException(f, tag);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the previous and next siblings up to max numIncludedSiblings.
   *
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFileNumBounded(File f,
                                                                                String tag,
                                                                                int num) {
    List<Triple<String, Element, String>> sents = Generics.newArrayList();
    try {
      sents = getTagElementTriplesFromFileNumBoundedSAXException(f, tag, num);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the two previous siblings and two next siblings.
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFileSAXException(
      File f, String tag) throws SAXException {
    return  getTagElementTriplesFromFileNumBoundedSAXException(f, tag, 2);
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the previous and next siblings up to max numIncludedSiblings.
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFileNumBoundedSAXException(
      File f, String tag, int numIncludedSiblings) throws SAXException {
    List<Triple<String, Element, String>> sents = Generics.newArrayList();
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(f);
      doc.getDocumentElement().normalize();

      NodeList nodeList=doc.getElementsByTagName(tag);
      for (int i = 0; i < nodeList.getLength(); i++) {
        // Get element
        Node prevNode = nodeList.item(i).getPreviousSibling();
        String prev = "";
        int count = 0;
        while (prevNode != null && count <= numIncludedSiblings) {
          prev = prevNode.getTextContent() + prev;
          prevNode = prevNode.getPreviousSibling();
          count++;
        }

        Node nextNode = nodeList.item(i).getNextSibling();
        String next = "";
        count = 0;
        while (nextNode != null && count <= numIncludedSiblings) {
          next = next + nextNode.getTextContent();
          nextNode = nextNode.getNextSibling();
          count++;
        }
        Element element = (Element)nodeList.item(i);
        Triple<String, Element, String> t = new Triple<>(prev, element, next);
        sents.add(t);
      }
    } catch (IOException | ParserConfigurationException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns a non-validating XML parser. The parser ignores both DTDs and XSDs.
   *
   * @return An XML parser in the form of a DocumentBuilder
   */
  public static DocumentBuilder getXmlParser() {
    DocumentBuilder db = null;
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      dbf.setValidating(false);

      //Disable DTD loading and validation
      //See http://stackoverflow.com/questions/155101/make-documentbuilder-parse-ignore-dtd-references
      dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
      dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

      db = dbf.newDocumentBuilder();
      db.setErrorHandler(new SAXErrorHandler());

    } catch (ParserConfigurationException e) {
      log.warnf("%s: Unable to create XML parser\n", XMLUtils.class.getName());
      log.warn(e);

    } catch(UnsupportedOperationException e) {
      log.warnf("%s: API error while setting up XML parser. Check your JAXP version\n", XMLUtils.class.getName());
      log.warn(e);
    }

    return db;
  }

  /**
   * Returns a validating XML parser given an XSD (not DTD!).
   *
   * @param schemaFile File wit hXML schema
   * @return An XML parser in the form of a DocumentBuilder
   */
  public static DocumentBuilder getValidatingXmlParser(File schemaFile) {
    DocumentBuilder db = null;
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

      SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
      Schema schema = factory.newSchema(schemaFile);
      dbf.setSchema(schema);

      db = dbf.newDocumentBuilder();
      db.setErrorHandler(new SAXErrorHandler());

    } catch (ParserConfigurationException e) {
      log.warnf("%s: Unable to create XML parser\n", XMLUtils.class.getName());
      log.warn(e);

    } catch (SAXException e) {
      log.warnf("%s: XML parsing exception while loading schema %s\n", XMLUtils.class.getName(),schemaFile.getPath());
      log.warn(e);

    } catch(UnsupportedOperationException e) {
      log.warnf("%s: API error while setting up XML parser. Check your JAXP version\n", XMLUtils.class.getName());
      log.warn(e);
    }

    return db;
  }

  /**
   * Block-level HTML tags that are rendered with surrounding line breaks.
   */
  private static final Set<String> breakingTags = Generics.newHashSet(Arrays.asList(new String[] {"blockquote", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "li", "ol", "p", "pre", "ul", "tr", "td"}));

  /**
   * @param r       the reader to read the XML/HTML from
   * @param mapBack a List of Integers mapping the positions in the result buffer
   *                to positions in the original Reader, will be cleared on receipt
   * @return the String containing the resulting text
   */
  public static String stripTags(Reader r, List<Integer> mapBack, boolean markLineBreaks) {
    if (mapBack != null) {
      mapBack.clear(); // just in case it has something in it!
    }
    StringBuilder result = new StringBuilder();
    try {
      int position = 0;
      do {
        String text = XMLUtils.readUntilTag(r);
        if (text.length() > 0) {
          // add offsets to the map back
          for (int i = 0; i < text.length(); i++) {
            result.append(text.charAt(i));
            if (mapBack != null) {
              mapBack.add(Integer.valueOf(position + i));
            }
          }
          position += text.length();
        }
        //        System.err.println(position + " got text: " + text);
        String tag = XMLUtils.readTag(r);
        if (tag == null) {
          break;
        }
        if (markLineBreaks && XMLUtils.isBreaking(parseTag(tag))) {
          result.append("\n");
          if (mapBack != null) {
            mapBack.add(Integer.valueOf(-position));
          }
        }
        position += tag.length();
        //        System.err.println(position + " got tag: " + tag);
      } while (true);
    } catch (IOException e) {
      log.warn("Error reading string");
      log.warn(e);
    }
    return result.toString();
  }

  public static boolean isBreaking(String tag) {
    return breakingTags.contains(tag);
  }

  public static boolean isBreaking(XMLTag tag) {
    return breakingTags.contains(tag.name);
  }

  /**
   * Reads all text up to next XML tag and returns it as a String.
   *
   * @return the String of the text read, which may be empty.
   */
  public static String readUntilTag(Reader r) throws IOException {
    if (!r.ready()) {
      return "";
    }
    StringBuilder b = new StringBuilder();
    int c = r.read();
    while (c >= 0 && c != '<') {
      b.append((char) c);
      c = r.read();
    }
    return b.toString();
  }

  /**
   * @return the new XMLTag object, or null if couldn't be created
   */
  public static XMLTag readAndParseTag(Reader r) throws IOException {
    String s = readTag(r);
    if (s == null) {
      return null;
    }
    XMLTag ret = null;
    try {
      ret = new XMLTag(s);
    } catch (Exception e) {
      log.warn("Failed to handle |" + s + "|");
    }
    return ret;
  }

  // Pattern is reentrant, going by the statement "many matchers can share the same pattern"
  // on the Pattern javadoc.  Therefore, this should be safe as a static final variable.
  private static final Pattern xmlEscapingPattern = Pattern.compile("&.+?;");

  public static String unescapeStringForXML(String s) {
    StringBuilder result = new StringBuilder();
    Matcher m = xmlEscapingPattern.matcher(s);
    int end = 0;
    while (m.find()) {
      int start = m.start();
      result.append(s, end, start);
      end = m.end();
      result.append(translate(s.substring(start, end)));
    }
    result.append(s, end, s.length());
    return result.toString();
  }

  private static char translate(String s) {
    switch (s) {
      case "&":
        return '&';
      case "<":
      case "≪":
        return '<';
      case ">":
      case "≫":
        return '>';
      case """:
        return '\"';
      case "'":
        return '\'';
      case "*":
        return '*';
      case "♯":
        return '\u266F';
      case "=":
        return '=';
      case " ":
        return (char) 0xA0;
      case "¡":
        return (char) 0xA1;
      case "¢":
      case "&shilling;":
        return (char) 0xA2;
      case "£":
        return (char) 0xA3;
      case "¤":
        return (char) 0xA4;
      case "¥":
        return (char) 0xA5;
      case "¦":
        return (char) 0xA6;
      case "§":
        return (char) 0xA7;
      case "¨":
        return (char) 0xA8;
      case "©":
        return (char) 0xA9;
      case "ª":
        return (char) 0xAA;
      case "« ":
        return (char) 0xAB;
      case "¬":
        return (char) 0xAC;
      case " ":
        return (char) 0xAD;
      case "®":
        return (char) 0xAE;
      case "¯":
        return (char) 0xAF;
      case "°":
        return (char) 0xB0;
      case "±":
        return (char) 0xB1;
      case "²":
        return (char) 0xB2;
      case "³":
        return (char) 0xB3;
      case "´":
        return (char) 0xB4;
      case "µ":
        return (char) 0xB5;
      case "·":
        return (char) 0xB7;
      case "¸":
        return (char) 0xB8;
      case "¹":
        return (char) 0xB9;
      case "º":
        return (char) 0xBA;
      case "»":
        return (char) 0xBB;
      case "¼ ":
        return (char) 0xBC;
      case "½":
        return (char) 0xBD;
      case "¾ ":
        return (char) 0xBE;
      case "¿":
        return (char) 0xBF;
      case "À":
        return (char) 0xC0;
      case "Á":
        return (char) 0xC1;
      case "Â":
        return (char) 0xC2;
      case "Ã":
        return (char) 0xC3;
      case "Ä":
        return (char) 0xC4;
      case "Å":
        return (char) 0xC5;
      case "Æ":
        return (char) 0xC6;
      case "Ç":
        return (char) 0xC7;
      case "È":
        return (char) 0xC8;
      case "É":
        return (char) 0xC9;
      case "Ê":
        return (char) 0xCA;
      case "Ë":
        return (char) 0xCB;
      case "Ì":
        return (char) 0xCC;
      case "Í":
        return (char) 0xCD;
      case "Î":
        return (char) 0xCE;
      case "Ï":
        return (char) 0xCF;
      case "Ð":
        return (char) 0xD0;
      case "Ñ":
        return (char) 0xD1;
      case "Ò":
        return (char) 0xD2;
      case "Ó":
        return (char) 0xD3;
      case "Ô":
        return (char) 0xD4;
      case "Õ":
        return (char) 0xD5;
      case "Ö":
        return (char) 0xD6;
      case "×":
        return (char) 0xD7;
      case "Ø":
        return (char) 0xD8;
      case "Ù":
        return (char) 0xD9;
      case "Ú":
        return (char) 0xDA;
      case "Û":
        return (char) 0xDB;
      case "Ü":
        return (char) 0xDC;
      case "Ý":
        return (char) 0xDD;
      case "Þ":
        return (char) 0xDE;
      case "ß":
        return (char) 0xDF;
      case "à":
        return (char) 0xE0;
      case "á":
        return (char) 0xE1;
      case "â":
        return (char) 0xE2;
      case "ã":
        return (char) 0xE3;
      case "ä":
        return (char) 0xE4;
      case "å":
        return (char) 0xE5;
      case "æ":
        return (char) 0xE6;
      case "ç":
        return (char) 0xE7;
      case "è":
        return (char) 0xE8;
      case "é":
        return (char) 0xE9;
      case "ê":
        return (char) 0xEA;
      case "ë ":
        return (char) 0xEB;
      case "ì":
        return (char) 0xEC;
      case "í":
        return (char) 0xED;
      case "î":
        return (char) 0xEE;
      case "ï":
        return 0xEF;
      case "ð":
        return (char) 0xF0;
      case "ñ":
        return (char) 0xF1;
      case "ò":
        return (char) 0xF2;
      case "ó":
        return (char) 0xF3;
      case "ô":
        return (char) 0xF4;
      case "õ":
        return (char) 0xF5;
      case "ö":
        return (char) 0xF6;
      case "÷":
        return (char) 0xF7;
      case "ø":
        return (char) 0xF8;
      case "ù":
        return (char) 0xF9;
      case "ú":
        return (char) 0xFA;
      case "û":
        return (char) 0xFB;
      case "ü":
        return (char) 0xFC;
      case "ý":
        return (char) 0xFD;
      case "þ":
        return (char) 0xFE;
      case "ÿ":
        return (char) 0xFF;
      case "Œ":
        return (char) 0x152;
      case "œ":
        return (char) 0x153;
      case "Š":
        return (char) 0x160;
      case "š":
        return (char) 0x161;
      case "Ÿ":
        return (char) 0x178;
      case "ˆ":
        return (char) 0x2C6;
      case "˜":
        return (char) 0x2DC;
      case "":
        return (char) 0x200E;
      case "":
        return (char) 0x200F;
      case "–":
        return (char) 0x2013;
      case "—":
        return (char) 0x2014;
      case "‘":
        return (char) 0x2018;
      case "’":
        return (char) 0x2019;
      case "‚":
        return (char) 0x201A;
      case "“":
      case "&bquo;":
      case "&bq;":
        return (char) 0x201C;
      case "”":
      case "&equo;":
        return (char) 0X201D;
      case "„":
        return (char) 0x201E;
      case "∼":
        return (char) 0x223C;
      case "√":
        return (char) 0x221A;
      case "≤":
        return (char) 0x2264;
      case "≥":
        return (char) 0x2265;
      case "←":
        return (char) 0x2190;
      case "↓":
        return (char) 0x2193;
      case "→":
        return (char) 0x2192;
      case "…":
        return (char) 0x2026;
      case "′":
        return (char) 0x2032;
      case "″":
      case "&ins;":
        return (char) 0x2033;
      case "™":
        return (char) 0x2122;
      case "Α":
      case "&Agr;":
        return (char) 0x391;
      case "Β":
      case "&Bgr;":
        return (char) 0x392;
      case "Γ":
      case "&Ggr;":
        return (char) 0x393;
      case "Δ":
      case "&Dgr;":
        return (char) 0x394;
      case "Ε":
      case "&Egr;":
        return (char) 0x395;
      case "Ζ":
      case "&Zgr;":
        return (char) 0x396;
      case "Η":
        return (char) 0x397;
      case "Θ":
      case "&THgr;":
        return (char) 0x398;
      case "Ι":
      case "&Igr;":
        return (char) 0x399;
      case "Κ":
      case "&Kgr;":
        return (char) 0x39A;
      case "Λ":
      case "&Lgr;":
        return (char) 0x39B;
      case "Μ":
      case "&Mgr;":
        return (char) 0x39C;
      case "Ν":
      case "&Ngr;":
        return (char) 0x39D;
      case "Ξ":
      case "&Xgr;":
        return (char) 0x39E;
      case "Ο":
      case "&Ogr;":
        return (char) 0x39F;
      case "Π":
      case "&Pgr;":
        return (char) 0x3A0;
      case "Ρ":
      case "&Rgr;":
        return (char) 0x3A1;
      case "Σ":
      case "&Sgr;":
        return (char) 0x3A3;
      case "Τ":
      case "&Tgr;":
        return (char) 0x3A4;
      case "Υ":
      case "&Ugr;":
        return (char) 0x3A5;
      case "Φ":
      case "&PHgr;":
        return (char) 0x3A6;
      case "Χ":
      case "&KHgr;":
        return (char) 0x3A7;
      case "Ψ":
      case "&PSgr;":
        return (char) 0x3A8;
      case "Ω":
      case "&OHgr;":
        return (char) 0x3A9;
      case "α":
      case "&agr;":
        return (char) 0x3B1;
      case "β":
      case "&bgr;":
        return (char) 0x3B2;
      case "γ":
      case "&ggr;":
        return (char) 0x3B3;
      case "δ":
      case "&dgr;":
        return (char) 0x3B4;
      case "ε":
      case "&egr;":
        return (char) 0x3B5;
      case "ζ":
      case "&zgr;":
        return (char) 0x3B6;
      case "η":
      case "&eegr;":
        return (char) 0x3B7;
      case "θ":
      case "&thgr;":
        return (char) 0x3B8;
      case "ι":
      case "&igr;":
        return (char) 0x3B9;
      case "κ":
      case "&kgr;":
        return (char) 0x3BA;
      case "λ":
      case "&lgr;":
        return (char) 0x3BB;
      case "μ":
      case "&mgr;":
        return (char) 0x3BC;
      case "ν":
      case "&ngr;":
        return (char) 0x3BD;
      case "ξ":
      case "&xgr;":
        return (char) 0x3BE;
      case "ο":
      case "&ogr;":
        return (char) 0x3BF;
      case "π":
      case "&pgr;":
        return (char) 0x3C0;
      case "ρ":
      case "&rgr;":
        return (char) 0x3C1;
      case "σ":
      case "&sgr;":
        return (char) 0x3C3;
      case "τ":
      case "&tgr;":
        return (char) 0x3C4;
      case "υ":
      case "&ugr;":
        return (char) 0x3C5;
      case "φ":
      case "&phgr;":
        return (char) 0x3C6;
      case "χ":
      case "&khgr;":
        return (char) 0x3C7;
      case "ψ":
      case "&psgr;":
        return (char) 0x3C8;
      case "ω":
      case "&ohgr;":
        return (char) 0x3C9;
      case "•":
        return (char) 0x2022;
      case "%":
        return '%';
      case "+":
        return '+';
      case "‐":
        return '-';
      case "ă":
      case "ā":
      case "≊":
      case "ą":
        return 'a';
      case "Ā":
        return 'A';
      case "ć":
      case "č":
      case "ĉ":
        return 'c';
      case "Č":
        return 'C';
      case "ď":
        return 'd';
      case "ě":
      case "ē":
      case "ę":
        return 'e';
      case "Ē":
      case "Ě":
        return 'E';
      case "ĺ":
        return 'l';
      case "Ĺ":
        return 'L';
      case "ń":
      case "ň":
      case "ņ":
        return 'n';
      case "ř":
      case "ŕ":
        return 'r';
      case "Ř":
        return 'R';
      case "ō":
        return 'o';
      case "ī":
        return 'i';
      case "ś":
      case "ş":
      case "ŝ":
        return 's';
      case "&Sacute":
      case "Ş":
        return 'S';
      case "ť":
      case "ţ":
        return 't';
      case "ū":
      case "ů":
        return 'u';
      case "ŵ":
        return 'w';
      case "Ŷ":
        return 'Y';
      case "ŷ":
        return 'y';
      case "ž":
      case "ź":
        return 'z';
      case "Ž":
        return 'Z';
      case "♥":
        return (char) 0x2665;
      case "∞":
        return (char) 0x221E;
      case "$":
        return '$';
      case "⊂":
      case "{":
        return (char) 0x2282;
      case "⊃":
      case "}":
        return (char) 0x2283;
      case "[":
        return '[';
      case "]":
        return ']';
      default:
        return ' ';
    }
  }

  /** Returns a String in which all the XML special characters have been
   *  escaped. The resulting String is valid to print in an XML file as an
   *  attribute or element value in all circumstances.  (Note that it may
   *  escape characters that didn't need to be escaped.)
   *
   *  @param in The String to escape
   *  @return The escaped String
   */
  public static String escapeXML(String in) {
    int leng = in.length();
    StringBuilder sb = new StringBuilder(leng);
    for (int i = 0; i < leng; i++) {
      char c = in.charAt(i);
      if (c == '&') {
        sb.append("&");
      } else if (c == '<') {
        sb.append("<");
      } else if (c == '>') {
        sb.append(">");
      } else if (c == '"') {
        sb.append(""");
      } else if (c == '\'') {
        sb.append("'");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  /** Returns a String in which some the XML special characters have been
   *  escaped: just the ones that need escaping in an element content.
   *
   *  @param in The String to escape
   *  @return The escaped String
   */
  public static String escapeElementXML(String in) {
    int leng = in.length();
    StringBuilder sb = new StringBuilder(leng);
    for (int i = 0; i < leng; i++) {
      char c = in.charAt(i);
      if (c == '&') {
        sb.append("&");
      } else if (c == '<') {
        sb.append("<");
      } else if (c == '>') {
        sb.append(">");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  /** Returns a String in which some XML special characters have been
   *  escaped. This just escapes attribute value ones, assuming that
   *  you're going to quote with double quotes.
   *  That is, only " and & are escaped.
   *
   *  @param in The String to escape
   *  @return The escaped String
   */
  public static String escapeAttributeXML(String in) {
    int leng = in.length();
    StringBuilder sb = new StringBuilder(leng);
    for (int i = 0; i < leng; i++) {
      char c = in.charAt(i);
      if (c == '&') {
        sb.append("&");
      } else if (c == '"') {
        sb.append(""");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  public static String escapeTextAroundXMLTags(String s) {
    StringBuilder result = new StringBuilder();
    Reader r = new StringReader(s);
    try {
      do {
        String text = readUntilTag(r);
        //      System.err.println("got text: " + text);
        result.append(escapeXML(text));
        XMLTag tag = readAndParseTag(r);
        //      System.err.println("got tag: " + tag);
        if (tag == null) {
          break;
        }
        result.append(tag);
      } while (true);
    } catch (IOException e) {
      log.warn("Error reading string");
      log.warn(e);
    }
    return result.toString();
  }

  /**
   * return either the first space or the first nbsp
   */
  public static int findSpace(String haystack, int begin) {
    int space = haystack.indexOf(' ', begin);
    int nbsp = haystack.indexOf('\u00A0', begin);
    if (space == -1 && nbsp == -1) {
      return -1;
    } else if (space >= 0 && nbsp >= 0) {
      return Math.min(space, nbsp);
    } else {
      // eg one is -1, and the other is >= 0
      return Math.max(space, nbsp);
    }
  }

  public static class XMLTag {

    /** Stores the complete string passed in as the tag on construction. */
    public String text;

    /** Stores the element name, such as "doc". */
    public String name;

    /** Stores attributes as a Map from keys to values. */
    public Map<String,String> attributes;

    /** Whether this is an ending tag or not. */
    public boolean isEndTag;

    /** Whether this is an empty element expressed as a single empty element tag like {@code <p/>}. */
    public boolean isSingleTag;

    /**
     * Assumes that String contains an XML tag.
     *
     * @param tag String to turn into an XMLTag object
     */
    public XMLTag(String tag) {
      if (tag == null || tag.isEmpty()) {
        throw new NullPointerException("Attempted to parse empty/null tag");
      }
      if (tag.charAt(0) != '<') {
        throw new IllegalArgumentException("Tag did not start with <");
      }
      if (tag.charAt(tag.length() - 1) != '>') {
        throw new IllegalArgumentException("Tag did not end with >");
      }
      text = tag;
      int begin = 1;
      if (tag.charAt(1) == '/') {
        begin = 2;
        isEndTag = true;
      } else {
        isEndTag = false;
      }
      int end = tag.length() - 1;
      if (tag.charAt(tag.length() - 2) == '/') {
        end = tag.length() - 2;
        isSingleTag = true;
      } else {
        isSingleTag = false;
      }
      tag = tag.substring(begin, end);
      attributes = Generics.newHashMap();
      begin = 0;
      end = findSpace(tag, 0);

      if (end < 0) {
        name = tag;
      } else {
        name = tag.substring(begin, end);
        do {
          begin = end + 1;
          while (begin < tag.length() && tag.charAt(begin) < 0x21) {
            begin++; // get rid of leading whitespace
          }
          if (begin == tag.length()) {
            break;
          }
          end = tag.indexOf('=', begin);
          if (end < 0) {
            String att = tag.substring(begin);
            attributes.put(att, "");
            break;
          }
          String att = tag.substring(begin, end).trim();
          begin = end + 1;
          String value = null;
          if (tag.length() > begin) {
            while (begin < tag.length() && tag.charAt(begin) < 0x21) {
              begin++;
            }
            if (begin < tag.length() && tag.charAt(begin) == '\"') {
              // get quoted expression
              begin++;
              end = tag.indexOf('\"', begin);
              if (end < 0) {
                break; // this is a problem
              }
              value = tag.substring(begin, end);
              end++;
            } else {
              // get unquoted expression
              end = findSpace(tag, begin);
              if (end < 0) {
                end = tag.length();
              }
//              System.err.println(begin + " " + end);
              value = tag.substring(begin, end);
            }
          }
          attributes.put(att, value);
        } while (end < tag.length() - 3);
      }
    }

    public String toString() {
      return text;
    }

    /**
     * Given a list of attributes, return the first one that is non-null
     */
    public String getFirstNonNullAttributeFromList(List<String> attributesList) {
      for (String attribute : attributesList) {
        if (attributes.get(attribute) != null) {
          return attributes.get(attribute);
        }
      }
      return null;
    }
  } // end static class XMLTag

  /**
   * Reads all text of the XML tag and returns it as a String.
   * Assumes that a '<' character has already been read.
   *
   * @param r The reader to read from
   * @return The String representing the tag, or null if one couldn't be read
   *         (i.e., EOF).  The returned item is a complete tag including angle
   *         brackets, such as {@code <TXT>}
   */
  public static String readTag(Reader r) throws IOException {
    if ( ! r.ready()) {
      return null;
    }
    StringBuilder b = new StringBuilder("<");
    int c = r.read();
    while (c >= 0) {
      b.append((char) c);
      if (c == '>') {
        break;
      }
      c = r.read();
    }
    if (b.length() == 1) {
      return null;
    }
    return b.toString();
  }

  public static XMLTag parseTag(String tagString) {
    if (tagString == null || tagString.isEmpty()) {
      return null;
    }
    if (tagString.charAt(0) != '<' ||
        tagString.charAt(tagString.length() - 1) != '>') {
      return null;
    }
    return new XMLTag(tagString);
  }

  public static Document readDocumentFromFile(String filename) throws Exception {
    InputSource in = new InputSource(new FileReader(filename));
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(false);
    DocumentBuilder db = factory.newDocumentBuilder();
    db.setErrorHandler(new SAXErrorHandler());
    return db.parse(in);
  }

  private static class SAXErrorHandler implements ErrorHandler {

    public static String makeBetterErrorString(String msg,
                                               SAXParseException ex) {
      StringBuilder sb = new StringBuilder(msg);
      sb.append(": ");
      String str = ex.getMessage();
      if (str.lastIndexOf('.') == str.length() - 1) {
        str = str.substring(0, str.length() - 1);
      }
      sb.append(str);
      sb.append(" at document line ").append(ex.getLineNumber());
      sb.append(", column ").append(ex.getColumnNumber());
      if (ex.getSystemId() != null) {
        sb.append(" in entity from systemID ").append(ex.getSystemId());
      } else if (ex.getPublicId() != null) {
        sb.append(" in entity from publicID ").append(ex.getPublicId());
      }
      sb.append('.');
      return sb.toString();
    }

    @Override
    public void warning(SAXParseException exception) {
      log.warn(makeBetterErrorString("Warning", exception));
    }

    @Override
    public void error(SAXParseException exception) {
      log.error(makeBetterErrorString("Error", exception));
    }

    @Override
    public void fatalError(SAXParseException ex) throws SAXParseException {
      throw new SAXParseException(makeBetterErrorString("Fatal Error", ex),
              ex.getPublicId(), ex.getSystemId(), ex.getLineNumber(), ex.getColumnNumber());
      // throw new RuntimeException(makeBetterErrorString("Fatal Error", ex));
    }

  } // end class SAXErrorHandler

  public static Document readDocumentFromString(String s) throws Exception {
    InputSource in = new InputSource(new StringReader(s));
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(false);
    return factory.newDocumentBuilder().parse(in);
  }

  /** Tests a few methods.
   *  If the first arg is -readDoc then this method tests
   *  readDocumentFromFile.
   *  Otherwise, it tests readTag/readUntilTag and slurpFile.
   */
  public static void main(String[] args) throws Exception {
    if (args[0].equals("-readDoc")) {
      Document doc = readDocumentFromFile(args[1]);
      System.out.println(doc);
    } else {
      String s = IOUtils.slurpFile(args[0]);
      Reader r = new StringReader(s);
      String tag = readTag(r);
      while (tag != null && ! tag.isEmpty()) {
        readUntilTag(r);
        tag = readTag(r);
        if (tag == null || tag.isEmpty()) {
          break;
        }
        System.out.println("got tag=" + new XMLTag(tag));
      }
    }
  }

}