zio.http.internal.FormState.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021 - 2023 Sporta Technologies PVT LTD & the ZIO HTTP contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package zio.http.internal
import zio._
import zio.http.Boundary
import zio.http.internal.FormAST._
private[http] sealed trait FormState {
def reset(): Unit
}
private[http] object FormState {
final class FormStateBuffer(boundary: Boundary) extends FormState { self =>
private val tree0: ChunkBuilder[FormAST] = ChunkBuilder.make[FormAST]()
private val buffer: ChunkBuilder[Byte] = new ChunkBuilder.Byte
private var bufferSize: Int = 0
private val closingBoundaryBytesSize = boundary.closingBoundaryBytes.size
private var boundaryMatches: Boolean = true
private var lastByte: OptionalByte = OptionalByte.None
private var isBufferEmpty = true
private var dropContents = false
private var phase0: Phase = Phase.Part1
private var lastTree = null.asInstanceOf[Chunk[FormAST]]
private def addToTree(ast: FormAST): Unit = {
if (lastTree ne null) lastTree = null
tree0 += ast
}
def tree: Chunk[FormAST] = {
if (lastTree eq null) lastTree = tree0.result()
lastTree
}
def phase: Phase = phase0
def append(byte: Byte): FormState = {
val crlf = byte == '\n' && !lastByte.isEmpty && lastByte.get == '\r'
val posInLine = bufferSize + (if (this.lastByte.isEmpty) 0 else 1)
boundaryMatches &&= posInLine < closingBoundaryBytesSize && boundary.closingBoundaryBytes(posInLine) == byte
val boundaryDetected = boundaryMatches && posInLine == closingBoundaryBytesSize - 1
def flush(ast: FormAST): Unit = {
buffer.clear()
bufferSize = 0
boundaryMatches = true
lastByte = OptionalByte.None
if (crlf && isBufferEmpty && (phase eq Phase.Part1)) phase0 = Phase.Part2
if (ast.isContent && dropContents) () else addToTree(ast)
isBufferEmpty = true
}
if (crlf && (phase eq Phase.Part1)) {
val ast = FormAST.makePart1(buffer.result(), boundary)
ast match {
case content: Content => flush(content); self
case header: Header => flush(header); self
case EncapsulatingBoundary(_) => BoundaryEncapsulated(tree)
case ClosingBoundary(_) => BoundaryClosed(tree)
}
} else if ((crlf || boundaryDetected) && (phase eq Phase.Part2)) {
if (boundaryDetected) {
buffer += '-'
buffer += '-'
}
val ast = FormAST.makePart2(buffer.result(), boundary)
ast match {
case content: Content =>
flush(content)
addToTree(EoL) // preserving EoL for multiline content
self
case EncapsulatingBoundary(_) => BoundaryEncapsulated(tree)
case ClosingBoundary(_) => BoundaryClosed(tree)
}
} else {
if (!lastByte.isEmpty) {
if (isBufferEmpty) isBufferEmpty = false
buffer += lastByte.get
bufferSize += 1
}
lastByte = OptionalByte.Some(byte)
self
}
}
def startIgnoringContents: FormStateBuffer = {
if (!dropContents) dropContents = true
self
}
def reset(): Unit = {
tree0.clear()
buffer.clear()
bufferSize = 0
boundaryMatches = true
isBufferEmpty = true
dropContents = false
phase0 = Phase.Part1
lastTree = null.asInstanceOf[Chunk[FormAST]]
lastByte = OptionalByte.None
}
}
final case class BoundaryEncapsulated(buffer: Chunk[FormAST]) extends FormState {
def reset(): Unit = ()
}
final case class BoundaryClosed(buffer: Chunk[FormAST]) extends FormState {
def reset(): Unit = ()
}
def fromBoundary(boundary: Boundary): FormState = {
new FormStateBuffer(boundary)
}
sealed trait Phase
object Phase {
case object Part1 extends Phase
case object Part2 extends Phase
}
// Avoids boxing of Byte values
sealed abstract class OptionalByte {
def get: Byte
final def isEmpty: Boolean = this eq OptionalByte.None
}
private object OptionalByte {
case object None extends OptionalByte {
def get: Byte = throw new NoSuchElementException("None.get")
}
final case class Some(get: Byte) extends OptionalByte
}
}