Commit f5c0a4fa authored by Ricki Hirner's avatar Ricki Hirner 🐑

Fix invalid TZOFFSETFROM/TO values like +5730 so that ical4j/Java doesn't throw an exception

parent f2a7e34c
Pipeline #150700565 passed with stages
in 5 minutes and 47 seconds
......@@ -12,7 +12,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0-rc01'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
}
......@@ -82,6 +82,7 @@ dependencies {
}
// ical4j requires JavaMail API (e.g. for EMAIL parameter)
implementation 'com.sun.mail:android-mail:1.6.5'
implementation 'commons-io:commons-io:2.6'
implementation 'org.slf4j:slf4j-jdk14:1.7.30'
implementation 'androidx.core:core-ktx:1.2.0'
......
......@@ -21,6 +21,7 @@ import net.fortuna.ical4j.model.property.RDate
import java.io.StringReader
import java.text.ParseException
import java.text.SimpleDateFormat
import java.time.ZoneOffset
import java.util.*
/**
......
......@@ -6,6 +6,11 @@ import net.fortuna.ical4j.transform.rfc5545.CreatedPropertyRule
import net.fortuna.ical4j.transform.rfc5545.DateListPropertyRule
import net.fortuna.ical4j.transform.rfc5545.DatePropertyRule
import net.fortuna.ical4j.transform.rfc5545.Rfc5545PropertyRule
import org.apache.commons.io.IOUtils
import java.io.IOException
import java.io.Reader
import java.io.StringReader
import java.util.*
import java.util.logging.Level
/**
......@@ -18,12 +23,58 @@ import java.util.logging.Level
*/
object ICalPreprocessor {
private val TZOFFSET_REGEXP = Regex("^(TZOFFSET(FROM|TO):[+\\-]?)((18|19|[2-6]\\d)\\d\\d)$", RegexOption.MULTILINE)
private val propertyRules = arrayOf(
CreatedPropertyRule(), // make sure CREATED is UTC
DatePropertyRule(),
DateListPropertyRule()
)
/**
* Some servers modify UTC offsets in TZOFFSET(FROM,TO) like "+005730" to an invalid "+5730".
*
* Rewrites values of all TZOFFSETFROM and TZOFFSETTO properties which match [TZOFFSET_REGEXP]
* so that an hour value of 00 is inserted.
*
* @param reader Reader that reads the potentially broken iCalendar (which for instance contains `TZOFFSETFROM:+5730`)
* @return Reader that reads the fixed iCalendar (for instance `TZOFFSETFROM:+005730`)
*/
fun fixInvalidUtcOffset(reader: Reader): Reader {
fun fixStringFromReader() =
IOUtils.toString(reader).replace(TZOFFSET_REGEXP) {
Ical4Android.log.log(Level.FINE, "Applying Synology WebDAV fix to invalid utc-offset", it.value)
"${it.groupValues[1]}00${it.groupValues[3]}"
}
var result: String? = null
val resetSupported = try {
reader.reset()
true
} catch(e: IOException) {
false
}
if (resetSupported) {
// reset is supported, no need to copy the whole stream to another String (unless we have to fix the TZOFFSET)
if (Scanner(reader).findWithinHorizon(TZOFFSET_REGEXP.toPattern(), 0) != null) {
reader.reset()
result = fixStringFromReader()
}
} else
result = fixStringFromReader()
if (result != null)
return StringReader(result)
// not modified, return original iCalendar
reader.reset()
return reader
}
/**
* Applies the set of rules (see class definition) to a given calendar object.
*
......
......@@ -76,10 +76,13 @@ open class ICalendar {
Ical4Android.log.fine("Parsing iCalendar stream")
Ical4Android.checkThreadContextClassLoader()
// apply hacks and workarounds that operate on plain text level
val reader2 = ICalPreprocessor.fixInvalidUtcOffset(reader)
// parse stream
val calendar: Calendar
try {
calendar = CalendarBuilder().build(reader)
calendar = CalendarBuilder().build(reader2)
} catch(e: ParserException) {
throw InvalidCalendarException("Couldn't parse iCalendar", e)
} catch(e: IllegalArgumentException) {
......
......@@ -17,6 +17,7 @@ import net.fortuna.ical4j.model.property.RRule
import net.fortuna.ical4j.model.property.RecurrenceId
import org.junit.Assert.*
import org.junit.Test
import java.io.BufferedReader
import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException
import java.io.InputStreamReader
......
......@@ -3,12 +3,62 @@ package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.model.Component
import net.fortuna.ical4j.model.component.VEvent
import org.apache.commons.io.IOUtils
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.InputStreamReader
import java.io.StringReader
class ICalPreprocessorTest {
@Test
fun testFixInvalidUtcOffset() {
val invalid = "BEGIN:VEVENT" +
"SUMMARY:Test" +
"DTSTART;TZID=Test:19970714T133000" +
"END:VEVENT" +
"BEGIN:VTIMEZONE\n" +
"TZID:Test\n" +
"BEGIN:DAYLIGHT\n" +
"DTSTART:19670430T020000\n" +
"TZOFFSETFROM:-5730\n" +
"TZOFFSETTO:+1920\n" +
"TZNAME:EDT\n" +
"END:DAYLIGHT\n" +
"BEGIN:STANDARD\n" +
"DTSTART:19671029T020000\n" +
"TZOFFSETFROM:-0400\n" +
"TZOFFSETTO:-0500\n" +
"TZNAME:EST" +
"END:STANDARD\n" +
"END:VTIMEZONE"
val valid = "BEGIN:VEVENT" +
"SUMMARY:Test" +
"DTSTART;TZID=Test:19970714T133000" +
"END:VEVENT" +
"BEGIN:VTIMEZONE\n" +
"TZID:Test\n" +
"BEGIN:DAYLIGHT\n" +
"DTSTART:19670430T020000\n" +
"TZOFFSETFROM:-005730\n" +
"TZOFFSETTO:+001920\n" +
"TZNAME:EDT\n" +
"END:DAYLIGHT\n" +
"BEGIN:STANDARD\n" +
"DTSTART:19671029T020000\n" +
"TZOFFSETFROM:-0400\n" +
"TZOFFSETTO:-0500\n" +
"TZNAME:EST" +
"END:STANDARD\n" +
"END:VTIMEZONE"
ICalPreprocessor.fixInvalidUtcOffset(StringReader(invalid)).let { result ->
assertEquals(valid, IOUtils.toString(result))
}
ICalPreprocessor.fixInvalidUtcOffset(StringReader(valid)).let { result ->
assertEquals(valid, IOUtils.toString(result))
}
}
@Test
fun testMsTimeZones() {
javaClass.classLoader!!.getResourceAsStream("events/outlook1.ics").use { stream ->
......
......@@ -15,6 +15,7 @@ import net.fortuna.ical4j.model.parameter.Value
import net.fortuna.ical4j.model.property.*
import org.junit.Assert.*
import org.junit.Test
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStreamReader
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment