aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authoradamw <adam@warski.org>2017-07-15 11:59:10 +0200
committeradamw <adam@warski.org>2017-07-15 11:59:10 +0200
commit8d9145a04cdd21a471fba61fc19203759b95514d (patch)
tree0d5eb11f6c87ed8d2f41b483e381852d871c8ee1 /core
parentbc685df2cd50814b45e669f4f602732887c2879c (diff)
downloadsttp-8d9145a04cdd21a471fba61fc19203759b95514d.tar.gz
sttp-8d9145a04cdd21a471fba61fc19203759b95514d.tar.bz2
sttp-8d9145a04cdd21a471fba61fc19203759b95514d.zip
Cookie parsing
Diffstat (limited to 'core')
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/Response.scala107
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/package.scala1
2 files changed, 108 insertions, 0 deletions
diff --git a/core/src/main/scala/com/softwaremill/sttp/Response.scala b/core/src/main/scala/com/softwaremill/sttp/Response.scala
index dbaf71f..a4d60a5 100644
--- a/core/src/main/scala/com/softwaremill/sttp/Response.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/Response.scala
@@ -1,5 +1,17 @@
package com.softwaremill.sttp
+import java.net.HttpCookie
+import java.text.SimpleDateFormat
+import java.time.ZonedDateTime
+import java.util.{
+ Calendar,
+ GregorianCalendar,
+ Locale,
+ StringTokenizer,
+ TimeZone
+}
+
+import scala.collection.JavaConverters._
import scala.collection.immutable.Seq
import scala.util.Try
@@ -18,4 +30,99 @@ case class Response[T](body: T, code: Int, headers: Seq[(String, String)]) {
def contentType: Option[String] = header(ContentTypeHeader)
def contentLength: Option[Long] =
header(ContentLengthHeader).flatMap(cl => Try(cl.toLong).toOption)
+
+ def cookies: Seq[Cookie] =
+ headers(SetCookieHeader)
+ .flatMap(h => HttpCookie.parse(h).asScala.map(hc => Cookie.apply(hc, h)))
+}
+
+case class Cookie(name: String,
+ value: String,
+ expires: Option[ZonedDateTime] = None,
+ maxAge: Option[Long] = None,
+ domain: Option[String] = None,
+ path: Option[String] = None,
+ secure: Boolean = false,
+ httpOnly: Boolean = false)
+
+object Cookie {
+ def apply(hc: HttpCookie, h: String): Cookie = {
+ // HttpCookie.parse has special handling for the expires attribute and turns it into max-age
+ // if the cookie contains an expires header; hand-parsing in such case to preserve the
+ // values from the cookie
+ val lch = h.toLowerCase
+ val (expires, maxAge) = if (lch.contains("expires=")) {
+ val tokenizer = new StringTokenizer(h, ";")
+ var e: Option[ZonedDateTime] = None
+ var ma: Option[Long] = None
+
+ while (tokenizer.hasMoreTokens) {
+ val t = tokenizer.nextToken()
+ val nv = t.split("=", 2)
+ if (nv(0).toLowerCase.contains("expires") && nv.length > 1) {
+ e = expiryDate2ZonedDateTime(nv(1).trim())
+ }
+ if (nv(0).toLowerCase.contains("max-age") && nv.length > 1) {
+ ma = Try(nv(1).toLong).toOption
+ }
+ }
+
+ (e, ma)
+ } else {
+ (None, if (hc.getMaxAge == -1) None else Some(hc.getMaxAge))
+ }
+
+ Cookie(
+ hc.getName,
+ hc.getValue,
+ expires,
+ maxAge,
+ Option(hc.getDomain),
+ Option(hc.getPath),
+ hc.getSecure,
+ hc.isHttpOnly
+ )
+ }
+
+ /**
+ * Modified version of `HttpCookie.expiryDate2DeltaSeconds` to return a `ZonedDateTime`, not a second-delta.
+ */
+ private def expiryDate2ZonedDateTime(
+ dateString: String): Option[ZonedDateTime] = {
+ val cal = new GregorianCalendar(Gmt)
+ CookieDateFormats.foreach { format =>
+ val df = new SimpleDateFormat(format, Locale.US)
+ cal.set(1970, 0, 1, 0, 0, 0)
+ df.setTimeZone(Gmt)
+ df.setLenient(false)
+ df.set2DigitYearStart(cal.getTime)
+ try {
+ cal.setTime(df.parse(dateString))
+ if (!format.contains("yyyy")) { // 2-digit years following the standard set
+ // out it rfc 6265
+ var year = cal.get(Calendar.YEAR)
+ year %= 100
+ if (year < 70) year += 2000
+ else year += 1900
+ cal.set(Calendar.YEAR, year)
+ }
+
+ return Some(cal.toZonedDateTime)
+ } catch {
+ case e: Exception =>
+ // Ignore, try the next date format
+ }
+ }
+
+ None
+ }
+ private val Gmt = TimeZone.getTimeZone("GMT")
+ private val CookieDateFormats = List(
+ "EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",
+ "EEE',' dd MMM yyyy HH:mm:ss 'GMT'",
+ "EEE MMM dd yyyy HH:mm:ss 'GMT'Z",
+ "EEE',' dd-MMM-yy HH:mm:ss 'GMT'",
+ "EEE',' dd MMM yy HH:mm:ss 'GMT'",
+ "EEE MMM dd yy HH:mm:ss 'GMT'Z"
+ )
}
diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala
index c39f256..a64908a 100644
--- a/core/src/main/scala/com/softwaremill/sttp/package.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/package.scala
@@ -233,6 +233,7 @@ package object sttp {
private[sttp] val ContentTypeHeader = "content-type"
private[sttp] val ContentLengthHeader = "content-length"
+ private[sttp] val SetCookieHeader = "set-cookie"
private val Utf8 = "utf-8"
private val ApplicationOctetStreamContentType = "application/octet-stream"