Skip to content

Commit 4bb9d3f

Browse files
committed
GZipFilter: Weaken ETag when gzipping
1 parent 4b97fa0 commit 4bb9d3f

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

‎web/play-filters-helpers/src/main/scala/play/filters/gzip/GzipFilter.scala‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,18 @@ class GzipFilter @Inject() (config: GzipFilterConfig)(implicit mat: Materializer
186186
*/
187187
private def isNotAlreadyCompressed(header: ResponseHeader) = header.headers.get(CONTENT_ENCODING).isEmpty
188188

189+
/**
190+
* Weakens a strong ETag, since gzip encoding changes the byte representation.
191+
*/
192+
private def weakenETag(etag: String): Option[String] = etag match {
193+
case weak if weak.startsWith("W/") => Some(weak) // already weak, leave as-is
194+
case strong if strong.startsWith("\"") => Some(s"W/$strong") // strong ETag: "tag" → W/"tag"
195+
case _ => None // invalid ETag: can't be weakened correctly, drop it
196+
}
197+
189198
private def setupHeader(rh: ResponseHeader): Map[String, String] = {
190-
rh.headers + (CONTENT_ENCODING -> "gzip") + rh.varyWith(ACCEPT_ENCODING)
199+
val headers = rh.headers.updatedWith(ETAG)(_.flatMap(weakenETag))
200+
headers + (CONTENT_ENCODING -> "gzip") + rh.copy(headers = headers).varyWith(ACCEPT_ENCODING)
191201
}
192202
}
193203

‎web/play-filters-helpers/src/test/scala/play/filters/gzip/GzipFilterSpec.scala‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,46 @@ class GzipFilterSpec extends PlaySpecification with DataTables {
225225
threshold = 18
226226
) { implicit app => checkGzippedBody(makeGzipRequest(app), "these are 18 bytes")(using app.materializer) }
227227

228+
"weaken ETag header when gziping a response" in withApplication(
229+
Ok("hello").withHeaders(ETAG -> "\"abc123\"")
230+
) { implicit app =>
231+
val result = makeGzipRequest(app)
232+
checkGzippedBody(result, "hello")(using app.materializer)
233+
header(ETAG, result) must beSome("W/\"abc123\"")
234+
}
235+
236+
"not weaken ETag header when not gziping a response" in withApplication(
237+
Ok("hello").withHeaders(ETAG -> "\"abc123\"")
238+
) { implicit app =>
239+
val result = route(app, FakeRequest().withHeaders(ACCEPT_ENCODING -> "identity")).get
240+
checkNotGzipped(result, "hello")(using app.materializer)
241+
header(ETAG, result) must beSome("\"abc123\"")
242+
}
243+
244+
"not add ETag header when gziping a response without an ETag header" in withApplication(
245+
Ok("hello")
246+
) { implicit app =>
247+
val result = makeGzipRequest(app)
248+
checkGzippedBody(result, "hello")(using app.materializer)
249+
header(ETAG, result) must beNone
250+
}
251+
252+
"not modify ETag header when gziping a response with an already weak ETag header" in withApplication(
253+
Ok("hello").withHeaders(ETAG -> "W/\"abc123\"")
254+
) { implicit app =>
255+
val result = makeGzipRequest(app)
256+
checkGzippedBody(result, "hello")(using app.materializer)
257+
header(ETAG, result) must beSome("W/\"abc123\"")
258+
}
259+
260+
"remove invalid ETag header when gzipping a response" in withApplication(
261+
Ok("hello").withHeaders(ETAG -> "abc123") // unquoted ETag value; a common violation of RFC 7232
262+
) { implicit app =>
263+
val result = makeGzipRequest(app)
264+
checkGzippedBody(result, "hello")(using app.materializer)
265+
header(ETAG, result) must beNone
266+
}
267+
228268
val body = Random.nextString(1000)
229269

230270
"a streamed body" should {

0 commit comments

Comments
 (0)