Repository: ktorio/ktor
File: ktor-http/ktor-http-cio/common/src/io/ktor/http/cio/ChunkedTransferEncoding.kt (Kotlin)

Code before:

/**
 * Encoder job type
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.EncoderJob)
 */
public typealias EncoderJob = ReaderJob

/**
 * Start chunked stream encoding coroutine
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.encodeChunked)
 */
@OptIn(DelicateCoroutinesApi::class)
public fun encodeChunked(
    output: ByteWriteChannel,
    coroutineContext: CoroutineContext
): EncoderJob = GlobalScope.reader(coroutineContext, autoFlush = false) {
    encodeChunked(output, channel)
}

/**
 * Chunked stream encoding loop
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.encodeChunked)
 */
public suspend fun encodeChunked(output: ByteWriteChannel, input: ByteReadChannel) {
    try {
        while (!input.isClosedForRead) {
            input.read { source, startIndex, endIndex ->
                if (endIndex == startIndex) return@read 0
                output.writeChunk(source, startIndex, endIndex)
            }
        }

        input.rethrowCloseCause()
        output.writeFully(LastChunkBytes)
    } catch (cause: Throwable) {
        output.close(cause)
        input.cancel(cause)
        throw cause
    } finally {
        output.flush()
    }
}

private fun ByteReadChannel.rethrowCloseCause() {
    val cause = when (this) {
        is ByteChannel -> closedCause
        else -> null
    }

Review comment (bug):
The chunk size calculation at line 123 doesn't check for overflow. If an attacker sends a chunk size with many hex digits, the left shift operation could cause the result to overflow and wrap around to a negative or small positive number. This could lead to incorrect chunk size calculation or even a denial of service. Add overflow detection before or after the shift-and-or operation.
```suggestion
                    if (digit == -1L) throw IOException("Invalid chunk size character: 0x${intValue.toString(16)}")
                    if (result > (Long.MAX_VALUE - digit) / 16) {
                        throw IOException("Chunk size is too large")
                    }
```

Code after:
    var inExtension = false
    var inQuotes = false
    var afterCr = false
    var i = 0
    while (i++ < MAX_CHUNK_SIZE_LENGTH) {
        if (buffer.exhausted() && !input.awaitContent()) throw EOFException()
        val byte = buffer.readByte()
        if (inQuotes && byte != QUOTE) continue
        try {
            when (byte) {
                CR -> continue // set in finally block
                LF -> {
                    if (!afterCr) throw IOException("Illegal newline character in chunk size")
                    if (i < 3) throw IOException("Empty chunk size")
                    return result
                }
                QUOTE -> inQuotes = !inQuotes
                SEMICOLON -> {
                    if (i == 1) throw IOException("Empty chunk size")
                    inExtension = true
                }
                else -> {
                    if (inExtension) continue // always ignore extensions
                    val intValue = byte.toInt() and 0xff
                    val digit = if (intValue < 0xff) HexTable[intValue] else -1L
                    if (digit == -1L) throw IOException("Invalid chunk size character: 0x${intValue.toString(16)}")
                    if ((result and -0x1000000000000000L) != 0L) throw IOException("Chunk size overflow")
                    result = (result shl 4) or digit
                }
            }
        } finally {
            afterCr = byte == CR
        }
    }
    throw IOException("Chunk size limit exceeded")
}

/**
 * Encoder job type
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.EncoderJob)
 */
public typealias EncoderJob = ReaderJob

/**
 * Start chunked stream encoding coroutine
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.http.cio.encodeChunked)
 */
@OptIn(DelicateCoroutinesApi::class)
public fun encodeChunked(