# HG changeset patch # User Carl Byington # Date 1242182089 25200 # Node ID 7c60d6d1c68161480687f6e3caf2129e43b99488 # Parent 07ceebd115ced3ad93587a6a9e06e13154ca7647 decode more recurrence mapi elements diff -r 07ceebd115ce -r 7c60d6d1c681 ChangeLog --- a/ChangeLog Wed May 06 10:37:46 2009 -0700 +++ b/ChangeLog Tue May 12 19:34:49 2009 -0700 @@ -5,6 +5,10 @@ * fix pst_attach_to_mem so the caller does not need to initialize the buffer pointer. * remove readpst -C switch, obsolete debugging code. + * update version to 4:0:0 since we made many changes to the interface. + * removed contact->access_method since we don't have a mapi element for it. + * changed pst_attach_to_mem to return pst_binary structure. + * decode more recurrence mapi elements. LibPST 0.6.37 (2009-04-17) =============================== diff -r 07ceebd115ce -r 7c60d6d1c681 TODO --- a/TODO Wed May 06 10:37:46 2009 -0700 +++ b/TODO Tue May 12 19:34:49 2009 -0700 @@ -4,9 +4,10 @@ pst2diii needs header and mime type updates from readpst. -At the next soname bump (to libpst.so.3) we should - remove contact->access_method (or find the proper mapi element id for it) - remove pst_x_attrib_ll->type (unused) - reorder appointment fields to collect related fields - remove readpstlog, and produce directly ascii debug log files - change pst_attach_to_mem to return pst_binary +At the next soname bump (to libpst.so.5) we should + remove readpstlog, and produce ascii debug log files + try fork() with child limit to get parallel readpst + move some of readpst into the shared library, in particular write_normal_email() + add helper function item_actual_type (mail, contact, etc) + change gmtime() and ctime() to _r versions for thread safe operation + diff -r 07ceebd115ce -r 7c60d6d1c681 configure.in --- a/configure.in Wed May 06 10:37:46 2009 -0700 +++ b/configure.in Tue May 12 19:34:49 2009 -0700 @@ -19,9 +19,9 @@ # 6. libtool will build libpst.so.x.y.z where the SONAME is libpst.so.x # and x=current-age, y=age, z=revision -libpst_version_info='3:0:1' +libpst_version_info='4:0:0' AC_SUBST(LIBPST_VERSION_INFO, [$libpst_version_info]) -libpst_so_major='2' +libpst_so_major='4' AC_SUBST(LIBPST_SO_MAJOR, [$libpst_so_major]) # libpst @@ -31,15 +31,6 @@ # 0.6.38 libpst.so.2 libpst.so.2.1.0 -# check for boost -AX_PYTHON -AX_BOOST_PYTHON -if test "$ax_python_bin" = "no"; then - AC_MSG_ERROR(python binary not found) -fi - -AC_SUBST(PYTHON_VERSION, [$ax_python_bin]) - # Check for win32 AC_MSG_CHECKING([for Win32]) @@ -155,6 +146,7 @@ # Checks for library functions. AC_FUNC_FSEEKO +AC_FUNC_STAT AC_FUNC_LSTAT AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK if test "$cross_compiling" != "yes"; then @@ -262,6 +254,42 @@ fi +# The following lines adds the --enable-python option to configure: +# +# Give the user the choice to enter one of these: +# --enable-python +# --enable-python=yes +# --enable-python=no +# +AC_MSG_CHECKING([whether to build the libpst python interface]) +AC_ARG_ENABLE([python], + AC_HELP_STRING([--enable-python], [build libpst python interface]), + [ + case "${enableval}" in + yes) ;; + no) ;; + *) AC_MSG_ERROR(bad value ${enableval} for --python) ;; + esac + ], + [ + enable_python=no + ]) +AC_MSG_RESULT([$enable_python]) +AM_CONDITIONAL(PYTHON_INTERFACE, [test "$enable_python" = "yes"]) +if test "$enable_python" = "yes"; then + enable_shared="yes" + # check for boost + AX_PYTHON + AX_BOOST_PYTHON + if test "$ax_python_bin" = "no"; then + AC_MSG_ERROR(python binary not found) + fi + + AC_SUBST(PYTHON_VERSION, [$ax_python_bin]) +fi + + + AC_OUTPUT( \ Makefile \ debian/Makefile \ diff -r 07ceebd115ce -r 7c60d6d1c681 libpst.spec.in --- a/libpst.spec.in Wed May 06 10:37:46 2009 -0700 +++ b/libpst.spec.in Tue May 12 19:34:49 2009 -0700 @@ -83,7 +83,7 @@ %build -%configure --enable-libpst-shared +%configure --enable-libpst-shared --enable-python make %{?_smp_mflags} @@ -149,7 +149,8 @@ %changelog * Mon Apr 20 2009 Carl Byington - 0.6.38-1 -- add python interface to the shared library +- add python interface to the shared library. +- bump soname to version 4. * Fri Apr 17 2009 Carl Byington - 0.6.37-1 - add pst_attach_to_mem() back into the shared library interface. diff -r 07ceebd115ce -r 7c60d6d1c681 python/Makefile.am --- a/python/Makefile.am Wed May 06 10:37:46 2009 -0700 +++ b/python/Makefile.am Tue May 12 19:34:49 2009 -0700 @@ -1,3 +1,5 @@ +if PYTHON_INTERFACE + if PLATFORM_WIN32 NO_UNDEFINED = -no-undefined else @@ -12,3 +14,5 @@ # set the include path found by configure INCLUDES= -I$(srcdir)/.. -I$(srcdir)/../src $(all_includes) -I$(PYTHON_INCLUDE_DIR) + +endif diff -r 07ceebd115ce -r 7c60d6d1c681 python/python-libpst.cpp --- a/python/python-libpst.cpp Wed May 06 10:37:46 2009 -0700 +++ b/python/python-libpst.cpp Tue May 12 19:34:49 2009 -0700 @@ -27,25 +27,27 @@ class pst { public: - pst(const string filename); - virtual ~pst(); - pst_desc_tree* pst_getTopOfFolders(); - ppst_binary pst_attach_to_mem(pst_item_attach *attach); - size_t pst_attach_to_file(pst_item_attach *attach, FILE* fp); - size_t pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp); - pst_desc_tree* pst_getNextDptr(pst_desc_tree* d); - pst_item* pst_parse_item(pst_desc_tree *d_ptr, pst_id2_tree *m_head); - void pst_freeItem(pst_item *item); - pst_index_ll* pst_getID(uint64_t i_id); - int pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type); - size_t pst_ff_getIDblock_dec(uint64_t i_id, char **buf); - size_t pst_ff_getIDblock(uint64_t i_id, char** buf); - string pst_rfc2426_escape(char *str); - string pst_rfc2425_datetime_format(const FILETIME *ft); - string pst_rfc2445_datetime_format(const FILETIME *ft); - string pst_default_charset(pst_item *item); - void pst_convert_utf8_null(pst_item *item, pst_string *str); - void pst_convert_utf8(pst_item *item, pst_string *str); + pst(const string filename); + virtual ~pst(); + pst_desc_tree* pst_getTopOfFolders(); + ppst_binary pst_attach_to_mem(pst_item_attach *attach); + size_t pst_attach_to_file(pst_item_attach *attach, FILE* fp); + size_t pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp); + pst_desc_tree* pst_getNextDptr(pst_desc_tree* d); + pst_item* pst_parse_item(pst_desc_tree *d_ptr, pst_id2_tree *m_head); + void pst_freeItem(pst_item *item); + pst_index_ll* pst_getID(uint64_t i_id); + int pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type); + size_t pst_ff_getIDblock_dec(uint64_t i_id, char **buf); + size_t pst_ff_getIDblock(uint64_t i_id, char** buf); + string pst_rfc2426_escape(char *str); + string pst_rfc2425_datetime_format(const FILETIME *ft); + string pst_rfc2445_datetime_format(const FILETIME *ft); + string pst_default_charset(pst_item *item); + void pst_convert_utf8_null(pst_item *item, pst_string *str); + void pst_convert_utf8(pst_item *item, pst_string *str); + pst_recurrence* pst_convert_recurrence(pst_item_appointment *appt); + void pst_free_recurrence(pst_recurrence* r); /** helper for python access to fopen() */ FILE* ppst_open_file(string filename, string mode); @@ -79,76 +81,88 @@ if (is_open) ::pst_close(&pf); } -pst_desc_tree* pst::pst_getTopOfFolders() { +pst_desc_tree* pst::pst_getTopOfFolders() { return topf; } -ppst_binary pst::pst_attach_to_mem(pst_item_attach *attach) { +ppst_binary pst::pst_attach_to_mem(pst_item_attach *attach) { + pst_binary r = ::pst_attach_to_mem(&pf, attach); ppst_binary rc; - rc.size = rc.size = ::pst_attach_to_mem(&pf, attach, &rc.data); + rc.size = r.size; + rc.data = r.data; return rc; } -size_t pst::pst_attach_to_file(pst_item_attach *attach, FILE* fp) { +size_t pst::pst_attach_to_file(pst_item_attach *attach, FILE* fp) { return ::pst_attach_to_file(&pf, attach, fp); } -size_t pst::pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp) { +size_t pst::pst_attach_to_file_base64(pst_item_attach *attach, FILE* fp) { return ::pst_attach_to_file_base64(&pf, attach, fp); } -pst_desc_tree* pst::pst_getNextDptr(pst_desc_tree* d) { +pst_desc_tree* pst::pst_getNextDptr(pst_desc_tree* d) { return ::pst_getNextDptr(d); } -pst_item* pst::pst_parse_item (pst_desc_tree *d_ptr, pst_id2_tree *m_head) { +pst_item* pst::pst_parse_item (pst_desc_tree *d_ptr, pst_id2_tree *m_head) { return ::pst_parse_item(&pf, d_ptr, m_head); } -void pst::pst_freeItem(pst_item *item) { +void pst::pst_freeItem(pst_item *item) { return ::pst_freeItem(item); } -pst_index_ll* pst::pst_getID(uint64_t i_id) { +pst_index_ll* pst::pst_getID(uint64_t i_id) { return ::pst_getID(&pf, i_id); } -int pst::pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type) { +int pst::pst_decrypt(uint64_t i_id, char *buf, size_t size, unsigned char type) { return ::pst_decrypt(i_id, buf, size, type); } -size_t pst::pst_ff_getIDblock_dec(uint64_t i_id, char **buf) { +size_t pst::pst_ff_getIDblock_dec(uint64_t i_id, char **buf) { return ::pst_ff_getIDblock_dec(&pf, i_id, buf); } -size_t pst::pst_ff_getIDblock(uint64_t i_id, char** buf) { +size_t pst::pst_ff_getIDblock(uint64_t i_id, char** buf) { return ::pst_ff_getIDblock(&pf, i_id, buf); } -string pst::pst_rfc2426_escape(char *str) { +string pst::pst_rfc2426_escape(char *str) { return ::pst_rfc2426_escape(str); } -string pst::pst_rfc2425_datetime_format(const FILETIME *ft) { +string pst::pst_rfc2425_datetime_format(const FILETIME *ft) { return ::pst_rfc2425_datetime_format((FILETIME *)ft); // cast away const is ok, since libpst did not modify it anyway, and the signature will change in more recent versions } -string pst::pst_rfc2445_datetime_format(const FILETIME *ft) { +string pst::pst_rfc2445_datetime_format(const FILETIME *ft) { return ::pst_rfc2445_datetime_format((FILETIME *)ft); // cast away const is ok, since libpst did not modify it anyway, and the signature will change in more recent versions } -string pst::pst_default_charset(pst_item *item) { +string pst::pst_default_charset(pst_item *item) { return ::pst_default_charset(item); } -void pst::pst_convert_utf8_null(pst_item *item, pst_string *str) { +void pst::pst_convert_utf8_null(pst_item *item, pst_string *str) { ::pst_convert_utf8_null(item, str); } -void pst::pst_convert_utf8(pst_item *item, pst_string *str) { +void pst::pst_convert_utf8(pst_item *item, pst_string *str) { ::pst_convert_utf8(item, str); } +pst_recurrence* pst::pst_convert_recurrence(pst_item_appointment *appt) +{ + return ::pst_convert_recurrence(appt); +} + +void pst::pst_free_recurrence(pst_recurrence* r) +{ + ::pst_free_recurrence(r); +} + FILE* pst::ppst_open_file(string filename, string mode) { return ::fopen(filename.c_str(), mode.c_str()); } @@ -189,6 +203,13 @@ } }; +struct make_python_pst_recurrence { + static PyObject* convert(pst_recurrence* const &s) { + if (s) return to_python_indirect()(s); + return NULL; + } +}; + struct make_python_pst_item_email { static PyObject* convert(pst_item_email* const &s) { if (s) return to_python_indirect()(s); @@ -231,6 +252,7 @@ to_python_converter(); to_python_converter(); to_python_converter(); + to_python_converter(); to_python_converter(); to_python_converter(); to_python_converter(); @@ -367,7 +389,6 @@ ; class_("pst_item_contact") - .def_readonly("access_method", &pst_item_contact::access_method) .def_readonly("account_name", &pst_item_contact::account_name) .def_readonly("address1", &pst_item_contact::address1) .def_readonly("address1a", &pst_item_contact::address1a) @@ -490,25 +511,42 @@ ; class_("pst_item_journal") + .add_property("start", make_getter(&pst_item_journal::start, return_value_policy())) .add_property("end", make_getter(&pst_item_journal::end, return_value_policy())) - .add_property("start", make_getter(&pst_item_journal::start, return_value_policy())) .def_readonly("type", &pst_item_journal::type) .def_readonly("description", &pst_item_journal::description) ; + class_("pst_recurrence") + .def_readonly("signature", &pst_recurrence::signature) + .def_readonly("type", &pst_recurrence::type) + .def_readonly("sub_type", &pst_recurrence::sub_type) + .def_readonly("parm1", &pst_recurrence::parm1) + .def_readonly("parm2", &pst_recurrence::parm2) + .def_readonly("parm3", &pst_recurrence::parm3) + .def_readonly("parm4", &pst_recurrence::parm4) + .def_readonly("parm5", &pst_recurrence::parm5) + .def_readonly("termination", &pst_recurrence::termination) + .def_readonly("interval", &pst_recurrence::interval) + .def_readonly("count", &pst_recurrence::count) + ; + class_("pst_item_appointment") + .add_property("start", make_getter(&pst_item_appointment::start, return_value_policy())) .add_property("end", make_getter(&pst_item_appointment::end, return_value_policy())) .def_readonly("location", &pst_item_appointment::location) + .def_readonly("alarm", &pst_item_appointment::alarm) .add_property("reminder", make_getter(&pst_item_appointment::reminder, return_value_policy())) .def_readonly("alarm_minutes", &pst_item_appointment::alarm_minutes) .def_readonly("alarm_filename", &pst_item_appointment::alarm_filename) - .add_property("start", make_getter(&pst_item_appointment::start, return_value_policy())) .def_readonly("timezonestring", &pst_item_appointment::timezonestring) .def_readonly("showas", &pst_item_appointment::showas) .def_readonly("label", &pst_item_appointment::label) .def_readonly("all_day", &pst_item_appointment::all_day) - .def_readonly("recurrence", &pst_item_appointment::recurrence) + .def_readonly("is_recurring", &pst_item_appointment::is_recurring) .def_readonly("recurrence_type", &pst_item_appointment::recurrence_type) + .def_readonly("recurrence_description", &pst_item_appointment::recurrence_description) + .def_readonly("recurrence_data", &pst_item_appointment::recurrence_data) .add_property("recurrence_start", make_getter(&pst_item_appointment::recurrence_start, return_value_policy())) .add_property("recurrence_end", make_getter(&pst_item_appointment::recurrence_end, return_value_policy())) ; diff -r 07ceebd115ce -r 7c60d6d1c681 regression/regression-tests.bash --- a/regression/regression-tests.bash Wed May 06 10:37:46 2009 -0700 +++ b/regression/regression-tests.bash Tue May 12 19:34:49 2009 -0700 @@ -99,27 +99,31 @@ doldif 18 test-mac.pst #doldif 19 harris.pst doldif 20 spam.pst + dolif 21 rendgen.pst else - dopst 1 ams.pst - dopst 2 sample_64.pst - dopst 3 test.pst - dopst 4 big_mail.pst - dopst 5 mbmg.archive.pst - dopst 6 Single2003-read.pst - dopst 7 Single2003-unread.pst - dopst 8 ol2k3high.pst - dopst 9 ol97high.pst - dopst 10 returned_message.pst - dopst 11 flow.pst - dopst 12 test-html.pst - dopst 13 test-text.pst - dopst 14 joe.romanowski.pst - dopst 15 hourig1.pst - #dopst 16 hourig2.pst - #dopst 17 hourig3.pst - dopst 18 test-mac.pst - #dopst 19 harris.pst - dopst 20 spam.pst + #dopst 1 ams.pst + #dopst 2 sample_64.pst + #dopst 3 test.pst + #dopst 4 big_mail.pst + #dopst 5 mbmg.archive.pst + #dopst 6 Single2003-read.pst + #dopst 7 Single2003-unread.pst + #dopst 8 ol2k3high.pst + #dopst 9 ol97high.pst + #dopst 10 returned_message.pst + #dopst 11 flow.pst + #dopst 12 test-html.pst + #dopst 13 test-text.pst + #dopst 14 joe.romanowski.pst + #dopst 15 hourig1.pst + ##dopst 16 hourig2.pst + ##dopst 17 hourig3.pst + #dopst 18 test-mac.pst + ##dopst 19 harris.pst + #dopst 20 spam.pst + dopst 21 rendgen.pst # single email appointment + dopst 22 rendgen2.pst # email appointment with no termination date + dopst 23 rendgen3.pst # mime signed email fi grep 'lost:' *err | grep -v 'lost: 0 ' diff -r 07ceebd115ce -r 7c60d6d1c681 src/libpst.c --- a/src/libpst.c Wed May 06 10:37:46 2009 -0700 +++ b/src/libpst.c Tue May 12 19:34:49 2009 -0700 @@ -518,28 +518,27 @@ } -size_t pst_attach_to_mem(pst_file *pf, pst_item_attach *attach, char **b) { +pst_binary pst_attach_to_mem(pst_file *pf, pst_item_attach *attach) { pst_index_ll *ptr; - pst_holder h = {b, NULL, 0}; - size_t size = 0; - *b = NULL; + pst_binary rc; + pst_holder h = {&rc.data, NULL, 0}; + rc.size = 0; + rc.data = NULL; DEBUG_ENT("pst_attach_to_mem"); if ((!attach->data.data) && (attach->i_id != (uint64_t)-1)) { ptr = pst_getID(pf, attach->i_id); if (ptr) { - size = pst_ff_getID2data(pf, ptr, &h); + rc.size = pst_ff_getID2data(pf, ptr, &h); } else { DEBUG_WARN(("Couldn't find ID pointer. Cannot handle attachment\n")); - *b = NULL; } } else { - size = attach->data.size; - *b = attach->data.data; + rc = attach->data; attach->data.data = NULL; // prevent pst_free_item() from trying to free this attach->data.size = 0; // since we have given that buffer to the caller } DEBUG_RET(); - return size; + return rc; } @@ -2063,6 +2062,12 @@ LIST_COPY_BIN(targ); \ DEBUG_EMAIL((label"\n")); \ } +#define LIST_COPY_APPT_BIN(label, targ) { \ + MALLOC_APPOINTMENT(item); \ + LIST_COPY_BIN(targ); \ + DEBUG_EMAIL((label"\n")); \ + DEBUG_EMAIL_HEXPRINT(targ.data, targ.size); \ +} #define NULL_CHECK(x) { if (!x) { DEBUG_WARN(("NULL_CHECK: Null Found\n")); break;} } @@ -2163,11 +2168,8 @@ item->type = PST_TYPE_JOURNAL; else if (pst_strincmp("IPM.Appointment", item->ascii_type, 15) == 0) item->type = PST_TYPE_APPOINTMENT; - //else if (pst_strincmp("IPM.Schedule.Meeting", item->ascii_type, 20) == 0) - // item->type = PST_TYPE_APPOINTMENT; - // these seem to be appointments, but they are inside the email folder, - // and unless we are in separate mode, we would dump an appointment - // into the middle of a mailbox file. + else if (pst_strincmp("IPM.Schedule.Meeting", item->ascii_type, 20) == 0) + item->type = PST_TYPE_SCHEDULE; // meeting requests and responses transported over email else if (pst_strincmp("IPM.StickyNote", item->ascii_type, 14) == 0) item->type = PST_TYPE_STICKYNOTE; else if (pst_strincmp("IPM.Task", item->ascii_type, 8) == 0) @@ -2926,8 +2928,14 @@ case 0x8215: // PR_OUTLOOK_EVENT_ALL_DAY LIST_COPY_APPT_BOOL("All day flag", item->appointment->all_day); break; + case 0x8216: // PR_OUTLOOK_EVENT_RECURRENCE_DATA + LIST_COPY_APPT_BIN("Appointment recurrence data", item->appointment->recurrence_data); + break; + case 0x8223: // PR_OUTLOOK_EVENT_IS_RECURRING + LIST_COPY_APPT_BOOL("Is recurring", item->appointment->is_recurring); + break; case 0x8231: // Recurrence type - LIST_COPY_APPT_ENUM("Appointment reccurence", item->appointment->recurrence_type, 0, 5, + LIST_COPY_APPT_ENUM("Appointment recurrence type ", item->appointment->recurrence_type, 0, 5, "None", "Daily", "Weekly", @@ -2935,7 +2943,7 @@ "Yearly"); break; case 0x8232: // Recurrence description - LIST_COPY_APPT_STR("Appointment recurrence description", item->appointment->recurrence); + LIST_COPY_APPT_STR("Appointment recurrence description", item->appointment->recurrence_description); break; case 0x8234: // TimeZone as String LIST_COPY_APPT_STR("TimeZone of times", item->appointment->timezonestring); @@ -3333,7 +3341,6 @@ free(item->message_store); } if (item->contact) { - SAFE_FREE_STR(item->contact->access_method); SAFE_FREE_STR(item->contact->account_name); SAFE_FREE_STR(item->contact->address1); SAFE_FREE_STR(item->contact->address1a); @@ -3443,19 +3450,20 @@ item->extra_fields = et; } if (item->journal) { + SAFE_FREE(item->journal->start); SAFE_FREE(item->journal->end); - SAFE_FREE(item->journal->start); SAFE_FREE_STR(item->journal->type); free(item->journal); } if (item->appointment) { + SAFE_FREE(item->appointment->start); + SAFE_FREE(item->appointment->end); SAFE_FREE_STR(item->appointment->location); SAFE_FREE(item->appointment->reminder); SAFE_FREE_STR(item->appointment->alarm_filename); - SAFE_FREE(item->appointment->start); - SAFE_FREE(item->appointment->end); SAFE_FREE_STR(item->appointment->timezonestring); - SAFE_FREE_STR(item->appointment->recurrence); + SAFE_FREE_STR(item->appointment->recurrence_description); + SAFE_FREE_BIN(item->appointment->recurrence_data); SAFE_FREE(item->appointment->recurrence_start); SAFE_FREE(item->appointment->recurrence_end); free(item->appointment); @@ -4165,7 +4173,7 @@ char *pst_rfc2425_datetime_format(const FILETIME *ft) { static char buffer[30]; - struct tm *stm = NULL; + struct tm* stm = NULL; DEBUG_ENT("rfc2425_datetime_format"); stm = pst_fileTimeToStructTM(ft); if (strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", stm)==0) { @@ -4178,7 +4186,7 @@ char *pst_rfc2445_datetime_format(const FILETIME *ft) { static char buffer[30]; - struct tm *stm = NULL; + struct tm* stm = NULL; DEBUG_ENT("rfc2445_datetime_format"); stm = pst_fileTimeToStructTM(ft); if (strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", stm)==0) { @@ -4189,6 +4197,20 @@ } +char *pst_rfc2445_datetime_format_now() { + static char buffer[30]; + struct tm stm; + time_t t = time(NULL); + DEBUG_ENT("rfc2445_datetime_format_now"); + gmtime_r(&t, &stm); + if (strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", &stm)==0) { + DEBUG_INFO(("Problem occured formatting date\n")); + } + DEBUG_RET(); + return buffer; +} + + /** Convert a code page integer into a string suitable for iconv() * * @param cp the code page integer used in the pst file @@ -4287,3 +4309,63 @@ free(newer); DEBUG_RET(); } + + +/** Decode raw recurrence data into a better structure. + * @param appt pointer to appointment structure + * @return pointer to decoded recurrence structure that must be free'd by the caller. + */ +pst_recurrence* pst_convert_recurrence(pst_item_appointment* appt) +{ + int m[4] = {3,4,4,5}; + pst_recurrence *r = pst_malloc(sizeof(pst_recurrence)); + memset(r, 0, sizeof(pst_recurrence)); + size_t s = appt->recurrence_data.size; + size_t i = 0; + char* p = appt->recurrence_data.data; + if (p) { + if (i+4 <= s) { r->signature = PST_LE_GET_UINT32(p+i); i += 4; } + if (i <= s) { r->type = PST_LE_GET_UINT8(p+i) - 0x0a; i += 2; } + if (i+4 <= s) { r->sub_type = PST_LE_GET_UINT32(p+i); i += 4; } + if (r->sub_type <= 3) { + int n = m[r->sub_type]; + int j = 0; + for (j=0; jparm1 + j) = PST_LE_GET_UINT32(p+i); i += 4; } + } + } + if (i <= s) { r->termination = PST_LE_GET_UINT8(p+i) - 0x21; i += 4; } + if (i+4 <= s) { r->count = PST_LE_GET_UINT32(p+i); i += 4; } + switch (r->type) { + case 0: // daily + r->interval = r->parm2 / (24 * 60); // was minutes between recurrences + if (r->sub_type) r->interval = 0; // !! don't handle sub-type 1 yet + break; + case 1: // weekly + r->interval = r->parm2; + break; + case 2: // monthly + r->interval = r->parm2; + // two flavors, every month on the Dth day, and every month on the Nth Tuesday + // those are not handled here. + break; + case 3: // yearly + r->interval = 0; + // two flavors, every year on the Dth day of the Mth month, and every year on the Nth Tuesday of the Mth month + // those are not handled here. + break; + default: + break; + } + } + return r; +} + + +/** Free a recurrence structure. + * @param r input pointer to be freed + */ +void pst_free_recurrence(pst_recurrence* r) +{ + if (r) free(r); +} diff -r 07ceebd115ce -r 7c60d6d1c681 src/libpst.h --- a/src/libpst.h Wed May 06 10:37:46 2009 -0700 +++ b/src/libpst.h Tue May 12 19:34:49 2009 -0700 @@ -24,6 +24,7 @@ #define PST_TYPE_NOTE 1 +#define PST_TYPE_SCHEDULE 2 #define PST_TYPE_APPOINTMENT 8 #define PST_TYPE_CONTACT 9 #define PST_TYPE_JOURNAL 10 @@ -32,6 +33,7 @@ #define PST_TYPE_OTHER 13 #define PST_TYPE_REPORT 14 + // defines types of possible encryption #define PST_NO_ENCRYPT 0 #define PST_COMP_ENCRYPT 1 @@ -63,7 +65,7 @@ #define PST_APP_LABEL_ANNIVERSARY 9 #define PST_APP_LABEL_PHONE_CALL 10 -// define type of reccuring event +// define type of recuring event #define PST_APP_RECUR_NONE 0 #define PST_APP_RECUR_DAILY 1 #define PST_APP_RECUR_WEEKLY 2 @@ -357,8 +359,6 @@ /** This contains the contact related mapi elements */ typedef struct pst_item_contact { - /** Unused - need to find the proper mapi element number for this */ - pst_string access_method; /** mapi element 0x3a00 PR_ACCOUNT */ pst_string account_name; /** mapi element 0x3003 PR_EMAIL_ADDRESS, or 0x8083 */ @@ -612,10 +612,10 @@ /** This contains the journal related mapi elements */ typedef struct pst_item_journal { + /** mapi element 0x8706 */ + FILETIME *start; /** mapi element 0x8708 */ FILETIME *end; - /** mapi element 0x8706 */ - FILETIME *start; /** mapi element 0x8700 */ pst_string type; /** mapi element 0x8712 */ @@ -623,9 +623,50 @@ } pst_item_journal; +/** This contains the recurrence data separated into fields. + http://www.geocities.com/cainrandom/dev/MAPIRecurrence.html +*/ +typedef struct pst_recurrence { + /** 0x30043004 */ + uint32_t signature; + /** @li 0 daily + * @li 1 weekly + * @li 2 monthly + * @li 3 yearly */ + uint32_t type; + /** implies number of recurrence parameters + * @li 0 has 3 parameters + * @li 1 has 4 parameters + * @li 2 has 4 parameters + * @li 3 has 5 parameters + */ + uint32_t sub_type; + /** must be contiguous, not an array to make python interface easier */ + uint32_t parm1; + uint32_t parm2; + uint32_t parm3; + uint32_t parm4; + uint32_t parm5; + /** type of termination of the recurrence + @li 0 terminates on a date + @li 1 terminates based on integer number of occurrences + @li 2 never terminates + */ + uint32_t termination; + /** recurrence interval in terms of the recurrence type */ + uint32_t interval; + /** number of occurrences, even if recurrence terminates based on date */ + uint32_t count; + // there is more data, including the termination date, + // but we can get that from other mapi elements. +} pst_recurrence; + + /** This contains the appointment related mapi elements */ typedef struct pst_item_appointment { + /** mapi element 0x820d PR_OUTLOOK_EVENT_START_DATE */ + FILETIME *start; /** mapi element 0x820e PR_OUTLOOK_EVENT_START_END */ FILETIME *end; /** mapi element 0x8208 PR_OUTLOOK_EVENT_LOCATION */ @@ -640,8 +681,6 @@ int32_t alarm_minutes; /** mapi element 0x851f */ pst_string alarm_filename; - /** mapi element 0x820d PR_OUTLOOK_EVENT_START_DATE */ - FILETIME *start; /** mapi element 0x8234 */ pst_string timezonestring; /** mapi element 0x8205 PR_OUTLOOK_EVENT_SHOW_TIME_AS @@ -667,8 +706,10 @@ * @li 1 true * @li 0 false */ int all_day; - /** mapi element 0x8232 recurrence description */ - pst_string recurrence; + /** mapi element 0x8223 PR_OUTLOOK_EVENT_IS_RECURRING + * @li 1 true + * @li 0 false */ + int is_recurring; /** mapi element 0x8231 * @li 0 none * @li 1 daily @@ -676,6 +717,10 @@ * @li 3 monthly * @li 4 yearly */ int32_t recurrence_type; + /** mapi element 0x8232 recurrence description */ + pst_string recurrence_description; + /** mapi element 0x8216 recurrence data */ + pst_binary recurrence_data; /** mapi element 0x8235 PR_OUTLOOK_EVENT_RECURRENCE_START */ FILETIME *recurrence_start; /** mapi element 0x8236 PR_OUTLOOK_EVENT_RECURRENCE_END */ @@ -705,6 +750,7 @@ pst_item_appointment *appointment; /** derived from mapi elements 0x001a PR_MESSAGE_CLASS or 0x3613 PR_CONTAINER_CLASS * @li 1 PST_TYPE_NOTE + * @li 2 PST_TYPE_SCHEDULE * @li 8 PST_TYPE_APPOINTMENT * @li 9 PST_TYPE_CONTACT * @li 10 PST_TYPE_JOURNAL @@ -773,8 +819,6 @@ * (PST_ATTRIB_HEADER). */ typedef struct pst_x_attrib_ll { - /** obsolete field, this is now unused */ - uint32_t type; /** @li 1 PST_MAP_ATTRIB map->int attribute @li 2 PST_MAP_HEADER map->string header */ @@ -878,11 +922,10 @@ /** Assemble the binary attachment into a single buffer. * @param pf pointer to the pst_file structure setup by pst_open(). * @param attach pointer to the attachment record - * @param b pointer to location to store the buffer pointer. The - * caller must free this buffer. - * @return size of the buffer, and return the buffer pointer in *b + * @return structure containing size of and pointer to the buffer. + * the caller must free this buffer. */ -size_t pst_attach_to_mem(pst_file *pf, pst_item_attach *attach, char **b); +pst_binary pst_attach_to_mem(pst_file *pf, pst_item_attach *attach); /** Write a binary attachment to a file. @@ -1000,28 +1043,48 @@ char * pst_rfc2445_datetime_format(const FILETIME *ft); +/** Convert the current time rfc2445 date/time format 19531015T231000Z + * @return time in rfc2445 format + */ +char * pst_rfc2445_datetime_format_now(); + + /** Get the default character set for this item. This is used to find * the charset for pst_string elements that are not already in utf8 encoding. - * @param item pointer to the mapi item of interest - * @return default character set as a string useable by iconv() + * @param item pointer to the mapi item of interest + * @return default character set as a string useable by iconv() */ const char* pst_default_charset(pst_item *item); /** Convert str to utf8 if possible; null strings are preserved. - * @param item pointer to the containing mapi item - * @param str pointer to the mapi string of interest + * @param item pointer to the containing mapi item + * @param str pointer to the mapi string of interest */ void pst_convert_utf8_null(pst_item *item, pst_string *str); /** Convert str to utf8 if possible; null strings are converted into empty strings. - * @param item pointer to the containing mapi item - * @param str pointer to the mapi string of interest + * @param item pointer to the containing mapi item + * @param str pointer to the mapi string of interest */ void pst_convert_utf8(pst_item *item, pst_string *str); +/** Decode raw recurrence data into a better structure. + * @param appt pointer to appointment structure + * @return pointer to decoded recurrence structure that must be free'd by the caller. + */ +pst_recurrence* pst_convert_recurrence(pst_item_appointment* appt); + + +/** Free a recurrence structure. + * @param r input pointer to be freed + */ +void pst_free_recurrence(pst_recurrence* r); + + + // switch from maximal packing back to default packing // undo the packing from the beginning of this file #ifdef _MSC_VER diff -r 07ceebd115ce -r 7c60d6d1c681 src/lspst.c --- a/src/lspst.c Wed May 06 10:37:46 2009 -0700 +++ b/src/lspst.c Tue May 12 19:34:49 2009 -0700 @@ -75,6 +75,7 @@ process(item, d_ptr->child); } else if (item->contact && (item->type == PST_TYPE_CONTACT)) { + if (!ff.type) ff.type = item->type; // Process Contact item if (ff.type != PST_TYPE_CONTACT) { DEBUG_MAIN(("main: I have a contact, but the folder isn't a contacts folder. Processing anyway\n")); @@ -84,9 +85,10 @@ printf("\t%s", pst_rfc2426_escape(item->contact->fullname.str)); printf("\n"); - } else if (item->email && (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) { + } else if (item->email && ((item->type == PST_TYPE_NOTE) || (item->type == PST_TYPE_SCHEDULE) || (item->type == PST_TYPE_REPORT))) { + if (!ff.type) ff.type = item->type; // Process Email item - if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_REPORT)) { + if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_SCHEDULE) && (ff.type != PST_TYPE_REPORT)) { DEBUG_MAIN(("main: I have an email, but the folder isn't an email folder. Processing anyway\n")); } printf("Email"); @@ -97,6 +99,7 @@ printf("\n"); } else if (item->journal && (item->type == PST_TYPE_JOURNAL)) { + if (!ff.type) ff.type = item->type; // Process Journal item if (ff.type != PST_TYPE_JOURNAL) { DEBUG_MAIN(("main: I have a journal entry, but folder isn't specified as a journal type. Processing...\n")); @@ -105,6 +108,7 @@ printf("Journal\t%s\n", pst_rfc2426_escape(item->subject.str)); } else if (item->appointment && (item->type == PST_TYPE_APPOINTMENT)) { + if (!ff.type) ff.type = item->type; // Process Calendar Appointment item DEBUG_MAIN(("main: Processing Appointment Entry\n")); if (ff.type != PST_TYPE_APPOINTMENT) { diff -r 07ceebd115ce -r 7c60d6d1c681 src/readpst.c --- a/src/readpst.c Wed May 06 10:37:46 2009 -0700 +++ b/src/readpst.c Tue May 12 19:34:49 2009 -0700 @@ -52,10 +52,13 @@ void find_html_charset(char *html, char *charset, size_t charsetlen); void find_rfc822_headers(char** extra_mime_headers); void write_body_part(FILE* f_output, pst_string *body, char *mime, char *charset, char *boundary, pst_file* pst); +void write_schedule_part_data(FILE* f_output, pst_item* item, const char* sender, const char* method); +void write_schedule_part(FILE* f_output, pst_item* item, const char* sender, const char* boundary); void write_normal_email(FILE* f_output, char f_name[], pst_item* item, int mode, int mode_MH, pst_file* pst, int save_rtf, char** extra_mime_headers); void write_vcard(FILE* f_output, pst_item *item, pst_item_contact* contact, char comment[]); -void write_appointment(FILE* f_output, pst_item *item, pst_item_appointment* appointment, - FILETIME* create_date, FILETIME* modify_date); +void write_journal(FILE* f_output, pst_item* item); +int file_time_compare(FILETIME* left, FILETIME* right); +void write_appointment(FILE* f_output, pst_item *item); void create_enter_dir(struct file_ll* f, pst_item *item); void close_enter_dir(struct file_ll *f); @@ -179,10 +182,10 @@ } } - } else if (item->email && (item->type == PST_TYPE_NOTE || item->type == PST_TYPE_REPORT)) { + } else if (item->email && ((item->type == PST_TYPE_NOTE) || (item->type == PST_TYPE_SCHEDULE) || (item->type == PST_TYPE_REPORT))) { if (!ff.type) ff.type = item->type; DEBUG_MAIN(("main: Processing Email\n")); - if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_REPORT)) { + if ((ff.type != PST_TYPE_NOTE) && (ff.type != PST_TYPE_SCHEDULE) && (ff.type != PST_TYPE_REPORT)) { ff.skip_count++; DEBUG_MAIN(("main: I have an email type %"PRIi32", but the folder type %"PRIi32" isn't an email folder. Skipping it\n", item->type, ff.type)); } @@ -203,18 +206,8 @@ else { ff.item_count++; if (mode == MODE_SEPARATE) mk_separate_file(&ff); - fprintf(ff.output, "BEGIN:VJOURNAL\n"); - if (item->subject.str) { - pst_convert_utf8(item, &item->subject); - fprintf(ff.output, "SUMMARY:%s\n", pst_rfc2426_escape(item->subject.str)); - } - if (item->body.str) { - pst_convert_utf8(item, &item->body); - fprintf(ff.output, "DESCRIPTION:%s\n", pst_rfc2426_escape(item->body.str)); - } - if (item->journal->start) - fprintf(ff.output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(item->journal->start)); - fprintf(ff.output, "END:VJOURNAL\n\n"); + write_journal(ff.output, item); + fprintf(ff.output, "\n"); } } else if (item->appointment && (item->type == PST_TYPE_APPOINTMENT)) { @@ -227,7 +220,8 @@ else { ff.item_count++; if (mode == MODE_SEPARATE) mk_separate_file(&ff); - write_appointment(ff.output, item, item->appointment, item->create_date, item->modify_date); + write_appointment(ff.output, item); + fprintf(ff.output, "\n"); } } else if (item->message_store) { @@ -1044,6 +1038,41 @@ } +void write_schedule_part_data(FILE* f_output, pst_item* item, const char* sender, const char* method) +{ + fprintf(f_output, "BEGIN:VCALENDAR\n"); + fprintf(f_output, "VERSION:2.0\n"); + fprintf(f_output, "PRODID:LibPST\n"); + fprintf(f_output, "METHOD:%s\n", method); + fprintf(f_output, "ORGANIZER;CN=\"%s\":MAILTO:%s\n", item->email->outlook_sender_name.str, sender); + write_appointment(f_output, item); + fprintf(f_output, "END:VCALENDAR\n"); +} + + +void write_schedule_part(FILE* f_output, pst_item* item, const char* sender, const char* boundary) +{ + const char* method = "REQUEST"; + const char* charset = "utf-8"; + char fname[20]; + if (!item->appointment) return; + + // inline appointment request + fprintf(f_output, "\n--%s\n", boundary); + fprintf(f_output, "Content-Type: %s; method=\"%s\"; charset=\"%s\"\n\n", "text/calendar", method, charset); + write_schedule_part_data(f_output, item, sender, method); + fprintf(f_output, "\n"); + + // attachment appointment request + snprintf(fname, sizeof(fname), "i%i.ics", rand()); + fprintf(f_output, "\n--%s\n", boundary); + fprintf(f_output, "Content-Type: %s; charset=\"%s\"; name=\"%s\"\n", "text/calendar", "utf-8", fname); + fprintf(f_output, "Content-Disposition: attachment; filename=\"%s\"\n\n", fname); + write_schedule_part_data(f_output, item, sender, method); + fprintf(f_output, "\n"); +} + + void write_normal_email(FILE* f_output, char f_name[], pst_item* item, int mode, int mode_MH, pst_file* pst, int save_rtf, char** extra_mime_headers) { char boundary[60]; @@ -1230,7 +1259,8 @@ } else if (item->attach || (item->email->rtf_compressed.data && save_rtf) || item->email->encrypted_body.data - || item->email->encrypted_htmlbody.data) { + || item->email->encrypted_htmlbody.data + || (item->type == PST_TYPE_SCHEDULE)) { // use multipart/mixed if we have attachments fprintf(f_output, "Content-Type: multipart/mixed;\n\tboundary=\"%s\"\n", boundary); } else { @@ -1295,6 +1325,10 @@ write_email_body(f_output, "The body of this email is encrypted. This isn't supported yet, but the body is now an attachment\n"); } + if (item->type == PST_TYPE_SCHEDULE) { + write_schedule_part(f_output, item, sender, boundary); + } + // other attachments { pst_item_attach* attach; @@ -1473,19 +1507,62 @@ } -void write_appointment(FILE* f_output, pst_item *item, pst_item_appointment* appointment, - FILETIME* create_date, FILETIME* modify_date) +void write_journal(FILE* f_output, pst_item* item) { + pst_item_journal* journal = item->journal; + + // make everything utf8 + pst_convert_utf8_null(item, &item->subject); + pst_convert_utf8_null(item, &item->body); + + fprintf(f_output, "BEGIN:VJOURNAL\n"); + fprintf(f_output, "DTSTAMP:%s\n", pst_rfc2445_datetime_format_now()); + if (item->create_date) + fprintf(f_output, "CREATED:%s\n", pst_rfc2445_datetime_format(item->create_date)); + if (item->modify_date) + fprintf(f_output, "LAST-MOD:%s\n", pst_rfc2445_datetime_format(item->modify_date)); + if (item->subject.str) + fprintf(f_output, "SUMMARY:%s\n", pst_rfc2426_escape(item->subject.str)); + if (item->body.str) + fprintf(f_output, "DESCRIPTION:%s\n", pst_rfc2426_escape(item->body.str)); + if (journal && journal->start) + fprintf(f_output, "DTSTART;VALUE=DATE-TIME:%s\n", pst_rfc2445_datetime_format(journal->start)); + fprintf(f_output, "END:VJOURNAL\n"); +} + + +/** + compare two FILETIME objects after converting to unix time_t. This + allows FILETIMEs that are beyond the range of time_t representaion to + be converted to 0, and therefore seem to be less than the right + side. The actual recurrence end values seen in pst files are very + large (outside the range of 32 bit time_t), but still finite. That is + a strange way to represent an infinite recurrence. + */ +int file_time_compare(FILETIME* left, FILETIME* right) +{ + time_t delta = pst_fileTimeToUnixTime(left) - pst_fileTimeToUnixTime(right); + if (delta < 0) return -1; + if (delta > 0) return 1; + return 0; +} + + +void write_appointment(FILE* f_output, pst_item* item) +{ + pst_item_appointment* appointment = item->appointment; + // make everything utf8 pst_convert_utf8_null(item, &item->subject); pst_convert_utf8_null(item, &item->body); pst_convert_utf8_null(item, &appointment->location); fprintf(f_output, "BEGIN:VEVENT\n"); - if (create_date) - fprintf(f_output, "CREATED:%s\n", pst_rfc2445_datetime_format(create_date)); - if (modify_date) - fprintf(f_output, "LAST-MOD:%s\n", pst_rfc2445_datetime_format(modify_date)); + fprintf(f_output, "DTSTAMP:%s\n", pst_rfc2445_datetime_format_now()); + if (item->create_date) + fprintf(f_output, "CREATED:%s\n", pst_rfc2445_datetime_format(item->create_date)); + if (item->modify_date) + fprintf(f_output, "LAST-MOD:%s\n", pst_rfc2445_datetime_format(item->modify_date)); if (item->subject.str) fprintf(f_output, "SUMMARY:%s\n", pst_rfc2426_escape(item->subject.str)); if (item->body.str) @@ -1509,6 +1586,15 @@ fprintf(f_output, "STATUS:CONFIRMED\n"); break; } + if (appointment->is_recurring) { + const char* rules[] = {"DAILY", "WEEKLY", "MONTHLY", "YEARLY"}; + pst_recurrence *rdata = pst_convert_recurrence(appointment); + fprintf(f_output, "RRULE:FREQ=%s", rules[rdata->type]); + if (rdata->count) fprintf(f_output, ";COUNT=%u", rdata->count); + if (rdata->interval) fprintf(f_output, ";INTERVAL=%u", rdata->interval); + fprintf(f_output, "\n"); + pst_free_recurrence(rdata); + } switch (appointment->label) { case PST_APP_LABEL_NONE: fprintf(f_output, "CATEGORIES:NONE\n"); @@ -1545,7 +1631,7 @@ break; } } - fprintf(f_output, "END:VEVENT\n\n"); + fprintf(f_output, "END:VEVENT\n"); } @@ -1617,7 +1703,15 @@ f->dname, f->item_count, f->skip_count, f->stored_count)); if (output_mode != OUTPUT_QUIET) printf("\t\"%s\" - %i items done, %i items skipped.\n", f->dname, f->item_count, f->skip_count); - if (f->output) fclose(f->output); + if (f->output) { + struct stat st; + fclose(f->output); + stat(f->name, &st); + if (!st.st_size) { + WARN(("removing empty output file %s ", f->name)); + remove(f->name); + } + } free(f->name); free(f->dname); diff -r 07ceebd115ce -r 7c60d6d1c681 src/timeconv.c --- a/src/timeconv.c Wed May 06 10:37:46 2009 -0700 +++ b/src/timeconv.c Tue May 12 19:34:49 2009 -0700 @@ -18,15 +18,12 @@ time_t pst_fileTimeToUnixTime(const FILETIME *filetime) { - int64_t t = filetime->dwHighDateTime; + uint64_t t = filetime->dwHighDateTime; + const uint64_t bias = 11644473600LL; t <<= 32; t += filetime->dwLowDateTime; - t -= 116444736000000000LL; - if (t < 0) { - return -1 - ((-t - 1) / 10000000); - } - else { - return t / 10000000; - } + t /= 10000000; + t -= bias; + return ((t > (uint64_t)0x000000007fffffff) && (sizeof(time_t) <= 4)) ? 0 : (time_t)t; }