Skip to content

Commit 5be0bac

Browse files
authored
Allow malformed MIME types: ignore BadContentTypeFormatExceptions (#787)
* Allow malformed MIME types: ignore BadContentTypeFormatExceptions When fetching calendar data, if the server responds with a malformed MIME type in the Content-Type header, we now log a warning and proceed without a content type instead of failing with error message. * Add space * Add tests
1 parent 8c9bade commit 5be0bac

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

app/src/androidTest/java/at/bitfire/icsdroid/CalendarFetcherTest.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,76 @@ class CalendarFetcherTest {
206206

207207
assertEquals(IOException::class.java, e?.javaClass)
208208
}
209+
210+
@Test
211+
fun testFetchNetwork_validContentType() {
212+
// Test that when a server returns a valid Content-Type header,
213+
// the fetcher correctly parses it and processes the calendar data with the proper contentType
214+
val icalCorrect = testContext.resources.openRawResource(R.raw.vienna_evolution).use { streamCorrect ->
215+
streamCorrect.bufferedReader().use { it.readText() }
216+
}
217+
218+
// create mock response with valid Content-Type header
219+
MockServer.enqueue(
220+
content = icalCorrect,
221+
status = HttpStatusCode.OK,
222+
headers = buildHeaders {
223+
append(HttpHeaders.ContentType, "text/calendar; charset=utf-8")
224+
}
225+
)
226+
227+
// make request to local mock server
228+
var ical: String? = null
229+
var receivedContentType: ContentType? = null
230+
val fetcher = object: CalendarFetcher(appContext, MockServer.uri(), client) {
231+
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
232+
ical = data.bufferedReader().use { it.readText() }
233+
receivedContentType = contentType
234+
}
235+
}
236+
runBlocking {
237+
fetcher.fetch()
238+
}
239+
240+
// assert content is correct and contentType is properly parsed
241+
assertEquals(icalCorrect, ical)
242+
assertEquals("text/calendar; charset=utf-8", receivedContentType.toString())
243+
}
244+
245+
@Test
246+
fun testFetchNetwork_malformedContentType() {
247+
// Test that when a server returns a malformed Content-Type header,
248+
// the fetcher logs a warning and successfully processes the calendar data with null contentType
249+
val icalCorrect = testContext.resources.openRawResource(R.raw.vienna_evolution).use { streamCorrect ->
250+
streamCorrect.bufferedReader().use { it.readText() }
251+
}
252+
253+
// create mock response with malformed Content-Type header (containts "-" at start)
254+
// that will trigger BadContentTypeFormatException to be thrown
255+
MockServer.enqueue(
256+
content = icalCorrect,
257+
status = HttpStatusCode.OK,
258+
headers = buildHeaders {
259+
append(HttpHeaders.ContentType, "-Text/calendar charset=utf-8")
260+
}
261+
)
262+
263+
// make request to local mock server
264+
var ical: String? = null
265+
var receivedContentType: ContentType? = null
266+
val fetcher = object: CalendarFetcher(appContext, MockServer.uri(), client) {
267+
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
268+
ical = data.bufferedReader().use { it.readText() }
269+
receivedContentType = contentType
270+
}
271+
}
272+
runBlocking {
273+
fetcher.fetch()
274+
}
275+
276+
// assert content is correct and contentType is null (due to malformed header)
277+
assertEquals(icalCorrect, ical)
278+
assertEquals(null, receivedContentType)
279+
}
280+
209281
}

app/src/main/java/at/bitfire/icsdroid/CalendarFetcher.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.ktor.client.request.basicAuth
1515
import io.ktor.client.request.get
1616
import io.ktor.client.request.header
1717
import io.ktor.client.statement.bodyAsChannel
18+
import io.ktor.http.BadContentTypeFormatException
1819
import io.ktor.http.ContentType
1920
import io.ktor.http.HttpHeaders
2021
import io.ktor.http.HttpStatusCode
@@ -154,16 +155,23 @@ open class CalendarFetcher(
154155
val statusCode = response.status
155156
when {
156157
// 20x
157-
statusCode.isSuccess() ->
158+
statusCode.isSuccess() -> {
159+
val contentType: ContentType? = try {
160+
response.contentType()
161+
} catch (e: BadContentTypeFormatException) {
162+
Log.w(Constants.TAG, "Failed to parse content type. Continuing without.", e)
163+
null
164+
}
158165
onSuccess(
159166
response.bodyAsChannel().toInputStream(),
160-
response.contentType(),
167+
contentType,
161168
response.headers[HttpHeaders.ETag],
162169
response.headers[HttpHeaders.LastModified]?.let {
163170
HttpUtils.parseDate(it)?.time
164171
},
165172
null
166173
)
174+
}
167175

168176
// 30x
169177
statusCode == HttpStatusCode.NotModified -> onNotModified()

0 commit comments

Comments
 (0)