Не подтверждена Коммит 5ec968c3 создал по автору Leonid Stashevsky's avatar Leonid Stashevsky Зафиксировано автором GitHub
Просмотр файлов

KTOR-917 KTOR-993 Prevent URI Encoding for Safe Chars (#3179)

* KTOR-993 Add test to verify URI encoding

* KTOR-917 Keep some characters in cookie URI encoding according to RFC 3986

* Fix codestyle warnings
владелец e1bfa586
......@@ -44,11 +44,11 @@ class CookiesTest {
@Test
fun testCookiesAreRenderedWithSpaceInBetween() = testSuspend {
var storage = AcceptAllCookiesStorage()
val storage = AcceptAllCookiesStorage()
storage.addCookie("http://localhost/", Cookie("name1", "value1"))
storage.addCookie("http://localhost/", Cookie("name2", "value2"))
val feature = HttpCookies(storage, emptyList())
var builder = HttpRequestBuilder()
val builder = HttpRequestBuilder()
feature.sendCookiesWith(builder)
......
......@@ -194,7 +194,7 @@ class CookiesTest : ClientLoader() {
val response = httpResponse.bodyAsText()
val cookieStrings = response.split("; ").filter { it.isNotBlank() }
assertEquals(4, cookieStrings.size)
assertEquals("uri=first%2C+cookie", cookieStrings[0])
assertEquals("uri=first,+cookie", cookieStrings[0])
assertEquals("raw=first%2C+cookie", cookieStrings[1])
assertEquals("base64=Zmlyc3QsIGNvb2tpZQ==", cookieStrings[2])
assertEquals("dquotes=\"first, cookie\"", cookieStrings[3])
......
......@@ -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)
}
/**
......
......@@ -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())
......
/*
* 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.http
import io.ktor.http.*
import kotlin.test.*
class UrlEncodeTest {
@Test
fun testUrlEncodePathKeepDigits() {
assertEquals("0123456789", "0123456789".encodeURLPath())
}
@Test
fun testUrlEncodeQueryComponentKeepDigits() {
assertEquals("0123456789", "0123456789".encodeURLQueryComponent())
}
@Test
fun testUrlKeepDigitsInPath() {
assertEquals("/0123456789/", Url("http://x.com/0123456789/").encodedPath)
}
@Test
fun testUrlEncodePathKeepLetters() {
assertEquals(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".encodeURLPath()
)
}
@Test
fun testUrlEncodeQueryComponentKeepLetters() {
assertEquals(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".encodeURLQueryComponent()
)
}
@Test
fun testUrlKeepLettersInPath() {
assertEquals(
"/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/",
Url("http://x.com/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/").encodedPath
)
}
@Test
fun testUrlEncodePathKeepHyphen() {
assertEquals("-", "-".encodeURLPath())
}
@Test
fun testUrlEncodeQueryComponentKeepHyphen() {
assertEquals("-", "-".encodeURLQueryComponent())
}
@Test
fun testUrlKeepHyphenInPath() {
assertEquals("/-/", Url("http://x.com/-/").encodedPath)
}
@Test
fun testUrlEncodePathKeepPeriod() {
assertEquals(".", ".".encodeURLPath())
}
@Test
fun testUrlEncodeQueryComponentKeepPeriod() {
assertEquals(".", ".".encodeURLQueryComponent())
}
@Test
fun testUrlKeepPeriodInPath() {
assertEquals("/./", Url("http://x.com/./").encodedPath)
}
@Test
fun testUrlEncodePathKeepUnderscore() {
assertEquals("_", "_".encodeURLPath())
}
@Test
fun testUrlEncodeQueryComponentKeepUnderscore() {
assertEquals("_", "_".encodeURLQueryComponent())
}
@Test
fun testUrlKeepUnderscoreInPath() {
assertEquals("/_/", Url("http://x.com/_/").encodedPath)
}
@Test
fun testUrlEncodePathKeepTilde() {
assertEquals("~", "~".encodeURLPath())
}
@Test
fun testUrlEncodeQueryComponentKeepTilde() {
assertEquals("~", "~".encodeURLQueryComponent())
}
@Test
fun testUrlKeepTildeInPath() {
assertEquals("/~/", Url("http://x.com/~/").encodedPath)
}
}
......@@ -242,7 +242,6 @@ class MyCalculatorPlugin {
): MyCalculatorPlugin {
return MyCalculatorPlugin()
}
}
fun add(x: Int, y: Int): Int {
......
/*
* 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
}
}
......@@ -81,7 +81,7 @@ class CookiesTest {
@Test
fun `add cookies bad characters`() {
testSetCookies("AB=1%3A2") {
testSetCookies("AB=1:2") {
cookies.append("AB", "1:2")
}
}
......@@ -176,204 +176,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)
}
}
/*
* 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")
}
}
/*
* 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)
}
}
/*
* 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)
}
}
/*
* 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")
}
}
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать