Skip to content

Commit

Permalink
[mkcal] Treat empty VTIMEZONE definition as invalid timezone. Contrib…
Browse files Browse the repository at this point in the history
…utes to JB#43708
  • Loading branch information
Chris Adams committed Nov 26, 2019
1 parent b285899 commit 45e962b
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 4 deletions.
6 changes: 6 additions & 0 deletions src/sqliteformat.cpp
Expand Up @@ -1114,6 +1114,12 @@ static KDateTime getDateTime(SqliteStorage *storage, sqlite3_stmt *stmt, int ind
} else {
date = sqlite3_column_int64(stmt, index);
dateTime = storage->fromOriginTime(date, timezone);
if (!dateTime.isValid()) {
// timezone is specified but invalid?
// fall back to local seconds from origin as clock time.
date = sqlite3_column_int64(stmt, index + 1);
dateTime = storage->fromLocalOriginTime(date);
}
}
if (isDate) {
QTime localTime(dateTime.toLocalZone().time());
Expand Down
13 changes: 9 additions & 4 deletions src/sqlitestorage.cpp
Expand Up @@ -3020,6 +3020,13 @@ sqlite3_int64 SqliteStorage::toLocalOriginTime(KDateTime dt)
+ dt1.time().secsTo(dt2.time());
}

KDateTime SqliteStorage::fromLocalOriginTime(sqlite3_int64 seconds)
{
// Note: don't call toClockTime() as that implies a conversion first to the local time zone.
KDateTime local(d->mOriginTime.addSecs(seconds));
return KDateTime(local.date(), local.time(), KDateTime::ClockTime);
}

KDateTime SqliteStorage::fromOriginTime(sqlite3_int64 seconds)
{
//qCDebug(lcMkcal) << "fromOriginTime" << seconds << d->mOriginTime.addSecs( seconds ).toUtc();
Expand All @@ -3041,12 +3048,10 @@ KDateTime SqliteStorage::fromOriginTime(sqlite3_int64 seconds, QString zonename)
// Then try calendar specific zones.
ICalTimeZones::ZoneMap zones = d->mCalendar->timeZones()->zones();
ICalTimeZone icaltimezone = zones.value(zonename);
if (icaltimezone.isValid()) {
const QByteArray emptyTz = "BEGIN:VTIMEZONE\r\nTZID:" + zonename.toUtf8() + "\r\nEND:VTIMEZONE\r\n";
if (icaltimezone.isValid() && icaltimezone.vtimezone() != emptyTz) {
dt =
d->mOriginTime.addSecs(seconds).toUtc().toTimeSpec(KDateTime::Spec(icaltimezone));
} else {
// Invalid zone, fall back to UTC.
dt = d->mOriginTime.addSecs(seconds).toUtc();
}
}
} else {
Expand Down
7 changes: 7 additions & 0 deletions src/sqlitestorage.h
Expand Up @@ -360,6 +360,13 @@ class MKCAL_EXPORT SqliteStorage : public ExtendedStorage
*/
sqlite3_int64 toLocalOriginTime(KDateTime dt);

/**
Convert seconds from the origin to clock time.
@param seconds relative to origin.
@return clocktime datetime.
*/
KDateTime fromLocalOriginTime(sqlite3_int64 seconds);

/**
Convert seconds from the origin to UTC datetime.
@param seconds relative to origin.
Expand Down
170 changes: 170 additions & 0 deletions tests/tst_storage.cpp
Expand Up @@ -26,6 +26,69 @@ namespace {
return input.toTimeSpec(spec);
}
}

QString readStreamString(QDataStream &stream, int length)
{
QString result;
short character = 0;
for (int i = 0; i < length && stream.status() == QDataStream::Ok; ++i) {
stream >> character;
if (character) {
result.append(QChar(character));
}
}

return result;
}

QDataStream &operator>>(QDataStream &stream, long &value)
{
qint32 temp = 0;
stream >> temp;
value = temp;
return stream;
}

QDataStream &operator>>(QDataStream &stream, KCalCore::_MSSystemTime &value)
{
stream >> value.wYear;
stream >> value.wMonth;
stream >> value.wDayOfWeek;
stream >> value.wDay;
stream >> value.wHour;
stream >> value.wMinute;
stream >> value.wSecond;
stream >> value.wMilliseconds;

return stream;
}

QDataStream &operator>>(QDataStream &stream, KCalCore::_MSTimeZone &value)
{
stream >> value.Bias;
value.StandardName = readStreamString(stream, 32);
stream >> value.StandardDate;
stream >> value.StandardBias;
value.DaylightName = readStreamString(stream, 32);
stream >> value.DaylightDate;
stream >> value.DaylightBias;

return stream;
}

KCalCore::_MSTimeZone parseMsTimeZone(const QByteArray &encodedBuffer)
{
static const int DecodedMsTimeZoneLength = 172;
const QByteArray decodedTimeZoneBuffer = encodedBuffer.length() == DecodedMsTimeZoneLength
? encodedBuffer
: QByteArray::fromBase64(encodedBuffer);

KCalCore::_MSTimeZone mstz;
QDataStream readStream(decodedTimeZoneBuffer);
readStream.setByteOrder(QDataStream::LittleEndian);
readStream >> mstz;
return mstz;
}
}

tst_storage::tst_storage(QObject *parent)
Expand Down Expand Up @@ -61,6 +124,113 @@ void tst_storage::tst_timezone()
QCOMPARE(localTime.utcOffset(), 7200);
}

void tst_storage::tst_vtimezone_data()
{
QTest::addColumn<QByteArray>("encodedMsTimeZone");
QTest::addColumn<bool>("encodedMsTzValid");
QTest::addColumn<QDateTime>("dateTime");
QTest::addColumn<QString>("timezone");
QTest::addColumn<QString>("eventUid");

// Helsinki,Kyiv,Riga (UTC+2)
QTest::newRow("helsinki vtimezone")
<< QByteArrayLiteral("iP///ygAVQBUAEMAKwAwADIAOgAwADAA"
"KQAgAEgAZQBsAHMAaQBuAGsAaQAsACAA"
"SwB5AGkAdgAsACAAUgBpAGcAYQAAAAoA"
"AAAFAAQAAAAAAAAAAAAAACgAVQBUAEMA"
"KwAwADIAOgAwADAAKQAgAEgAZQBsAHMA"
"aQBuAGsAaQAsACAASwB5AGkAdgAsACAA"
"UgBpAGcAYQAAAAMAAAAFAAMAAAAAAAAA"
"xP///w==")
<< true
<< QDateTime(QDate(2019, 11, 06), QTime(10, 00, 00))
<< QStringLiteral("Europe/Helsinki")
<< QStringLiteral("tst_vtimezone:helsinki vtimezone");

// Amsterdam,Berlin (UTC+1)
QTest::newRow("berlin vtimezone")
<< QByteArrayLiteral("xP///ygAVQBUAEMAKwAwADEAOgAwADAA"
"KQAgAEEAbQBzAHQAZQByAGQAYQBtACwA"
"IABCAGUAcgBsAGkAbgAsACAAQgAAAAoA"
"AAAFAAMAAAAAAAAAAAAAACgAVQBUAEMA"
"KwAwADEAOgAwADAAKQAgAEEAbQBzAHQA"
"ZQByAGQAYQBtACwAIABCAGUAcgBsAGkA"
"bgAsACAAQgAAAAMAAAAFAAIAAAAAAAAA"
"xP///w==")
<< true
<< QDateTime(QDate(2019, 11, 06), QTime(10, 00, 00))
<< QStringLiteral("Europe/Berlin")
<< QStringLiteral("tst_vtimezone:berlin vtimezone");

// Brisbane (UTC+10). NOTE: this test will fail unless you
// delete the database or clear the
// timezones table first.
QTest::newRow("brisbane vtimezone")
<< QByteArrayLiteral("qP3//ygAVQBUAEMAKwAxADAAOgAwADAA"
"KQAgAEIAcgBpAHMAYgBhAG4AZQAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAACgAVQBUAEMA"
"KwAxADAAOgAwADAAKQAgAEIAcgBpAHMA"
"YgBhAG4AZQAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAA==")
<< false // not entirely sure why, but offset info not stored?
<< QDateTime(QDate(2019, 11, 06), QTime(10, 00, 00))
<< QStringLiteral("Australia/Brisbane")
<< QStringLiteral("tst_vtimezone:brisbane vtimezone");
}

void tst_storage::tst_vtimezone()
{
QFETCH(QByteArray, encodedMsTimeZone);
QFETCH(bool, encodedMsTzValid);
QFETCH(QDateTime, dateTime);
QFETCH(QString, timezone);
QFETCH(QString, eventUid);

KCalCore::_MSTimeZone mstz = parseMsTimeZone(encodedMsTimeZone);
KCalCore::ICalTimeZone vtimezone = m_calendar->parseZone(&mstz);

const KDateTime kdtvtz(dateTime, KDateTime::Spec(vtimezone));
const KDateTime kdtstz(dateTime, KDateTime::Spec(KSystemTimeZones::zone(timezone)));
const KDateTime endkdtstz(dateTime.addSecs(3600), KDateTime::Spec(KSystemTimeZones::zone(timezone)));

QCOMPARE(kdtvtz.toString(), kdtstz.toString());

// add an event which lasts an hour starting at the given date time in vtimezone spec.
KCalCore::Event::Ptr event = KCalCore::Event::Ptr(new KCalCore::Event());
event->startUpdates();
event->setUid(eventUid);
event->setLocation(QStringLiteral("Test location"));
event->setAllDay(false);
event->setDtStart(kdtvtz);
event->setDtEnd(KDateTime(dateTime.addSecs(3600), KDateTime::Spec(vtimezone)));
event->setDescription(QStringLiteral("Test description"));
event->setSummary(QStringLiteral("Test event summary"));
event->setCategories(QStringList() << QStringLiteral("Category One"));
event->endUpdates();

m_calendar->addEvent(event, NotebookId);
m_storage->save();
const QString uid = event->uid();
reloadDb();

auto fetchEvent = m_calendar->event(uid);
QVERIFY(fetchEvent);
if (encodedMsTzValid) {
QCOMPARE(fetchEvent->dtStart().toString(), kdtstz.toString());
QCOMPARE(fetchEvent->dtEnd().toString(), endkdtstz.toString());
} else {
// if we were unable to reconstruct the vtimezone from the database data,
// we expect that the returned event will match the expected time,
// from clock-time perspective.
QCOMPARE(kdatetimeAsTimeSpec(fetchEvent->dtStart(), KDateTime::ClockTime),
kdatetimeAsTimeSpec(kdtstz, KDateTime::ClockTime));
QCOMPARE(kdatetimeAsTimeSpec(fetchEvent->dtEnd(), KDateTime::ClockTime),
kdatetimeAsTimeSpec(endkdtstz, KDateTime::ClockTime));
}
}

void tst_storage::tst_allday_data()
{
QTest::addColumn<QDate>("startDate");
Expand Down
2 changes: 2 additions & 0 deletions tests/tst_storage.h
Expand Up @@ -22,6 +22,8 @@ private slots:
void cleanup();

void tst_timezone();
void tst_vtimezone_data();
void tst_vtimezone();
void tst_allday_data();
void tst_allday();
void tst_alldayUtc();
Expand Down

0 comments on commit 45e962b

Please sign in to comment.