From f776d7cbbf82bb6e60f5db9b48daab9887d18dfa Mon Sep 17 00:00:00 2001 From: Rustam Date: Fri, 22 Jan 2021 17:02:05 +0100 Subject: [PATCH] KTOR-1797 Add check for unescaped quote inside quoted header parameter (#2314) --- .../io/ktor/http/HeaderValueWithParameters.kt | 30 ++++++++++++++++- .../test/io/ktor/tests/http/HeadersTest.kt | 33 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt b/ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt index ec4b0cb02c1..a53121a811b 100644 --- a/ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt +++ b/ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt @@ -93,7 +93,35 @@ private fun String.checkNeedEscape(): Boolean { return false } -private fun String.isQuoted(): Boolean = length > 1 && first() == '\"' && last() == '\"' +private fun String.isQuoted(): Boolean { + if (length < 2) { + return false + } + if (first() != '"' || last() != '"') { + return false + } + var startIndex = 1 + do { + val index = indexOf('"', startIndex) + if (index == lastIndex) { + break + } + + var slashesCount = 0 + var slashIndex = index - 1 + while (this[slashIndex] == '\\') { + slashesCount++ + slashIndex-- + } + if (slashesCount % 2 == 0) { + return false + } + + startIndex = index + 1 + } while (startIndex < length) + + return true +} /** * Escape string using double quotes diff --git a/ktor-http/common/test/io/ktor/tests/http/HeadersTest.kt b/ktor-http/common/test/io/ktor/tests/http/HeadersTest.kt index 08a7f531034..41e2650d49e 100644 --- a/ktor-http/common/test/io/ktor/tests/http/HeadersTest.kt +++ b/ktor-http/common/test/io/ktor/tests/http/HeadersTest.kt @@ -245,6 +245,39 @@ class HeadersTest { } } + @Test + fun testRenderQuotesIfHasUnescapedQuotes() { + // first + assertEquals( + """file; k="\"\"vv\""""", + ContentDisposition.File.withParameter("k", """""vv"""").toString() + ) + // middle + assertEquals( + """file; k="\"v\"v\""""", + ContentDisposition.File.withParameter("k", """"v"v"""").toString() + ) + // last + assertEquals( + """file; k="\"vv\"\""""", + ContentDisposition.File.withParameter("k", """"vv""""").toString() + ) + // escaped slash + assertEquals( + """file; k="\"v\\\\\"v\""""", + ContentDisposition.File.withParameter("k", """"v\\"v"""").toString() + ) + } + + @Test + fun testDoesNotRenderQuotesIfHasEscapedQuotes() { + // middle + assertEquals( + """file; k="v\"v"""", + ContentDisposition.File.withParameter("k", """"v\"v"""").toString() + ) + } + @Test fun headersOfShouldBeCaseInsensitive() { val value = "world"