2 commits - src/CMakeLists.txt src/kolabconversions.h src/kolabformat.cpp src/objectvalidation.cpp src/objectvalidation.h src/xcalconversions.h tests/CMakeLists.txt tests/validationtest.cpp tests/validationtest.h tztable.h utils/zonetabconversion.py

Christian Mollekopf mollekopf at kolabsys.com
Wed Sep 18 10:26:53 CEST 2013


 src/CMakeLists.txt         |    1 
 src/kolabconversions.h     |    6 
 src/kolabformat.cpp        |   28 ++
 src/objectvalidation.cpp   |  142 +++++++++++++++
 src/objectvalidation.h     |   52 +++++
 src/xcalconversions.h      |   11 -
 tests/CMakeLists.txt       |    5 
 tests/validationtest.cpp   |   88 +++++++++
 tests/validationtest.h     |   36 +++
 tztable.h                  |  424 +++++++++++++++++++++++++++++++++++++++++++++
 utils/zonetabconversion.py |   29 +++
 11 files changed, 806 insertions(+), 16 deletions(-)

New commits:
commit db3731bfcbf49e5f8cbd9a2d2fb7c1d1f3d1b702
Author: Christian Mollekopf <mollekopf at kolabsys.com>
Date:   Wed Sep 18 10:25:37 2013 +0200

    Whitelist valid timezones according to the content of zone.tab.

diff --git a/src/objectvalidation.cpp b/src/objectvalidation.cpp
index 04cd0b6..eeaa660 100644
--- a/src/objectvalidation.cpp
+++ b/src/objectvalidation.cpp
@@ -24,22 +24,36 @@
 #include "kolabconfiguration.h"
 #include "kolabfile.h"
 #include "utils.h"
+#include "tztable.h"
+#include <boost/unordered_set.hpp>
 
 namespace Kolab {
 
+static boost::unordered::unordered_set<std::string> initializeTzSet() {
+    boost::unordered::unordered_set<std::string> set;
+    for (int i = 0; i < numOlsonTimezones; i++) {
+        set.insert(olsonTimezones[i]);
+    }
+    return set;
+}
+
+const boost::unordered::unordered_set<std::string> tzSet = initializeTzSet();
+
 bool isValid(const cDateTime &datetime)
 {
     if (!datetime.isValid()) {
         return true;
     }
     const std::string tz = datetime.timezone();
-    if (datetime.isUTC() && !tz.empty()) {
-        Utils::logMessage("A UTC datetime may not have a timezone", "", 0, Error);
-        return false;
-    }
-    if (tz == "Z") {
-        Utils::logMessage("Z is not a valid timezone. Set to UTC instead", "", 0, Error);
-        return false;
+    if (!tz.empty()) {
+        if (datetime.isUTC() && !tz.empty()) {
+            Utils::logMessage("A UTC datetime may not have a timezone", "", 0, Error);
+            return false;
+        }
+        if (tzSet.find(tz) == tzSet.end()) {
+            Utils::logMessage("not a valid olson timezone.", "", 0, Error);
+            return false;
+        }
     }
     return true;
 }
@@ -91,7 +105,7 @@ void validate(const Todo& todo)
 
 void validate(const Journal& journal)
 {
-
+    ASSERTVALID(journal.start());
 }
 
 void validate(const Contact& contact)
diff --git a/tests/validationtest.cpp b/tests/validationtest.cpp
index 5a1aa58..97a928e 100644
--- a/tests/validationtest.cpp
+++ b/tests/validationtest.cpp
@@ -39,6 +39,14 @@ void ValidationTest::testNoErrorOnValidEvent()
     QCOMPARE(Kolab::error(), Kolab::NoError);
 }
 
+void ValidationTest::testOlsonTimezone()
+{
+    Event event;
+    event.setStart(cDateTime("Europe/Zurich",2013,1,1,1,1,1));
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::NoError);
+}
+
 void ValidationTest::testDifferentTimezones()
 {
     Event event;
@@ -66,6 +74,13 @@ void ValidationTest::testTimezoneZ()
     QCOMPARE(Kolab::error(), Kolab::Error);
 }
 
+void ValidationTest::testWindowsTimezone()
+{
+    Event event;
+    event.setStart(cDateTime("Central European Standard Time",2013,1,1,1,1,1));
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::Error);
+}
 
 
 QTEST_MAIN( ValidationTest )
diff --git a/tests/validationtest.h b/tests/validationtest.h
index 3a2c5e1..4751ca0 100644
--- a/tests/validationtest.h
+++ b/tests/validationtest.h
@@ -26,9 +26,11 @@ class ValidationTest: public QObject {
 private slots:
     void testErrorOnEmptyEvent();
     void testNoErrorOnValidEvent();
+    void testOlsonTimezone();
     void testDifferentTimezones();
     void testUTCwithTimezone();
     void testTimezoneZ();
+    void testWindowsTimezone();
 };
 
 #endif
diff --git a/tztable.h b/tztable.h
new file mode 100644
index 0000000..f59eb1a
--- /dev/null
+++ b/tztable.h
@@ -0,0 +1,424 @@
+//This file was generated by the zonetabconversion.py script
+static const char* olsonTimezones[] = {
+    "Europe/Andorra",
+    "Asia/Dubai",
+    "Asia/Kabul",
+    "America/Antigua",
+    "America/Anguilla",
+    "Europe/Tirane",
+    "Asia/Yerevan",
+    "Africa/Luanda",
+    "Antarctica/McMurdo",
+    "Antarctica/South_Pole",
+    "Antarctica/Rothera",
+    "Antarctica/Palmer",
+    "Antarctica/Mawson",
+    "Antarctica/Davis",
+    "Antarctica/Casey",
+    "Antarctica/Vostok",
+    "Antarctica/DumontDUrville",
+    "Antarctica/Syowa",
+    "America/Argentina/Buenos_Aires",
+    "America/Argentina/Cordoba",
+    "America/Argentina/Salta",
+    "America/Argentina/Jujuy",
+    "America/Argentina/Tucuman",
+    "America/Argentina/Catamarca",
+    "America/Argentina/La_Rioja",
+    "America/Argentina/San_Juan",
+    "America/Argentina/Mendoza",
+    "America/Argentina/San_Luis",
+    "America/Argentina/Rio_Gallegos",
+    "America/Argentina/Ushuaia",
+    "Pacific/Pago_Pago",
+    "Europe/Vienna",
+    "Australia/Lord_Howe",
+    "Antarctica/Macquarie",
+    "Australia/Hobart",
+    "Australia/Currie",
+    "Australia/Melbourne",
+    "Australia/Sydney",
+    "Australia/Broken_Hill",
+    "Australia/Brisbane",
+    "Australia/Lindeman",
+    "Australia/Adelaide",
+    "Australia/Darwin",
+    "Australia/Perth",
+    "Australia/Eucla",
+    "America/Aruba",
+    "Europe/Mariehamn",
+    "Asia/Baku",
+    "Europe/Sarajevo",
+    "America/Barbados",
+    "Asia/Dhaka",
+    "Europe/Brussels",
+    "Africa/Ouagadougou",
+    "Europe/Sofia",
+    "Asia/Bahrain",
+    "Africa/Bujumbura",
+    "Africa/Porto-Novo",
+    "America/St_Barthelemy",
+    "Atlantic/Bermuda",
+    "Asia/Brunei",
+    "America/La_Paz",
+    "America/Kralendijk",
+    "America/Noronha",
+    "America/Belem",
+    "America/Fortaleza",
+    "America/Recife",
+    "America/Araguaina",
+    "America/Maceio",
+    "America/Bahia",
+    "America/Sao_Paulo",
+    "America/Campo_Grande",
+    "America/Cuiaba",
+    "America/Santarem",
+    "America/Porto_Velho",
+    "America/Boa_Vista",
+    "America/Manaus",
+    "America/Eirunepe",
+    "America/Rio_Branco",
+    "America/Nassau",
+    "Asia/Thimphu",
+    "Africa/Gaborone",
+    "Europe/Minsk",
+    "America/Belize",
+    "America/St_Johns",
+    "America/Halifax",
+    "America/Glace_Bay",
+    "America/Moncton",
+    "America/Goose_Bay",
+    "America/Blanc-Sablon",
+    "America/Montreal",
+    "America/Toronto",
+    "America/Nipigon",
+    "America/Thunder_Bay",
+    "America/Iqaluit",
+    "America/Pangnirtung",
+    "America/Resolute",
+    "America/Atikokan",
+    "America/Rankin_Inlet",
+    "America/Winnipeg",
+    "America/Rainy_River",
+    "America/Regina",
+    "America/Swift_Current",
+    "America/Edmonton",
+    "America/Cambridge_Bay",
+    "America/Yellowknife",
+    "America/Inuvik",
+    "America/Creston",
+    "America/Dawson_Creek",
+    "America/Vancouver",
+    "America/Whitehorse",
+    "America/Dawson",
+    "Indian/Cocos",
+    "Africa/Kinshasa",
+    "Africa/Lubumbashi",
+    "Africa/Bangui",
+    "Africa/Brazzaville",
+    "Europe/Zurich",
+    "Africa/Abidjan",
+    "Pacific/Rarotonga",
+    "America/Santiago",
+    "Pacific/Easter",
+    "Africa/Douala",
+    "Asia/Shanghai",
+    "Asia/Harbin",
+    "Asia/Chongqing",
+    "Asia/Urumqi",
+    "Asia/Kashgar",
+    "America/Bogota",
+    "America/Costa_Rica",
+    "America/Havana",
+    "Atlantic/Cape_Verde",
+    "America/Curacao",
+    "Indian/Christmas",
+    "Asia/Nicosia",
+    "Europe/Prague",
+    "Europe/Berlin",
+    "Europe/Busingen",
+    "Africa/Djibouti",
+    "Europe/Copenhagen",
+    "America/Dominica",
+    "America/Santo_Domingo",
+    "Africa/Algiers",
+    "America/Guayaquil",
+    "Pacific/Galapagos",
+    "Europe/Tallinn",
+    "Africa/Cairo",
+    "Africa/El_Aaiun",
+    "Africa/Asmara",
+    "Europe/Madrid",
+    "Africa/Ceuta",
+    "Atlantic/Canary",
+    "Africa/Addis_Ababa",
+    "Europe/Helsinki",
+    "Pacific/Fiji",
+    "Atlantic/Stanley",
+    "Pacific/Chuuk",
+    "Pacific/Pohnpei",
+    "Pacific/Kosrae",
+    "Atlantic/Faroe",
+    "Europe/Paris",
+    "Africa/Libreville",
+    "Europe/London",
+    "America/Grenada",
+    "Asia/Tbilisi",
+    "America/Cayenne",
+    "Europe/Guernsey",
+    "Africa/Accra",
+    "Europe/Gibraltar",
+    "America/Godthab",
+    "America/Danmarkshavn",
+    "America/Scoresbysund",
+    "America/Thule",
+    "Africa/Banjul",
+    "Africa/Conakry",
+    "America/Guadeloupe",
+    "Africa/Malabo",
+    "Europe/Athens",
+    "Atlantic/South_Georgia",
+    "America/Guatemala",
+    "Pacific/Guam",
+    "Africa/Bissau",
+    "America/Guyana",
+    "Asia/Hong_Kong",
+    "America/Tegucigalpa",
+    "Europe/Zagreb",
+    "America/Port-au-Prince",
+    "Europe/Budapest",
+    "Asia/Jakarta",
+    "Asia/Pontianak",
+    "Asia/Makassar",
+    "Asia/Jayapura",
+    "Europe/Dublin",
+    "Asia/Jerusalem",
+    "Europe/Isle_of_Man",
+    "Asia/Kolkata",
+    "Indian/Chagos",
+    "Asia/Baghdad",
+    "Asia/Tehran",
+    "Atlantic/Reykjavik",
+    "Europe/Rome",
+    "Europe/Jersey",
+    "America/Jamaica",
+    "Asia/Amman",
+    "Asia/Tokyo",
+    "Africa/Nairobi",
+    "Asia/Bishkek",
+    "Asia/Phnom_Penh",
+    "Pacific/Tarawa",
+    "Pacific/Enderbury",
+    "Pacific/Kiritimati",
+    "Indian/Comoro",
+    "America/St_Kitts",
+    "Asia/Pyongyang",
+    "Asia/Seoul",
+    "Asia/Kuwait",
+    "America/Cayman",
+    "Asia/Almaty",
+    "Asia/Qyzylorda",
+    "Asia/Aqtobe",
+    "Asia/Aqtau",
+    "Asia/Oral",
+    "Asia/Vientiane",
+    "Asia/Beirut",
+    "America/St_Lucia",
+    "Europe/Vaduz",
+    "Asia/Colombo",
+    "Africa/Monrovia",
+    "Africa/Maseru",
+    "Europe/Vilnius",
+    "Europe/Luxembourg",
+    "Europe/Riga",
+    "Africa/Tripoli",
+    "Africa/Casablanca",
+    "Europe/Monaco",
+    "Europe/Chisinau",
+    "Europe/Podgorica",
+    "America/Marigot",
+    "Indian/Antananarivo",
+    "Pacific/Majuro",
+    "Pacific/Kwajalein",
+    "Europe/Skopje",
+    "Africa/Bamako",
+    "Asia/Rangoon",
+    "Asia/Ulaanbaatar",
+    "Asia/Hovd",
+    "Asia/Choibalsan",
+    "Asia/Macau",
+    "Pacific/Saipan",
+    "America/Martinique",
+    "Africa/Nouakchott",
+    "America/Montserrat",
+    "Europe/Malta",
+    "Indian/Mauritius",
+    "Indian/Maldives",
+    "Africa/Blantyre",
+    "America/Mexico_City",
+    "America/Cancun",
+    "America/Merida",
+    "America/Monterrey",
+    "America/Matamoros",
+    "America/Mazatlan",
+    "America/Chihuahua",
+    "America/Ojinaga",
+    "America/Hermosillo",
+    "America/Tijuana",
+    "America/Santa_Isabel",
+    "America/Bahia_Banderas",
+    "Asia/Kuala_Lumpur",
+    "Asia/Kuching",
+    "Africa/Maputo",
+    "Africa/Windhoek",
+    "Pacific/Noumea",
+    "Africa/Niamey",
+    "Pacific/Norfolk",
+    "Africa/Lagos",
+    "America/Managua",
+    "Europe/Amsterdam",
+    "Europe/Oslo",
+    "Asia/Kathmandu",
+    "Pacific/Nauru",
+    "Pacific/Niue",
+    "Pacific/Auckland",
+    "Pacific/Chatham",
+    "Asia/Muscat",
+    "America/Panama",
+    "America/Lima",
+    "Pacific/Tahiti",
+    "Pacific/Marquesas",
+    "Pacific/Gambier",
+    "Pacific/Port_Moresby",
+    "Asia/Manila",
+    "Asia/Karachi",
+    "Europe/Warsaw",
+    "America/Miquelon",
+    "Pacific/Pitcairn",
+    "America/Puerto_Rico",
+    "Asia/Gaza",
+    "Asia/Hebron",
+    "Europe/Lisbon",
+    "Atlantic/Madeira",
+    "Atlantic/Azores",
+    "Pacific/Palau",
+    "America/Asuncion",
+    "Asia/Qatar",
+    "Indian/Reunion",
+    "Europe/Bucharest",
+    "Europe/Belgrade",
+    "Europe/Kaliningrad",
+    "Europe/Moscow",
+    "Europe/Volgograd",
+    "Europe/Samara",
+    "Asia/Yekaterinburg",
+    "Asia/Omsk",
+    "Asia/Novosibirsk",
+    "Asia/Novokuznetsk",
+    "Asia/Krasnoyarsk",
+    "Asia/Irkutsk",
+    "Asia/Yakutsk",
+    "Asia/Khandyga",
+    "Asia/Vladivostok",
+    "Asia/Sakhalin",
+    "Asia/Ust-Nera",
+    "Asia/Magadan",
+    "Asia/Kamchatka",
+    "Asia/Anadyr",
+    "Africa/Kigali",
+    "Asia/Riyadh",
+    "Pacific/Guadalcanal",
+    "Indian/Mahe",
+    "Africa/Khartoum",
+    "Europe/Stockholm",
+    "Asia/Singapore",
+    "Atlantic/St_Helena",
+    "Europe/Ljubljana",
+    "Arctic/Longyearbyen",
+    "Europe/Bratislava",
+    "Africa/Freetown",
+    "Europe/San_Marino",
+    "Africa/Dakar",
+    "Africa/Mogadishu",
+    "America/Paramaribo",
+    "Africa/Juba",
+    "Africa/Sao_Tome",
+    "America/El_Salvador",
+    "America/Lower_Princes",
+    "Asia/Damascus",
+    "Africa/Mbabane",
+    "America/Grand_Turk",
+    "Africa/Ndjamena",
+    "Indian/Kerguelen",
+    "Africa/Lome",
+    "Asia/Bangkok",
+    "Asia/Dushanbe",
+    "Pacific/Fakaofo",
+    "Asia/Dili",
+    "Asia/Ashgabat",
+    "Africa/Tunis",
+    "Pacific/Tongatapu",
+    "Europe/Istanbul",
+    "America/Port_of_Spain",
+    "Pacific/Funafuti",
+    "Asia/Taipei",
+    "Africa/Dar_es_Salaam",
+    "Europe/Kiev",
+    "Europe/Uzhgorod",
+    "Europe/Zaporozhye",
+    "Europe/Simferopol",
+    "Africa/Kampala",
+    "Pacific/Johnston",
+    "Pacific/Midway",
+    "Pacific/Wake",
+    "America/New_York",
+    "America/Detroit",
+    "America/Kentucky/Louisville",
+    "America/Kentucky/Monticello",
+    "America/Indiana/Indianapolis",
+    "America/Indiana/Vincennes",
+    "America/Indiana/Winamac",
+    "America/Indiana/Marengo",
+    "America/Indiana/Petersburg",
+    "America/Indiana/Vevay",
+    "America/Chicago",
+    "America/Indiana/Tell_City",
+    "America/Indiana/Knox",
+    "America/Menominee",
+    "America/North_Dakota/Center",
+    "America/North_Dakota/New_Salem",
+    "America/North_Dakota/Beulah",
+    "America/Denver",
+    "America/Boise",
+    "America/Shiprock",
+    "America/Phoenix",
+    "America/Los_Angeles",
+    "America/Anchorage",
+    "America/Juneau",
+    "America/Sitka",
+    "America/Yakutat",
+    "America/Nome",
+    "America/Adak",
+    "America/Metlakatla",
+    "Pacific/Honolulu",
+    "America/Montevideo",
+    "Asia/Samarkand",
+    "Asia/Tashkent",
+    "Europe/Vatican",
+    "America/St_Vincent",
+    "America/Caracas",
+    "America/Tortola",
+    "America/St_Thomas",
+    "Asia/Ho_Chi_Minh",
+    "Pacific/Efate",
+    "Pacific/Wallis",
+    "Pacific/Apia",
+    "Asia/Aden",
+    "Indian/Mayotte",
+    "Africa/Johannesburg",
+    "Africa/Lusaka",
+    "Africa/Harare"
+};
+
+static const int numOlsonTimezones = sizeof olsonTimezones / sizeof *olsonTimezones;
+
diff --git a/utils/zonetabconversion.py b/utils/zonetabconversion.py
new file mode 100755
index 0000000..adb78fd
--- /dev/null
+++ b/utils/zonetabconversion.py
@@ -0,0 +1,29 @@
+#!/bin/python2.7
+
+tztable = open("tztable.h", "w")
+tztable.write("//This file was generated by the zonetabconversion.py script\n");
+tztable.write("static const char* olsonTimezones[] = {\n");
+
+zonefile = open("/usr/share/zoneinfo/zone.tab", "r")
+first = True
+for line in zonefile:
+    # print line
+    if line.startswith('#'):
+        # print "continue"
+        continue
+    else:
+        tz = line.split(None)[2]
+        print tz
+        if first:
+            first = False
+            tztable.write("    \"")
+        else:
+            tztable.write(",\n    \"")
+        tztable.write(tz)
+        tztable.write("\"")
+        
+tztable.write("\n};\n")
+tztable.write("\n")
+tztable.write("static const int numOlsonTimezones = sizeof olsonTimezones / sizeof *olsonTimezones;\n")
+tztable.write("\n")
+        


commit 5a4ea1decec25183a5e2cdd0a17ddf7f241b8d3b
Author: Christian Mollekopf <mollekopf at kolabsys.com>
Date:   Wed Sep 18 08:43:58 2013 +0200

    Framework for additional validation of kolab objects.
    
    This allows to validate kolab objects beyond what the schema covers. (Such as valid timezones).

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3527408..732a0a5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -7,6 +7,7 @@ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wp,-D_FORTIFY_SOURCE=2 -O2" ) #a
 # Library with serialization/deserialization code and kolab-containers
 add_library(kolabxml SHARED
     kolabformat.cpp
+    objectvalidation.cpp
     containers/kolabcontainers.cpp
     containers/kolabnote.cpp
     containers/kolabevent.cpp
diff --git a/src/kolabconversions.h b/src/kolabconversions.h
index 3fffa1b..f4ae321 100644
--- a/src/kolabconversions.h
+++ b/src/kolabconversions.h
@@ -140,7 +140,6 @@ std::string serializeObject(const T &, const std::string prod = std::string());
 template <>
 std::string serializeObject <Kolab::Configuration> (const Kolab::Configuration &note, const std::string prod)
 {
-    clearErrors();
     try {
         const std::string &uid = getUID(note.uid());
         setCreatedUid(uid);
@@ -195,7 +194,6 @@ std::string serializeObject <Kolab::Configuration> (const Kolab::Configuration &
 template <>
 std::string serializeObject <Kolab::Note> (const Kolab::Note &note, const std::string prod)
 {
-    clearErrors();
     try {
         const std::string &uid = getUID(note.uid());
         setCreatedUid(uid);
@@ -273,7 +271,6 @@ std::string serializeObject <Kolab::Note> (const Kolab::Note &note, const std::s
 template <>
 std::string serializeObject <Kolab::File> (const Kolab::File &file, const std::string prod)
 {
-    clearErrors();
     try {
         const std::string &uid = getUID(file.uid());
         setCreatedUid(uid);
@@ -349,7 +346,6 @@ boost::shared_ptr<T> deserializeObject(const std::string& s, bool isUrl);
 template <>
 boost::shared_ptr<Kolab::Note> deserializeObject <Kolab::Note> (const std::string& s, bool isUrl)
 {
-    clearErrors();
     try {
         std::auto_ptr<KolabXSD::Note> note;
         if (isUrl) {
@@ -442,7 +438,6 @@ boost::shared_ptr<Kolab::Note> deserializeObject <Kolab::Note> (const std::strin
 template <>
 boost::shared_ptr<Kolab::Configuration> deserializeObject <Kolab::Configuration> (const std::string& s, bool isUrl)
 {
-    clearErrors();
     try {
         std::auto_ptr<KolabXSD::Configuration> configuration;
         if (isUrl) {
@@ -506,7 +501,6 @@ boost::shared_ptr<Kolab::Configuration> deserializeObject <Kolab::Configuration>
 template <>
 boost::shared_ptr<Kolab::File> deserializeObject <Kolab::File> (const std::string& s, bool isUrl)
 {
-    clearErrors();
     try {
         std::auto_ptr<KolabXSD::File> file;
         if (isUrl) {
diff --git a/src/kolabformat.cpp b/src/kolabformat.cpp
index 90c7ead..bb0a418 100644
--- a/src/kolabformat.cpp
+++ b/src/kolabformat.cpp
@@ -23,6 +23,7 @@
 #include "xcardconversions.h"
 #include "utils.h"
 #include "kolabconversions.h"
+#include "objectvalidation.h"
 
 namespace Kolab {
     
@@ -73,6 +74,7 @@ void overrideTimestamp(const cDateTime& dt)
 
 Kolab::Event readEvent(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     Kolab::XCAL::IncidenceTrait <Kolab::Event >::IncidencePtr ptr = XCAL::deserializeIncidence< XCAL::IncidenceTrait<Kolab::Event> >(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Event();
@@ -82,11 +84,14 @@ Kolab::Event readEvent(const std::string& s, bool isUrl)
 
 std::string writeEvent(const Kolab::Event &event, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(event);
     return XCAL::serializeIncidence< XCAL::IncidenceTrait<Kolab::Event> >(event, productId);
 }
 
 Kolab::Todo readTodo(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     XCAL::IncidenceTrait<Kolab::Todo>::IncidencePtr ptr = XCAL::deserializeIncidence< XCAL::IncidenceTrait<Kolab::Todo> >(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Todo();
@@ -96,11 +101,14 @@ Kolab::Todo readTodo(const std::string& s, bool isUrl)
 
 std::string writeTodo(const Kolab::Todo &event, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(event);
     return XCAL::serializeIncidence< XCAL::IncidenceTrait<Kolab::Todo> >(event, productId);
 }
 
 Journal readJournal(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     XCAL::IncidenceTrait<Kolab::Journal>::IncidencePtr ptr = XCAL::deserializeIncidence<XCAL::IncidenceTrait<Kolab::Journal> >(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Journal();
@@ -110,11 +118,14 @@ Journal readJournal(const std::string& s, bool isUrl)
 
 std::string writeJournal(const Kolab::Journal &j, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(j);
     return XCAL::serializeIncidence<XCAL::IncidenceTrait<Kolab::Journal> >(j, productId);
 }
 
 Kolab::Freebusy readFreebusy(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     XCAL::IncidenceTrait<Kolab::Freebusy>::IncidencePtr ptr = XCAL::deserializeIncidence<XCAL::IncidenceTrait<Kolab::Freebusy> >(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Freebusy();
@@ -124,11 +135,14 @@ Kolab::Freebusy readFreebusy(const std::string& s, bool isUrl)
 
 std::string writeFreebusy(const Freebusy &f, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(f);
     return XCAL::serializeFreebusy<XCAL::IncidenceTrait<Kolab::Freebusy> >(f, productId);
 }
 
 Kolab::Contact readContact(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     boost::shared_ptr <Kolab::Contact > ptr = XCARD::deserializeCard<Kolab::Contact>(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Contact();
@@ -138,11 +152,14 @@ Kolab::Contact readContact(const std::string& s, bool isUrl)
 
 std::string writeContact(const Contact &contact, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(contact);
     return XCARD::serializeCard(contact, productId);
 }
 
 DistList readDistlist(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     boost::shared_ptr <Kolab::DistList> ptr = XCARD::deserializeCard<Kolab::DistList>(s, isUrl);
     if (!ptr.get()) {
         return Kolab::DistList();
@@ -152,11 +169,14 @@ DistList readDistlist(const std::string& s, bool isUrl)
 
 std::string writeDistlist(const DistList &list, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(list);
     return XCARD::serializeCard(list, productId);
 }
 
 Note readNote(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     boost::shared_ptr <Kolab::Note> ptr = Kolab::KolabObjects::deserializeObject<Kolab::Note>(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Note();
@@ -166,11 +186,14 @@ Note readNote(const std::string& s, bool isUrl)
 
 std::string writeNote(const Note &note, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(note);
     return Kolab::KolabObjects::serializeObject<Kolab::Note>(note, productId);
 }
 
 File readFile(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     boost::shared_ptr <Kolab::File> ptr = Kolab::KolabObjects::deserializeObject<Kolab::File>(s, isUrl);
     if (!ptr.get()) {
         return Kolab::File();
@@ -180,11 +203,14 @@ File readFile(const std::string& s, bool isUrl)
 
 std::string writeFile(const File &file, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(file);
     return Kolab::KolabObjects::serializeObject<Kolab::File>(file, productId);
 }
 
 Configuration readConfiguration(const std::string& s, bool isUrl)
 {
+    Utils::clearErrors();
     boost::shared_ptr <Kolab::Configuration> ptr = Kolab::KolabObjects::deserializeObject<Kolab::Configuration>(s, isUrl);
     if (!ptr.get()) {
         return Kolab::Configuration();
@@ -194,6 +220,8 @@ Configuration readConfiguration(const std::string& s, bool isUrl)
 
 std::string writeConfiguration(const Configuration &config, const std::string& productId)
 {
+    Utils::clearErrors();
+    validate(config);
     return Kolab::KolabObjects::serializeObject< Kolab::Configuration >(config, productId);
 }
 
diff --git a/src/objectvalidation.cpp b/src/objectvalidation.cpp
new file mode 100644
index 0000000..04cd0b6
--- /dev/null
+++ b/src/objectvalidation.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2013  Christian Mollekopf <mollekopf at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "objectvalidation.h"
+
+#include "kolabevent.h"
+#include "kolabtodo.h"
+#include "kolabjournal.h"
+#include "kolabcontact.h"
+#include "kolabconfiguration.h"
+#include "kolabfile.h"
+#include "utils.h"
+
+namespace Kolab {
+
+bool isValid(const cDateTime &datetime)
+{
+    if (!datetime.isValid()) {
+        return true;
+    }
+    const std::string tz = datetime.timezone();
+    if (datetime.isUTC() && !tz.empty()) {
+        Utils::logMessage("A UTC datetime may not have a timezone", "", 0, Error);
+        return false;
+    }
+    if (tz == "Z") {
+        Utils::logMessage("Z is not a valid timezone. Set to UTC instead", "", 0, Error);
+        return false;
+    }
+    return true;
+}
+
+#define ASSERT(arg) \
+    do {\
+        if ( !(arg) ) { \
+            Utils::logMessage(#arg " is false", __FILE__, __LINE__, Error); \
+        } \
+    } while(0)
+
+#define ASSERTEQUAL(arg1, arg2) \
+    do {\
+        if ( (arg1) != (arg2) ) { \
+            Utils::logMessage(#arg1 " != " #arg2, __FILE__, __LINE__, Error); \
+        } \
+    } while(0)
+
+#define ASSERTEXISTING(arg) \
+    do {\
+        if ( !(arg).isValid() ) { \
+            Utils::logMessage(#arg " is not set", __FILE__, __LINE__, Error); \
+        } \
+    } while(0)
+
+#define ASSERTVALID(arg) \
+    do {\
+        if ( (arg).isValid() && !isValid((arg)) ) { \
+            Utils::logMessage(#arg " is not valid", __FILE__, __LINE__, Error); \
+        } \
+    } while(0)
+
+void validate(const Event &event)
+{
+    ASSERTEXISTING(event.start());
+    ASSERTVALID(event.start());
+    ASSERTVALID(event.end());
+    if (event.end().isValid()) {
+        ASSERTEQUAL(event.start().timezone(), event.end().timezone());
+    }
+}
+
+void validate(const Todo& todo)
+{
+    ASSERTVALID(todo.start());
+    ASSERTVALID(todo.due());
+    ASSERTEQUAL(todo.start().timezone(), todo.due().timezone());
+}
+
+void validate(const Journal& journal)
+{
+
+}
+
+void validate(const Contact& contact)
+{
+
+}
+
+void validate(const DistList& distlist)
+{
+
+}
+
+void validate(const Freebusy& freebusy)
+{
+
+}
+
+void validate(const Note& note)
+{
+
+}
+
+void validate(const Configuration& configuration)
+{
+
+}
+
+void validate(const File& file)
+{
+
+}
+
+}
+
diff --git a/src/objectvalidation.h b/src/objectvalidation.h
new file mode 100644
index 0000000..61e430a
--- /dev/null
+++ b/src/objectvalidation.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013  Christian Mollekopf <mollekopf at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef OBJECTVALIDATION_H
+#define OBJECTVALIDATION_H
+
+namespace Kolab {
+
+class Event;
+class Todo;
+class Journal;
+class Freebusy;
+class Contact;
+class DistList;
+class Note;
+class Configuration;
+class File;
+
+/**
+ * The validations in here are supposed to validate kolab objects beyond what the xml schema covers.
+ *
+ * A typical usecase is to check if event start/end date have the same timezone, or if all used timezones are valid.
+ */
+
+void validate(const Kolab::Event &event);
+void validate(const Kolab::Todo &todo);
+void validate(const Kolab::Journal &journal);
+void validate(const Kolab::Freebusy &freebusy);
+void validate(const Kolab::Contact &contact);
+void validate(const Kolab::DistList &distlist);
+void validate(const Kolab::Note &note);
+void validate(const Kolab::Configuration &configuration);
+void validate(const Kolab::File &file);
+
+}
+
+#endif
+
diff --git a/src/xcalconversions.h b/src/xcalconversions.h
index 6dfbc9c..a0489a8 100644
--- a/src/xcalconversions.h
+++ b/src/xcalconversions.h
@@ -1466,10 +1466,6 @@ template < > struct IncidenceTrait <Kolab::Event>
         getIncidenceProperties<icalendar_2_0::KolabEvent::properties_type>(prop, event);
         getTodoEventProperties<icalendar_2_0::KolabEvent::properties_type>(prop, event);
 
-        if (!event.start().isValid()) {
-            ERROR("Start date is missing, but is mandatory for events");
-        }
-
         if (event.end().isValid()) {
             prop.dtend(fromDate<icalendar_2_0::KolabEvent::properties_type::dtend_type>(event.end()));
         } else if (event.duration().isValid()) {
@@ -1860,8 +1856,6 @@ std::string serializeIncidence(const typename T::IncidenceType &incidence, const
     
     using namespace icalendar_2_0;
     typedef typename T::KolabType KolabType;
-    
-    clearErrors();
 
     try {
 
@@ -1923,8 +1917,7 @@ typename T::IncidencePtr deserializeIncidence(const std::string& s, bool isUrl)
     typedef typename T::IncidencePtr IncidencePtr;
     typedef typename T::IncidenceType IncidenceType;
     typedef typename T::KolabType KolabType;
-    
-    clearErrors();
+
     try {
         std::auto_ptr<icalendar_2_0::IcalendarType> icalendar;
         if (isUrl) {
@@ -1978,8 +1971,6 @@ std::string serializeFreebusy(const Kolab::Freebusy &incidence, const std::strin
     using namespace icalendar_2_0;
     typedef typename T::KolabType KolabType;
 
-    clearErrors();
-
     try {
 
         typename KolabType::properties_type::uid_type uid( getUID(incidence.uid()));
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 94c1142..47f811a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -29,6 +29,11 @@ if (QT4_FOUND)
     add_executable(parsingtest parsingtest.cpp ${CMAKE_CURRENT_BINARY_DIR}/${CONVERSIONTEST_MOC})
     target_link_libraries(parsingtest ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} kolabxml ${XERCES_C})
     add_test(parsingtest ${CMAKE_CURRENT_BINARY_DIR}/parsingtest)
+
+    QT4_AUTOMOC(validationtest.cpp)
+    add_executable(validationtest validationtest.cpp ${CMAKE_CURRENT_BINARY_DIR}/${CONVERSIONTEST_MOC})
+    target_link_libraries(validationtest ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} kolabxml ${XERCES_C})
+    add_test(validationtest ${CMAKE_CURRENT_BINARY_DIR}/validationtest)
 else()
     message(WARNING "Could not build tests because qt is missing")
 endif()
diff --git a/tests/validationtest.cpp b/tests/validationtest.cpp
new file mode 100644
index 0000000..5a1aa58
--- /dev/null
+++ b/tests/validationtest.cpp
@@ -0,0 +1,73 @@
+/*
+    Copyright (C) 2013 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "validationtest.h"
+
+#include <QTest>
+
+#include "src/kolabformat.h"
+
+using namespace Kolab;
+
+void ValidationTest::testErrorOnEmptyEvent()
+{
+    Event event;
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::Error);
+}
+
+void ValidationTest::testNoErrorOnValidEvent()
+{
+    Event event;
+    event.setStart(cDateTime(2013,1,1,1,1,1));
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::NoError);
+}
+
+void ValidationTest::testDifferentTimezones()
+{
+    Event event;
+    event.setStart(cDateTime("Europe/Zurich",2013,1,1,1,1,1));
+    event.setEnd(cDateTime("Europe/London",2013,1,1,1,1,1));
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::Error);
+}
+
+void ValidationTest::testUTCwithTimezone()
+{
+    Event event;
+    cDateTime dt("Europe/Zurich",2013,1,1,1,1,1);
+    dt.setUTC(true);
+    event.setStart(dt);
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::Error);
+}
+
+void ValidationTest::testTimezoneZ()
+{
+    Event event;
+    event.setStart(cDateTime("Z",2013,1,1,1,1,1));
+    writeEvent(event);
+    QCOMPARE(Kolab::error(), Kolab::Error);
+}
+
+
+
+QTEST_MAIN( ValidationTest )
+
+#include "validationtest.moc"
diff --git a/tests/validationtest.h b/tests/validationtest.h
new file mode 100644
index 0000000..3a2c5e1
--- /dev/null
+++ b/tests/validationtest.h
@@ -0,0 +1,34 @@
+/*
+    Copyright (C) 2013 Christian Mollekopf <mollekopf at kolabsys.com>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#ifndef VALIDATIONTEST_H
+#define VALIDATIONTEST_H
+
+
+#include <QObject>
+
+class ValidationTest: public QObject {
+    Q_OBJECT
+private slots:
+    void testErrorOnEmptyEvent();
+    void testNoErrorOnValidEvent();
+    void testDifferentTimezones();
+    void testUTCwithTimezone();
+    void testTimezoneZ();
+};
+
+#endif




More information about the commits mailing list