diff --git a/ktor-http/common/src/io/ktor/http/Cookie.kt b/ktor-http/common/src/io/ktor/http/Cookie.kt index 464b9858ba3..4cc4d554644 100644 --- a/ktor-http/common/src/io/ktor/http/Cookie.kt +++ b/ktor-http/common/src/io/ktor/http/Cookie.kt @@ -183,7 +183,7 @@ public fun encodeCookieValue(value: String, encoding: CookieEncoding): String = else -> value } CookieEncoding.BASE64_ENCODING -> value.encodeBase64() - CookieEncoding.URI_ENCODING -> value.encodeURLQueryComponent(encodeFull = true, spaceToPlus = true) + CookieEncoding.URI_ENCODING -> value.encodeURLQueryComponent(encodeFull = false, spaceToPlus = true) } /** diff --git a/ktor-http/common/test/io/ktor/tests/http/CodecTest.kt b/ktor-http/common/test/io/ktor/tests/http/CodecTest.kt index a4ddad98959..7534f1fb07d 100644 --- a/ktor-http/common/test/io/ktor/tests/http/CodecTest.kt +++ b/ktor-http/common/test/io/ktor/tests/http/CodecTest.kt @@ -141,7 +141,7 @@ class CodecTest { mapOf( "a" to listOf("b", "c", "d"), "1" to listOf("2"), - "x" to listOf("y", "z"), + "x" to listOf("y", "z") ).entries.formUrlEncodeTo(result) assertEquals("a=b&a=c&a=d&1=2&x=y&x=z", result.toString()) diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/Base64EncodingCookiesTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/Base64EncodingCookiesTest.kt new file mode 100644 index 00000000000..e7f2e4c1ad4 --- /dev/null +++ b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/Base64EncodingCookiesTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.server.plugins + +import io.ktor.http.* +import kotlin.test.* + +class Base64EncodingCookiesTest { + @Test + fun `no bad characters`() { + testEncode("YWJj", "abc") + } + + @Test + fun `space inside`() { + testEncode("YWJjIDEyMw==", "abc 123") + } + + @Test + fun `equals inside`() { + testEncode("YWJjPTEyMw==", "abc=123") + } + + private fun testEncode(expected: String, value: String): String { + val encoded = encodeCookieValue(value, CookieEncoding.BASE64_ENCODING) + assertEquals(expected, encoded, "Encode failed") + assertEquals(value, decodeCookieValue(encoded, CookieEncoding.BASE64_ENCODING), "Decode failed") + return encoded + } +} diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/CookiesTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/CookiesTest.kt index 369b9d3fe2d..c2947b92234 100644 --- a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/CookiesTest.kt +++ b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/CookiesTest.kt @@ -177,203 +177,3 @@ class CookiesTest { private fun String.cutSetCookieHeader() = substringBeforeLast("\$x-enc").trimEnd().removeSuffix(";") } -class DQuotesEncodingTest { - @Test - fun `no bad characters`() { - testEncode("abc", "abc") - } - - @Test - fun `space inside`() { - testEncode("\"abc 123\"", "abc 123") - } - - @Test - fun `equals inside`() { - testEncode("abc=123", "abc=123") - } - - private fun testEncode(expected: String, value: String) { - val encoded = encodeCookieValue(value, CookieEncoding.DQUOTES) - assertEquals(expected, encoded, "Encode failed") - assertEquals(value, decodeCookieValue(encoded, CookieEncoding.DQUOTES), "Decode failed") - } -} - -class Base64EncodingTest { - @Test - fun `no bad characters`() { - testEncode("YWJj", "abc") - } - - @Test - fun `space inside`() { - testEncode("YWJjIDEyMw==", "abc 123") - } - - @Test - fun `equals inside`() { - testEncode("YWJjPTEyMw==", "abc=123") - } - - private fun testEncode(expected: String, value: String): String { - val encoded = encodeCookieValue(value, CookieEncoding.BASE64_ENCODING) - assertEquals(expected, encoded, "Encode failed") - assertEquals(value, decodeCookieValue(encoded, CookieEncoding.BASE64_ENCODING), "Decode failed") - return encoded - } -} - -class URIEncodingTest { - @Test - fun `no bad characters`() { - testEncode("abc", "abc") - } - - @Test - fun `space inside`() { - testEncode("abc+123", "abc 123") - } - - @Test - fun `equals inside`() { - testEncode("abc%3D123", "abc=123") - } - - private fun testEncode(expected: String, value: String) { - val encoded = encodeCookieValue(value, CookieEncoding.URI_ENCODING) - assertEquals(expected, encoded, "Encode failed") - assertEquals(value, decodeCookieValue(encoded, CookieEncoding.URI_ENCODING), "Decode failed") - } -} - -class RawCookieTest { - @Test - fun testRawEncodingWithEquals() { - val cookieValue = "my.value.key=my.value.value+my.value.id=5" - val encoded = encodeCookieValue(cookieValue, CookieEncoding.RAW) - assertEquals(cookieValue, encoded) - } -} - -class ParserServerSetCookieTest { - @Test - fun testSimpleParse() { - val header = "key=value; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("value", parsed.value) - assertEquals(999, parsed.maxAge) - assertEquals("1", parsed.extensions["\$extension"]) - assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) - } - - @Test - fun testSimpleParseCustomEncoding() { - val header = "key=value; max-Age=999; \$extension=1; \$x-enc=RAW" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("value", parsed.value) - assertEquals(999, parsed.maxAge) - assertEquals("1", parsed.extensions["\$extension"]) - assertEquals(CookieEncoding.RAW, parsed.encoding) - } - - @Test - fun testSimpleParseMissingEncoding() { - val header = "key=value; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("value", parsed.value) - assertEquals(999, parsed.maxAge) - assertEquals("1", parsed.extensions["\$extension"]) - assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) - } - - @Test - fun testSimpleParseVersionAtStart() { - val header = "\$Version=1; key=value; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("value", parsed.value) - assertEquals(999, parsed.maxAge) - assertEquals("1", parsed.extensions["\$extension"]) - assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) - } - - @Test - fun testParseWithQuotes() { - val header = "key=\"aaa; bbb = ccc\"; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("aaa; bbb = ccc", parsed.value) - assertEquals(999, parsed.maxAge) - assertEquals("1", parsed.extensions["\$extension"]) - assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) - } - - @Test - fun testParseExpires() { - val header = "SESSION=cart%3D%2523cl%26userId%3D%2523sid1; " + - "Expires=Sat, 16 Jan 2016 13:43:28 GMT; HttpOnly; \$x-enc=URI_ENCODING" - val parsed = parseServerSetCookieHeader(header) - - val expires = parsed.expires - assertNotNull(expires) - assertEquals(2016, expires.year) - assertEquals(io.ktor.util.date.Month.JANUARY, expires.month) - assertEquals(16, expires.dayOfMonth) - } - - @Test - fun testParseBase64() { - val header = "SESSION=MTIzCg==; \$x-enc=BASE64_ENCODING" - val parsed = parseServerSetCookieHeader(header) - assertEquals("123\n", parsed.value) - } - - @Test - fun testMaxAge() { - val header = "key=aaa; max-age=999" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("aaa", parsed.value) - assertEquals(999, parsed.maxAge) - } - - @Test - fun testMaxAgeNegative() { - val header = "key=aaa; max-age=-1" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("aaa", parsed.value) - assertEquals(0, parsed.maxAge) - } - - @Test - fun testMaxAgeTooLong() { - val header = "key=aaa; max-age=3153600000" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("key", parsed.name) - assertEquals("aaa", parsed.value) - assertEquals(Int.MAX_VALUE, parsed.maxAge) - } - - @Test - fun testSlash() { - val header = "384f8bdb/sessid=GLU787LwmQa9uLqnM7nWHzBm; path=/" - val parsed = parseServerSetCookieHeader(header) - - assertEquals("384f8bdb/sessid", parsed.name) - assertEquals("GLU787LwmQa9uLqnM7nWHzBm", parsed.value) - assertEquals("/", parsed.path) - } -} diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/DQuotesCookiesEncodingTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/DQuotesCookiesEncodingTest.kt new file mode 100644 index 00000000000..c925bed8d5b --- /dev/null +++ b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/DQuotesCookiesEncodingTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.server.plugins + +import io.ktor.http.* +import kotlin.test.* + +class DQuotesCookiesEncodingTest { + @Test + fun `no bad characters`() { + testEncode("abc", "abc") + } + + @Test + fun `space inside`() { + testEncode("\"abc 123\"", "abc 123") + } + + @Test + fun `equals inside`() { + testEncode("abc=123", "abc=123") + } + + private fun testEncode(expected: String, value: String) { + val encoded = encodeCookieValue(value, CookieEncoding.DQUOTES) + assertEquals(expected, encoded, "Encode failed") + assertEquals(value, decodeCookieValue(encoded, CookieEncoding.DQUOTES), "Decode failed") + } +} diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/ParserServerSetCookieTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/ParserServerSetCookieTest.kt new file mode 100644 index 00000000000..c22f483b98d --- /dev/null +++ b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/ParserServerSetCookieTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.server.plugins + +import io.ktor.http.* +import io.ktor.util.date.* +import kotlin.test.* + +class ParserServerSetCookieTest { + @Test + fun testSimpleParse() { + val header = "key=value; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("value", parsed.value) + assertEquals(999, parsed.maxAge) + assertEquals("1", parsed.extensions["\$extension"]) + assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) + } + + @Test + fun testSimpleParseCustomEncoding() { + val header = "key=value; max-Age=999; \$extension=1; \$x-enc=RAW" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("value", parsed.value) + assertEquals(999, parsed.maxAge) + assertEquals("1", parsed.extensions["\$extension"]) + assertEquals(CookieEncoding.RAW, parsed.encoding) + } + + @Test + fun testSimpleParseMissingEncoding() { + val header = "key=value; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("value", parsed.value) + assertEquals(999, parsed.maxAge) + assertEquals("1", parsed.extensions["\$extension"]) + assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) + } + + @Test + fun testSimpleParseVersionAtStart() { + val header = "\$Version=1; key=value; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("value", parsed.value) + assertEquals(999, parsed.maxAge) + assertEquals("1", parsed.extensions["\$extension"]) + assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) + } + + @Test + fun testParseWithQuotes() { + val header = "key=\"aaa; bbb = ccc\"; max-Age=999; \$extension=1; \$x-enc=URI_ENCODING" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("aaa; bbb = ccc", parsed.value) + assertEquals(999, parsed.maxAge) + assertEquals("1", parsed.extensions["\$extension"]) + assertEquals(CookieEncoding.URI_ENCODING, parsed.encoding) + } + + @Test + fun testParseExpires() { + val header = "SESSION=cart%3D%2523cl%26userId%3D%2523sid1; " + + "Expires=Sat, 16 Jan 2016 13:43:28 GMT; HttpOnly; \$x-enc=URI_ENCODING" + val parsed = parseServerSetCookieHeader(header) + + val expires = parsed.expires + assertNotNull(expires) + assertEquals(2016, expires.year) + assertEquals(Month.JANUARY, expires.month) + assertEquals(16, expires.dayOfMonth) + } + + @Test + fun testParseBase64() { + val header = "SESSION=MTIzCg==; \$x-enc=BASE64_ENCODING" + val parsed = parseServerSetCookieHeader(header) + assertEquals("123\n", parsed.value) + } + + @Test + fun testMaxAge() { + val header = "key=aaa; max-age=999" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("aaa", parsed.value) + assertEquals(999, parsed.maxAge) + } + + @Test + fun testMaxAgeNegative() { + val header = "key=aaa; max-age=-1" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("aaa", parsed.value) + assertEquals(0, parsed.maxAge) + } + + @Test + fun testMaxAgeTooLong() { + val header = "key=aaa; max-age=3153600000" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("key", parsed.name) + assertEquals("aaa", parsed.value) + assertEquals(Int.MAX_VALUE, parsed.maxAge) + } + + @Test + fun testSlash() { + val header = "384f8bdb/sessid=GLU787LwmQa9uLqnM7nWHzBm; path=/" + val parsed = parseServerSetCookieHeader(header) + + assertEquals("384f8bdb/sessid", parsed.name) + assertEquals("GLU787LwmQa9uLqnM7nWHzBm", parsed.value) + assertEquals("/", parsed.path) + } +} diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/RawCookieTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/RawCookieTest.kt new file mode 100644 index 00000000000..d05b6e0e37e --- /dev/null +++ b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/RawCookieTest.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.server.plugins + +import io.ktor.http.* +import kotlin.test.* + +class RawCookieTest { + @Test + fun testRawEncodingWithEquals() { + val cookieValue = "my.value.key=my.value.value+my.value.id=5" + val encoded = encodeCookieValue(cookieValue, CookieEncoding.RAW) + assertEquals(cookieValue, encoded) + } +} diff --git a/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/URIEncodingCookiesTest.kt b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/URIEncodingCookiesTest.kt new file mode 100644 index 00000000000..545923482ee --- /dev/null +++ b/ktor-server/ktor-server-tests/jvmAndNix/test/io/ktor/tests/server/plugins/URIEncodingCookiesTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.server.plugins + +import io.ktor.http.* +import kotlin.test.* + +class URIEncodingCookiesTest { + @Test + fun `no bad characters`() { + testEncode("abc", "abc") + } + + @Test + fun `space inside`() { + testEncode("abc+123", "abc 123") + } + + @Test + fun `equals inside`() { + testEncode("abc=123", "abc=123") + } + + @Test + fun `encode keep digits`() { + testEncode("0123456789", "0123456789") + } + + @Test + fun `encode keep letters`() { + testEncode( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + ) + } + + @Test + fun `encode keep hypen`() { + testEncode("abc-123", "abc-123") + } + + @Test + fun `encode keep underscore`() { + testEncode("abc_123", "abc_123") + } + + @Test + fun `encode keep period`() { + testEncode("abc.123", "abc.123") + } + + @Test + fun `encode keep tilde`() { + testEncode("abc~123", "abc~123") + } + + private fun testEncode(expected: String, value: String) { + val encoded = encodeCookieValue(value, CookieEncoding.URI_ENCODING) + assertEquals(expected, encoded, "Encode failed") + assertEquals(value, decodeCookieValue(encoded, CookieEncoding.URI_ENCODING), "Decode failed") + } +}