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

spinoco.protocol.http.header.value.HttpCookie.scala Maven / Gradle / Ivy

The newest version!
package spinoco.protocol.http.header.value

import java.time.LocalDateTime

import scodec.bits.BitVector
import scodec.codecs._
import scodec.{Attempt, Codec, Err}
import spinoco.protocol.common.util.attempt
import spinoco.protocol.http.codec.helper._
import spinoco.protocol.common.codec._

import scala.concurrent.duration._
import scala.collection.immutable.HashSet

/**
  * see http://tools.ietf.org/html/rfc6265
  */
sealed case class HttpCookie(
   name: String
   , content: String
   , expires: Option[LocalDateTime]
   , maxAge: Option[FiniteDuration]
   , domain: Option[String]
   , path: Option[String]
   , secure: Boolean
   , httpOnly: Boolean
   , params: Map[String, String]
)

object HttpCookie {

  object rfc2616 {
    val controls: Seq[Byte] = (0 to 31).map(_.toByte) :+ 127.toByte
    val separators = Seq[Byte]('=', ')', '(', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '{', '}', ' ', 9.toByte)
    val nonTokens = HashSet.empty[Byte] ++ separators ++ controls

    val token: Codec[String] = takeWhile(asciiString) { b => nonTokens.contains(b) == false }
  }

  val cookiePair: Codec[(String,String)] =
    (rfc2616.token :: constant(_equal) :: (ignoreWS ~> ascii)).as[(String,String)]
  // https://tools.ietf.org/html/rfc6265#section-5.3

  val codec: Codec[HttpCookie] = {
    val kvTuple:Codec[(String,String)] = { choice (
        asciiConstant("secure").exmap ( _ => Attempt.successful("Secure" -> "") , { case (k,_)  => if (k == "Secure") Attempt.successful(()) else Attempt.failure(Err("Secure expected")) })
        , asciiConstant("httponly").exmap ( _ => Attempt.successful("HttpOnly" -> "") , { case (k,_)  => if (k == "HttpOnly") Attempt.successful(()) else Attempt.failure(Err("HttpOnly expected")) })
        , cookiePair
      )
    }

    val defaultParams = Set("Max-Age", "Expires", "Domain", "Path", "Secure", "HttpOnly")

    def decode(h: (String, String), t: List[(String, String)]):Attempt[HttpCookie] = {
      val params = t.toMap
      params.get("Max-Age").map { s => attempt(s.toLong.seconds).map(Some(_)) }.getOrElse(Attempt.successful(None)).flatMap { maxAge =>
      params.get("Expires").map { s => httpDateTimeCodec.decodeValue(BitVector.view(s.getBytes)).map(Some(_)) }.getOrElse(Attempt.successful(None)).map { expires =>
        HttpCookie(
          name = h._1
          , content = h._2
          , expires = expires
          , maxAge = maxAge
          , domain = params.get("Domain")
          , path = params.get("Path")
          , secure = params.isDefinedAt("Secure")
          , httpOnly = params.isDefinedAt("HttpOnly")
          , params = params -- defaultParams
        )
      }}

    }

    def encode(cookie:HttpCookie): Attempt[((String, String), List[(String, String)])] = {
      Attempt.successful {
        (cookie.name -> cookie.content) ->
        (cookie.params
          ++ cookie.expires.map { localDateTime2String  }.map { "Expires" -> _ } .toMap
          ++ cookie.maxAge.map { _.toSeconds.toString }.map { "Max-Age" -> _ }.toMap
          ++ cookie.domain.map { "Domain" -> _ }.toMap
          ++ cookie.path.map { "Path" -> _ }.toMap
          ++ (if (cookie.secure) Map("Secure" -> "true") else Map.empty)
          ++ (if (cookie.httpOnly) Map("HttpOnly" -> "true") else Map.empty)
        ).toList
      }
    }

    parametrizedN(semicolon, semicolon_SP, kvTuple, kvTuple).exmap(decode _ tupled,encode)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy