/
headers.scala
226 lines (189 loc) · 9.13 KB
/
headers.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package unfiltered.request
import scala.util.control.Exception.allCatch
import scala.util.control.Exception.catching
import scala.util.matching.Regex
trait DateParser extends (String => java.util.Date)
object DateFormatting {
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
def format(date: Date): String =
new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH) {
setTimeZone(TimeZone.getTimeZone("GMT"))
}.format(date)
def parseAs(fmt: String)(value: String): Option[Date] =
allCatch.opt(new SimpleDateFormat(fmt, Locale.US).parse(value))
/** Preferred HTTP date format Sun, 06 Nov 1994 08:49:37 GMT */
def RFC1123: String => Option[Date] = parseAs("EEE, dd MMM yyyy HH:mm:ss z") _
/** Sunday, 06-Nov-94 08:49:37 GMT */
def RFC1036: String => Option[Date] = parseAs("EEEEEE, dd-MMM-yy HH:mm:ss z") _
/** Sun Nov 6 08:49:37 1994 */
def ANSICTime: String => Option[Date] = parseAs("EEE MMM d HH:mm:ss yyyy") _
/** @return various date coersion formats falling back on None value */
def parseDate(raw: String): Option[Date] = RFC1123(raw) orElse RFC1036(raw) orElse ANSICTime(raw)
}
/** A header with values mapped to keys in a Map. */
private[request] class MappedRequestHeader[A, B](val name: String)(parser: Iterator[String] => Map[A, B])
extends RequestExtractor[Map[A, B]] {
def unapply[T](req: HttpRequest[T]): Some[Map[A, B]] = Some(parser(req.headers(name)))
def apply[T](req: HttpRequest[T]): Map[A, B] = parser(req.headers(name))
}
/** A header with comma delimited values. Implementations of this extractor
* will not match requests for which the header `name` is not present.*/
private[request] class SeqRequestHeader[T](val name: String)(parser: Iterator[String] => List[T])
extends RequestExtractor[List[T]] {
def unapply[A](req: HttpRequest[A]): Option[List[T]] =
Some(parser(req.headers(name))).filter { _.nonEmpty }
def apply[T](req: HttpRequest[T]) = parser(req.headers(name))
}
/** A header with a single value. Implementations of this extractor
* will not match requests for which the header `name` is not present.*/
private[request] class RequestHeader[A](val name: String)(parser: Iterator[String] => List[A])
extends RequestExtractor[A] {
def unapply[T](req: HttpRequest[T]): Option[A] = parser(req.headers(name)).headOption
def apply[T](req: HttpRequest[T]): Option[A] = parser(req.headers(name)).headOption
}
private[request] object DateValueParser extends (Iterator[String] => List[java.util.Date]) {
import DateFormatting._
def apply(values: Iterator[String]): List[java.util.Date] =
values.toList.flatMap(parseDate)
}
private[request] object IntValueParser extends (Iterator[String] => List[Int]) {
def tryInt(raw: String): Option[Int] = catching(classOf[NumberFormatException]).opt(raw.toInt)
def apply(values: Iterator[String]): List[Int] =
values.toList.flatMap(tryInt)
}
private[request] object StringValueParser extends (Iterator[String] => List[String]) {
def apply(values: Iterator[String]) =
values.toList
}
private[request] object UriValueParser extends (Iterator[String] => List[java.net.URI]) {
import java.net.URI
import java.net.URISyntaxException
def toUri(raw: String): Option[URI] =
catching(classOf[URISyntaxException], classOf[NullPointerException]).opt(new URI(raw))
def apply(values: Iterator[String]): List[URI] =
values.toList.flatMap(toUri)
}
private[request] object SeqValueParser extends (Iterator[String] => List[String]) {
def apply(values: Iterator[String]): List[String] = {
def split(raw: String): List[String] =
(raw.split(",") map {
_.trim.takeWhile { _ != ';' }.mkString
}).toList
values.toList.flatMap(split)
}
}
private[request] case class Conneg(value: String, qualifier: Double = 1.0)
private[request] object Conneg {
val EqualsMatcher: Regex = """(\w*)="?([a-zA-Z\.0-9]*)"?""".r
def apply(input: String): Conneg = {
val split = input.trim().split(";").toList
val params = split.tail
.foldLeft(Map[String, Option[String]]()) {
case (map, s) => {
val item = s.trim match {
case EqualsMatcher(a, b) => (a.trim, Some(b.trim))
case _ => (s, None)
}
map + item
}
}
.collect { case (a, Some(b)) => (a, b) }
new Conneg(split.head, params.get("q").map(_.toDouble).getOrElse(1.0))
}
}
private[request] object ConnegValueParser extends (Iterator[String] => List[String]) {
def apply(values: Iterator[String]): List[String] = {
def parse: String => scala.List[Conneg] = { raw =>
raw.split(",").map(Conneg(_)).toList
}
values.toList.flatMap(parse).sortBy(_.qualifier)(implicitly[Ordering[Double]].reverse).map(_.value)
}
}
/** Header whose value should be a date and time. Parsing is attempted
* for formats defined in the DateFormatting object, in this order:
* RFC1123, RFC1036, ANSICTime. */
class DateHeader(name: String) extends RequestHeader(name)(DateValueParser)
/** A repeatable header may be specified in more than one header k-v pair and
* whose values are a list delimited by comma
* see also [[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]] */
class RepeatableHeader(name: String) extends SeqRequestHeader(name)(SeqValueParser)
/** Header whose value should be a valid URI. */
class UriHeader(name: String) extends RequestHeader(name)(UriValueParser)
/** Header whose value can be any string. */
class StringHeader(name: String) extends RequestHeader(name)(StringValueParser)
/** Header whose value should be an integer. (Is stored in an Int.) */
class IntHeader(name: String) extends RequestHeader(name)(IntValueParser)
/* Header where the value needs to be sorted by the qualifier attribute. */
class ConnegHeader(name: String) extends SeqRequestHeader(name)(ConnegValueParser)
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10
object Accept extends ConnegHeader("Accept")
object AcceptCharset extends ConnegHeader("Accept-Charset")
object AcceptEncoding extends ConnegHeader("Accept-Encoding")
object AcceptLanguage extends ConnegHeader("Accept-Language")
/** To handle request body content encodings */
object RequestContentEncoding extends ConnegHeader("Content-Encoding") {
private def matching(t: String) =
RequestExtractor.predicate(RequestContentEncoding) { encs =>
encs.exists { _.equalsIgnoreCase(t) }
}
val GZip: RequestExtractor.Predicate[List[String]] = matching("gzip")
val Deflate: RequestExtractor.Predicate[List[String]] = matching("deflate")
val Compress: RequestExtractor.Predicate[List[String]] = matching("compress")
val SDCH: RequestExtractor.Predicate[List[String]] = matching("sdch")
val Identity: RequestExtractor.Predicate[List[String]] = matching("identity")
}
object Authorization extends StringHeader("Authorization")
object Connection extends StringHeader("Connection")
object RequestContentType extends StringHeader("Content-Type")
object Expect extends StringHeader("Expect")
object From extends StringHeader("From")
object Host extends StringHeader("Host")
object IfMatch extends RepeatableHeader("If-Match")
object IfModifiedSince extends DateHeader("If-Modified-Since")
object IfNoneMatch extends RepeatableHeader("If-None-Match")
object IfRange extends StringHeader("If-Range") // can also be an http date
object IfUnmodifiedSince extends DateHeader("If-Unmodified-Since")
object MaxForwards extends IntHeader("Max-Forwards")
object ProxyAuthorization extends StringHeader("Proxy-Authorization")
object Range extends RepeatableHeader("Range") // there more structure here
object Referer extends UriHeader("Referer")
object TE extends RepeatableHeader("TE")
object Upgrade extends RepeatableHeader("Upgrade")
object UserAgent extends StringHeader("User-Agent") // maybe a bit more structure here
object Via extends RepeatableHeader("Via")
object XForwardedFor extends RepeatableHeader("X-Forwarded-For")
object XForwardedPort extends IntHeader("X-Forwarded-Port")
object XForwardedProto extends StringHeader("X-Forwarded-Proto")
/** Extracts the charset value from the Content-Type header, if present */
object Charset {
import unfiltered.util.MIMEType
def unapply[T](req: HttpRequest[T]): Option[String] = {
for {
case MIMEType(mimeType) <- RequestContentType(req)
charset <- mimeType.params.get("charset")
} yield charset
}
def apply[T](req: HttpRequest[T]): Option[String] = unapply(req)
}
/** Extracts hostname and port separately from the Host header, setting
* a default port of 80 or 443 when none is specified */
object HostPort {
import unfiltered.util.Of
def unapply[T](req: HttpRequest[T]): Option[(String, Int)] =
req match {
case Host(hostname) =>
hostname.split(':') match {
case Array(host, Of.Int(port)) => Some((host, port))
case _ => Some((hostname, if (req.isSecure) 443 else 80))
}
case _ => None
}
}
//CORS request headers
//https://www.w3.org/TR/cors/#syntax
object Origin extends StringHeader("Origin")
object AccessControlRequestMethod extends StringHeader("Access-Control-Request-Method")
object AccessControlRequestHeaders extends RepeatableHeader("Access-Control-Request-Headers")