Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
- Headers map is no longer exposed
Browse files Browse the repository at this point in the history
- invalid dateheader no longer causes IllegalArgumentException
- added CaseInsensitiveString to preserve the case of the Header name, but allow case insensitive comparison.
  • Loading branch information
hamnis committed Oct 31, 2009
1 parent 3d9dc1e commit 7e15852
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.hamnaberg.recondo

import java.util.Locale
import java.text.Collator

/**
* @author <a href="mailto:erlend@hamnaberg.net">Erlend Hamnaberg</a>
* @version $Revision: $
*/
case class CaseInsensitiveString(original: String) {

override def equals(obj: Any) : Boolean = {
if (obj == null) return false
if (!obj.isInstanceOf[CaseInsensitiveString]) return false
val n = obj.asInstanceOf[CaseInsensitiveString]
if (!original.equalsIgnoreCase(n.original)) {
return false
}
return true
}

override def hashCode = 31 * original.toLowerCase(Locale.ENGLISH).hashCode
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ object Header {
val formatter = DateTimeFormat.forPattern(PATTERN_RFC1123).
withZone(DateTimeZone.forID("UTC")).
withLocale(Locale.US);
formatter.parseDateTime(header.value);
try {
Some(formatter.parseDateTime(header.value))
}
catch {
case e:IllegalArgumentException => None
};
}

def toHttpDate(name: String, time: DateTime) = {
Expand Down
30 changes: 16 additions & 14 deletions recondo-api/src/main/scala/net/hamnaberg/recondo/Headers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import net.liftweb.json.JsonAST._
* @author <a href="mailto:erlend@hamnaberg.net">Erlend Hamnaberg</a>
* @version $Revision : #5 $ $Date: 2008/09/15 $
*/
class Headers(h: Map[String, List[String]]) extends Iterable[Header] with ToJSON {
private[recondo] val headers = Map() ++ h
class Headers private(h: Map[CaseInsensitiveString, List[String]]) extends Iterable[Header] with ToJSON {
import Headers._
private[this] val headers = Map() ++ h

def this() = this(Map());

Expand All @@ -43,7 +44,7 @@ class Headers(h: Map[String, List[String]]) extends Iterable[Header] with ToJSON
def +(header: Header): Headers = {
val heads = headers.get(header.name).getOrElse(Nil)
if (!heads.contains(header.value)) {
new Headers(headers + (header.name -> (header.value :: heads)))
new Headers(headers + (new CaseInsensitiveString(header.name) -> (header.value :: heads)))
}
else {
this
Expand All @@ -54,15 +55,15 @@ class Headers(h: Map[String, List[String]]) extends Iterable[Header] with ToJSON
val x = headers.get(header.name).map(_ - header.value).getOrElse(Nil)
x match {
case List() => new Headers(headers - header.name)
case x => new Headers(headers + (header.name -> x))
case x => new Headers(headers + (new CaseInsensitiveString(header.name) -> x))
}
}

def -(headerName: String): Headers = {
new Headers(headers - headerName)
}
def --(headerNames: Iterable[String]): Headers = {
new Headers(headers -- headerNames)
new Headers(headers -- headerNames.map(x => new CaseInsensitiveString(x)))
}

def ++(heads: Iterable[Header]): Headers = heads.foldLeft(this){_ + _}
Expand All @@ -74,8 +75,6 @@ class Headers(h: Map[String, List[String]]) extends Iterable[Header] with ToJSON

def contains(name: String) = !getHeaders(name).isEmpty

private[recondo] def asMap = headers;

override def equals(obj: Any) = {
if (obj.isInstanceOf[Headers]) {
val h = obj.asInstanceOf[Headers]
Expand Down Expand Up @@ -104,24 +103,24 @@ class Headers(h: Map[String, List[String]]) extends Iterable[Header] with ToJSON
if (contains(Header(VARY, "*"))) return false
val interestingHeaderNames = Set(CACHE_CONTROL, EXPIRES, LAST_MODIFIED)
val cacheableHeaders = new Headers(headers.filter(interestingHeaderNames contains _._1)).toList
val dateHeaderValue = firstHeader(DATE).map(Header.fromHttpDate(_)).getOrElse(return false)

val dateHeaderValue = firstHeader(DATE).map(Header.fromHttpDate(_)).map{case Some(x) => x}.getOrElse(return false)
val now = new DateTime
for (h <- cacheableHeaders) {
if (!analyzeCachability(h, dateHeaderValue)) {
if (!analyzeCachability(h, dateHeaderValue, now)) {
return false
}
}
contains(LAST_MODIFIED) || contains(ETAG)
}

private def analyzeCachability(h: Header, dateHeaderValue: DateTime): Boolean = h match {
private def analyzeCachability(h: Header, dateHeaderValue: DateTime, now: DateTime): Boolean = h match {
case Header(CACHE_CONTROL, v) => {
val expire = firstHeader(EXPIRES).map {analyzeCachability(_, dateHeaderValue)} getOrElse false
val expire = firstHeader(EXPIRES).map {analyzeCachability(_, dateHeaderValue, now)} getOrElse false
(v.contains("max-age") || expire) && !(v.contains("no-store") || v.contains("no-cache"))
}
case Header(EXPIRES, _) => dateHeaderValue.isBefore(Header.fromHttpDate(h))
case Header(EXPIRES, _) => dateHeaderValue.isBefore(Header.fromHttpDate(h).getOrElse(now))
case Header(LAST_MODIFIED, _) => {
val lastModified = Header.fromHttpDate(h)
val lastModified = Header.fromHttpDate(h).getOrElse(now)
dateHeaderValue.isAfter(lastModified) || dateHeaderValue.equals(lastModified)
}
}
Expand All @@ -139,4 +138,7 @@ object Headers extends FromJSON[Headers] {
} yield new Header(name, value)
Headers() ++ headers
}

implicit def convertToCaseInsensitive(orignal: String) : CaseInsensitiveString = new CaseInsensitiveString(orignal)
implicit def convertToString(caseInsensitive: CaseInsensitiveString) : String = caseInsensitive.original
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Response(val status: Status, val headers: Headers, val payload: Option[Pay

lazy val lastModified : Option[DateTime] = {
val header = headers firstHeader ("Last-Modified")
header.map(x => Some(Header.fromHttpDate(x))) getOrElse None
header.map(Header.fromHttpDate(_)).map{case Some(x) => x}
}

lazy val allowedMethods : Set[Method] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ class HeaderTest {
Assert.assertEquals(header, Header("foo" -> "foo"));
}

@Test
def testIllegalDateHeader {
Assert.assertFalse("Illegal Date header was legal",Header.fromHttpDate(new Header("foo", "-1")).isDefined)
Assert.assertFalse("Illegal Date header was legal",Header.fromHttpDate(new Header("date", "akdslj")).isDefined)
}

@Test
def testEquals {
val header = new Header("foo", "foo")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class HeadersTest {
val allow = Header("Allow", "GET")
val h = Headers() ++ List(allow, Header("If-Match", new Tag("1234", false).format))
Assert.assertFalse("Headers was empty", h.isEmpty)
Assert.assertEquals("Headers was not empty", 2, h.size)
Assert.assertEquals("Headers has the wrong size", 2, h.size)
val h2 = h - allow
Assert.assertFalse("Headers was not empty", h2.isEmpty)
Assert.assertEquals("Headers had the wrong size", 1, h2.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ class Cache(val storage: Storage, val resolver: ResponseResolver) {

private[this] def updateCacheFromResolved(request: Request, resolvedResponse: Response, cachedResponse: Response): Response = {
val updatedHeaders = resolvedResponse.headers -- Helper.unmodifiableHeaders
val cachedHeaders = cachedResponse.headers.asMap
val newHeaders = cachedHeaders ++ updatedHeaders.asMap
val response = new Response(cachedResponse.status, new Headers(newHeaders), cachedResponse.payload)
val cachedHeaders = cachedResponse.headers
val newHeaders = cachedHeaders ++ updatedHeaders
val response = new Response(cachedResponse.status, newHeaders, cachedResponse.payload)
storage.update(request, response)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package net.hamnaberg.recondo.core


import org.joda.time.{Seconds, DateTime}
import org.joda.time.format.{DateTimeFormat}
import net.liftweb.json.JsonDSL._
import net.hamnaberg.recondo._

/**
Expand Down Expand Up @@ -32,8 +30,9 @@ object CacheItem {
}
}
else if (headers contains(HeaderConstants.EXPIRES)) {
val expires = Header.fromHttpDate(headers.first(HeaderConstants.EXPIRES))
val date = Header.fromHttpDate(headers.first(HeaderConstants.DATE))
val now = new DateTime()
val expires = Header.fromHttpDate(headers.first(HeaderConstants.EXPIRES)).getOrElse(now)
val date = Header.fromHttpDate(headers.first(HeaderConstants.DATE)).getOrElse(now)
Seconds.secondsBetween(date, expires).getSeconds
}
else {
Expand Down

0 comments on commit 7e15852

Please sign in to comment.