Repository: ktorio/ktor
File: ktor-io/common/src/io/ktor/utils/io/ByteReadChannelOperations.kt (Kotlin)

Code before:

public suspend fun ByteReadChannel.discardExact(value: Long) {
    if (discard(value) < value) throw EOFException("Unable to discard $value bytes")
}

@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.discard(max: Long = Long.MAX_VALUE): Long {
    var remaining = max
    while (remaining > 0 && !isClosedForRead) {
        if (availableForRead == 0) {
            awaitContent()
        }
        val count = minOf(remaining, readBuffer.remaining)
        readBuffer.discard(count)

        remaining -= count
    }

    return max - remaining
}

private const val CR: Byte = '\r'.code.toByte()
private const val LF: Byte = '\n'.code.toByte()

@JvmInline
public value class LineEndingMode private constructor(private val mode: Int) {

    public operator fun contains(other: LineEndingMode): Boolean =
        mode or other.mode == mode

    public operator fun plus(other: LineEndingMode): LineEndingMode =
        LineEndingMode(mode or other.mode)

    override fun toString(): String = when (this) {
        CR -> "CR"
        LF -> "LF"
        CRLF -> "CRLF"
        else -> values.filter { it in this }.toString()
    }

    public companion object {
        public val CR: LineEndingMode = LineEndingMode(0b001)
        public val LF: LineEndingMode = LineEndingMode(0b010)
        public val CRLF: LineEndingMode = LineEndingMode(0b100)
        public val Any: LineEndingMode = LineEndingMode(0b111)

        private val values = listOf(CR, LF, CRLF)
    }
}

/**

Review comment (suggestion):
I could fix the problem only for chunked body, but it seems we should also fix line endings in request/response parsing.

Code after:
}

@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.discard(max: Long = Long.MAX_VALUE): Long {
    var remaining = max
    while (remaining > 0 && !isClosedForRead) {
        if (availableForRead == 0) {
            awaitContent()
        }
        val count = minOf(remaining, readBuffer.remaining)
        readBuffer.discard(count)

        remaining -= count
    }

    return max - remaining
}

private const val CR: Byte = '\r'.code.toByte()
private const val LF: Byte = '\n'.code.toByte()

/**
 * Reads a line of UTF-8 characters to the specified [out] buffer.
 * It recognizes CR, LF and CRLF as a line delimiter.
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.utils.io.readUTF8LineTo)
 *
 * @param out the buffer to write the line to
 * @param max the maximum number of characters to read
 *
 * @return `true` if a new line separator was found or max bytes appended. `false` if no new line separator and no bytes read.
 * @throws TooLongLineException if max is reached before encountering a newline or end of input
 */
@OptIn(InternalAPI::class)
public suspend fun ByteReadChannel.readUTF8LineTo(out: Appendable, max: Int = Int.MAX_VALUE): Boolean {
    return readUTF8LineTo(out, max, lineEnding = LineEndingMode.Any)
}

/**
 * Reads a line of UTF-8 characters to the specified [out] buffer.
 * It recognizes the specified line ending as a line delimiter and throws an exception
 * if an unexpected line delimiter is found.
 * By default, all line endings (CR, LF and CRLF) are allowed as a line delimiter.
 *
 * [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.utils.io.readUTF8LineTo)
 *
 * @param out the buffer to write the line to
 * @param max the maximum number of characters to read
 * @param lineEnding the allowed line endings
 *
 * @return `true` if a new line separator was found or max bytes appended. `false` if no new line separator and no bytes read.