All Downloads are FREE. Search and download functionalities are using the official Maven repository.

korolev.server.internal.FormDataCodec.scala Maven / Gradle / Ivy

There is a newer version: 1.16.0-M5
Show newest version
/*
 * Copyright 2017-2020 Aleksey Fomkin
 *
 * 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 korolev.server.internal

import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets

import korolev.web.FormData

import scala.annotation.tailrec

private[korolev] final class FormDataCodec(maxPartSize: Int) {

  import FormDataCodec._

  private val threadLocalBuffer = new ThreadLocal[ByteBuffer] {
    override def initialValue(): ByteBuffer = {
      ByteBuffer.allocate(maxPartSize)
    }
  }

  def decode(source: ByteBuffer, boundary: String): FormData = {

    val boundaryWithBreaks = s"\n--$boundary\n"
    val end = s"\n--$boundary--\n"
    val buffer = threadLocalBuffer.get()

    buffer.clear()

    // Check the delimiter is reached
    def checkDelimiter(delimiter: String): Boolean = {
      val d = delimiter.getBytes
      @tailrec def aux(pos: Int, startPos: Int): Boolean = {
        if (pos < d.length) {
          if(source.position() == source.limit()) {
            true
          } else {
            val b = source.get()
            if (b == '\r') aux(pos, startPos)
            else if (b == d(pos)) aux(pos + 1, startPos)
            else {
              source.position(startPos)
              false
            }
          }
        } else true
      }
      aux(0, source.position)
    }

    type Entries = List[FormData.Entry]
    type Headers = List[(String, String)]

    def loop(entries: Entries,
             headers: Headers,
             state: DecoderState): Entries = {

      def updateEntries() = {
        val content = {
          val bytes = new Array[Byte](buffer.position)
          buffer.rewind()
          buffer.get(bytes)
          buffer.clear()
          ByteBuffer.wrap(bytes)
        }
        val nameOpt = headers collectFirst {
          case (key, value) if key.toLowerCase == "content-disposition" =>
            value
        } flatMap { contentDisposition =>
          contentDisposition.split(';') collectFirst {
            case s if s.indexOf('=') + s.indexOf("name") > -1 =>
              val Array(_, value) = s.split('=')
              value.stripPrefix("\"").stripSuffix("\"")
          }
        }
        nameOpt.fold(entries) { name =>
          val newEntry = FormData.Entry(name, content, headers)
          newEntry :: entries
        }
      }

      state match {
        case _ if source.position() == source.limit() =>
          List.empty[FormData.Entry]
        case Buffering if checkDelimiter(end) => updateEntries()
        case Buffering if checkDelimiter(boundaryWithBreaks) =>
          val ue = updateEntries()
          loop(ue, Nil, Headers)
        case Headers if checkDelimiter("\n\n") =>
          val bytes = new Array[Byte](buffer.position)
          buffer.rewind()
          buffer.get(bytes)
          val rawHeaders = new String(bytes, StandardCharsets.ISO_8859_1)
          val headers = rawHeaders.split('\n').toList.collect {
            case line if line.contains(":") =>
              val Array(key, value) = line.split(":", 2)
              key.trim -> value.trim
          }
          buffer.clear()
          loop(entries, headers, Buffering)
        case Buffering | Headers =>
          buffer.put(source.get())
          loop(entries, headers, state)
      }
    }

    checkDelimiter(s"--$boundary\n")
    FormData(loop(Nil, Nil, Headers))
  }
}

private[korolev] object FormDataCodec {
  private sealed trait DecoderState
  private case object Buffering extends DecoderState
  private case object Headers extends DecoderState
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy