diff options
author | adamw <adam@warski.org> | 2017-07-15 11:59:10 +0200 |
---|---|---|
committer | adamw <adam@warski.org> | 2017-07-15 11:59:10 +0200 |
commit | 8d9145a04cdd21a471fba61fc19203759b95514d (patch) | |
tree | 0d5eb11f6c87ed8d2f41b483e381852d871c8ee1 /core | |
parent | bc685df2cd50814b45e669f4f602732887c2879c (diff) | |
download | sttp-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.scala | 107 | ||||
-rw-r--r-- | core/src/main/scala/com/softwaremill/sttp/package.scala | 1 |
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" |