diff --git a/coil-base/src/androidTest/java/coil/map/FileUriMapperTest.kt b/coil-base/src/androidTest/java/coil/map/FileUriMapperTest.kt index da735363d2..9a5d8dbd8c 100644 --- a/coil-base/src/androidTest/java/coil/map/FileUriMapperTest.kt +++ b/coil-base/src/androidTest/java/coil/map/FileUriMapperTest.kt @@ -47,6 +47,7 @@ class FileUriMapperTest { assertNull(mapper.map(uri, Options(context))) } + /** Regression test: https://github.com/coil-kt/coil/issues/1344 */ @Test fun parsesPoundCharacterCorrectly() { val path = "/sdcard/fi#le.jpg" diff --git a/coil-base/src/main/java/coil/network/CacheResponse.kt b/coil-base/src/main/java/coil/network/CacheResponse.kt index 1acae7fd95..b6cf9b43bc 100644 --- a/coil-base/src/main/java/coil/network/CacheResponse.kt +++ b/coil-base/src/main/java/coil/network/CacheResponse.kt @@ -1,5 +1,6 @@ package coil.network +import coil.util.addUnsafeNonAscii import okhttp3.CacheControl import okhttp3.Headers import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -25,7 +26,7 @@ internal class CacheResponse { val responseHeadersLineCount = source.readUtf8LineStrict().toInt() val responseHeaders = Headers.Builder() for (i in 0 until responseHeadersLineCount) { - responseHeaders.add(source.readUtf8LineStrict()) + responseHeaders.addUnsafeNonAscii(source.readUtf8LineStrict()) } this.responseHeaders = responseHeaders.build() } diff --git a/coil-base/src/main/java/coil/util/Utils.kt b/coil-base/src/main/java/coil/util/Utils.kt index 166862f7d6..8cb9296807 100644 --- a/coil-base/src/main/java/coil/util/Utils.kt +++ b/coil-base/src/main/java/coil/util/Utils.kt @@ -232,6 +232,13 @@ internal fun isAssetUri(uri: Uri): Boolean { return uri.scheme == SCHEME_FILE && uri.firstPathSegment == ASSET_FILE_PATH_ROOT } +/** Modified from [Headers.Builder.add] */ +internal fun Headers.Builder.addUnsafeNonAscii(line: String) = apply { + val index = line.indexOf(':') + require(index != -1) { "Unexpected header: $line" } + addUnsafeNonAscii(line.substring(0, index).trim(), line.substring(index + 1)) +} + private const val STANDARD_MEMORY_MULTIPLIER = 0.2 private const val LOW_MEMORY_MULTIPLIER = 0.15 diff --git a/coil-base/src/test/java/coil/network/CacheResponseTest.kt b/coil-base/src/test/java/coil/network/CacheResponseTest.kt index 52032c3da7..3a127d5aeb 100644 --- a/coil-base/src/test/java/coil/network/CacheResponseTest.kt +++ b/coil-base/src/test/java/coil/network/CacheResponseTest.kt @@ -1,13 +1,11 @@ package coil.network -import android.content.Context -import androidx.test.core.app.ApplicationProvider import coil.util.createMockWebServer +import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.MockResponse import okio.Buffer -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -16,17 +14,9 @@ import kotlin.test.assertEquals @RunWith(RobolectricTestRunner::class) class CacheResponseTest { - private lateinit var context: Context - private lateinit var server: MockWebServer - - @Before - fun before() { - context = ApplicationProvider.getApplicationContext() - server = createMockWebServer(IMAGE) - } - @Test fun `can serialize and deserialize cache response`() { + val server = createMockWebServer(IMAGE) val url = server.url(IMAGE) val request = Request.Builder().url(url).build() val response = OkHttpClient().newCall(request).execute() @@ -42,6 +32,29 @@ class CacheResponseTest { assertEquals(expected.responseHeaders, actual.responseHeaders) } + /** Regression test: https://github.com/coil-kt/coil/issues/1467 */ + @Test + fun `can deserialize cache response with non ascii characters in headers`() { + val headerName = "name" + val headerValue = "微信图片" + val server = createMockWebServer() + val responseHeaders = Headers.Builder() + .addUnsafeNonAscii(headerName, headerValue) + .build() + server.enqueue(MockResponse().setHeaders(responseHeaders).setBody(Buffer())) + val url = server.url("微信图片.jpg") + val request = Request.Builder().url(url).build() + val response = OkHttpClient().newCall(request).execute() + val expected = CacheResponse(response) + + val buffer = Buffer() + expected.writeTo(buffer) + val actual = CacheResponse(buffer) + + assertEquals(headerValue, actual.responseHeaders[headerName]) + assertEquals(expected.responseHeaders, actual.responseHeaders) + } + companion object { private const val IMAGE = "normal.jpg" }