Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,76 @@ class CalendarFetcherTest {

assertEquals(IOException::class.java, e?.javaClass)
}

@Test
fun testFetchNetwork_validContentType() {
// Test that when a server returns a valid Content-Type header,
// the fetcher correctly parses it and processes the calendar data with the proper contentType
val icalCorrect = testContext.resources.openRawResource(R.raw.vienna_evolution).use { streamCorrect ->
streamCorrect.bufferedReader().use { it.readText() }
}

// create mock response with valid Content-Type header
MockServer.enqueue(
content = icalCorrect,
status = HttpStatusCode.OK,
headers = buildHeaders {
append(HttpHeaders.ContentType, "text/calendar; charset=utf-8")
}
)

// make request to local mock server
var ical: String? = null
var receivedContentType: ContentType? = null
val fetcher = object: CalendarFetcher(appContext, MockServer.uri(), client) {
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
ical = data.bufferedReader().use { it.readText() }
receivedContentType = contentType
}
}
runBlocking {
fetcher.fetch()
}

// assert content is correct and contentType is properly parsed
assertEquals(icalCorrect, ical)
assertEquals("text/calendar; charset=utf-8", receivedContentType.toString())
}

@Test
fun testFetchNetwork_malformedContentType() {
// Test that when a server returns a malformed Content-Type header,
// the fetcher logs a warning and successfully processes the calendar data with null contentType
val icalCorrect = testContext.resources.openRawResource(R.raw.vienna_evolution).use { streamCorrect ->
streamCorrect.bufferedReader().use { it.readText() }
}

// create mock response with malformed Content-Type header (containts "-" at start)
// that will trigger BadContentTypeFormatException to be thrown
MockServer.enqueue(
content = icalCorrect,
status = HttpStatusCode.OK,
headers = buildHeaders {
append(HttpHeaders.ContentType, "-Text/calendar charset=utf-8")
}
)

// make request to local mock server
var ical: String? = null
var receivedContentType: ContentType? = null
val fetcher = object: CalendarFetcher(appContext, MockServer.uri(), client) {
override suspend fun onSuccess(data: InputStream, contentType: ContentType?, eTag: String?, lastModified: Long?, displayName: String?) {
ical = data.bufferedReader().use { it.readText() }
receivedContentType = contentType
}
}
runBlocking {
fetcher.fetch()
}

// assert content is correct and contentType is null (due to malformed header)
assertEquals(icalCorrect, ical)
assertEquals(null, receivedContentType)
}

}
12 changes: 10 additions & 2 deletions app/src/main/java/at/bitfire/icsdroid/CalendarFetcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.ktor.client.request.basicAuth
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.BadContentTypeFormatException
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
Expand Down Expand Up @@ -154,16 +155,23 @@ open class CalendarFetcher(
val statusCode = response.status
when {
// 20x
statusCode.isSuccess() ->
statusCode.isSuccess() -> {
val contentType: ContentType? = try {
response.contentType()
} catch (e: BadContentTypeFormatException) {
Log.w(Constants.TAG, "Failed to parse content type. Continuing without.", e)
null
}
onSuccess(
response.bodyAsChannel().toInputStream(),
response.contentType(),
contentType,
response.headers[HttpHeaders.ETag],
response.headers[HttpHeaders.LastModified]?.let {
HttpUtils.parseDate(it)?.time
},
null
)
}

// 30x
statusCode == HttpStatusCode.NotModified -> onNotModified()
Expand Down