forked from pepegar/hammock
/
Cookie.scala
157 lines (144 loc) · 5.27 KB
/
Cookie.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package hammock
package hi
import java.time.ZonedDateTime
import cats._
import cats.implicits._
import hammock.hi.Cookie.SameSite
import hammock.hi.platformspecific._
import monocle.Optional
import monocle.macros.Lenses
@Lenses case class Cookie(
name: String,
value: String,
expires: Option[ZonedDateTime] = None,
maxAge: Option[Int] = None,
domain: Option[String] = None,
path: Option[String] = None,
secure: Option[Boolean] = None,
httpOnly: Option[Boolean] = None,
sameSite: Option[SameSite] = None,
custom: Option[Map[String, String]] = None
)
object Cookie {
val expiresOpt: Optional[Cookie, ZonedDateTime] = Optional[Cookie, ZonedDateTime] {
_.expires
} { date =>
{
case cookie @ Cookie(_, _, None, _, _, _, _, _, _, _) => cookie
case cookie @ _ => cookie.copy(expires = Some(date))
}
}
val maxAgeOpt: Optional[Cookie, Int] = Optional[Cookie, Int] {
_.maxAge
} { age =>
{
case cookie @ Cookie(_, _, _, None, _, _, _, _, _, _) => cookie
case cookie @ _ => cookie.copy(maxAge = Some(age))
}
}
val domainOpt: Optional[Cookie, String] = Optional[Cookie, String] {
_.domain
} { domain =>
{
case cookie @ Cookie(_, _, _, _, None, _, _, _, _, _) => cookie
case cookie @ _ => cookie.copy(domain = Some(domain))
}
}
val pathOpt: Optional[Cookie, String] = Optional[Cookie, String] {
_.path
} { path =>
{
case cookie @ Cookie(_, _, _, _, _, None, _, _, _, _) => cookie
case cookie @ _ => cookie.copy(path = Some(path))
}
}
val secureOpt: Optional[Cookie, Boolean] = Optional[Cookie, Boolean] {
_.secure
} { secure =>
{
case cookie @ Cookie(_, _, _, _, _, _, None, _, _, _) => cookie
case cookie @ _ => cookie.copy(secure = Some(secure))
}
}
val httpOnlyOpt: Optional[Cookie, Boolean] = Optional[Cookie, Boolean] {
_.httpOnly
} { httpOnly =>
{
case cookie @ Cookie(_, _, _, _, _, _, _, None, _, _) => cookie
case cookie @ _ => cookie.copy(httpOnly = Some(httpOnly))
}
}
val sameSiteOpt: Optional[Cookie, SameSite] = Optional[Cookie, SameSite] {
_.sameSite
} { sameSite =>
{
case cookie @ Cookie(_, _, _, _, _, _, _, _, None, _) => cookie
case cookie @ _ => cookie.copy(sameSite = Some(sameSite))
}
}
val customOpt: Optional[Cookie, Map[String, String]] = Optional[Cookie, Map[String, String]] {
_.custom
} { custom =>
{
case cookie @ Cookie(_, _, _, _, _, _, _, _, _, None) => cookie
case cookie @ _ => cookie.copy(custom = Some(custom))
}
}
sealed trait SameSite
object SameSite {
case object Strict extends SameSite
case object Lax extends SameSite
implicit val sameSiteShow = new Show[SameSite] {
def show(s: SameSite): String = s match {
case Strict => "Strict"
case Lax => "Lax"
}
}
implicit val sameSiteEq = new Eq[SameSite] {
def eqv(a: SameSite, b: SameSite): Boolean = (a, b) match {
case (Strict, Strict) => true
case (Lax, Lax) => true
case _ => false
}
}
}
implicit val cookieShow = new Show[Cookie] {
def show(cookie: Cookie): String = render(cookie)
}
implicit val cookieEq: Eq[Cookie] = new Eq[Cookie] {
def eqv(a: Cookie, b: Cookie): Boolean = {
a.name === b.name &&
a.value === b.value &&
a.expires.equals(b.expires) &&
a.maxAge === b.maxAge &&
a.domain === b.domain &&
a.path === b.path &&
a.secure === b.secure &&
a.httpOnly === b.httpOnly &&
a.sameSite === b.sameSite &&
a.custom === b.custom
}
}
/**
* renders a cookie in the Set-Cookie header format
*/
def render(cookie: Cookie)(implicit fmt: DateFormatter): String = {
def renderPair[S: Show](k: String)(v: S) = k ++ "=" ++ Show[S].show(v)
def maybeShowDate(date: Option[ZonedDateTime]): Option[String] = date map (date => fmt.format(date))
def expires = maybeShowDate(cookie.expires) map renderPair("Expires")
def maxAge = cookie.maxAge map renderPair("MaxAge")
def domain = cookie.domain map renderPair("Domain")
def path = cookie.path map renderPair("Path")
def secure = cookie.secure map renderPair("Secure")
def httpOnly = cookie.httpOnly map renderPair("HttpOnly")
def sameSite = cookie.sameSite map renderPair("SameSite")
val maybes = List(expires, maxAge, domain, path, secure, httpOnly, sameSite)
.filter(_.nonEmpty)
.map(_.get)
val custom: List[String] = cookie.custom match {
case None => Nil
case Some(elems) => elems.map { case (k, v) => renderPair(k)(v) } toList
}
(s"${renderPair(cookie.name)(cookie.value)}" :: maybes ::: custom).mkString("; ")
}
}