aboutsummaryrefslogtreecommitdiffstats
path: root/camel
diff options
context:
space:
mode:
Diffstat (limited to 'camel')
-rw-r--r--camel/ChangeLog4865
-rw-r--r--camel/camel-charset-map.c361
-rw-r--r--camel/camel-filter-driver.c1526
-rw-r--r--camel/camel-filter-search.c704
-rw-r--r--camel/camel-folder-search.c1409
-rw-r--r--camel/camel-folder-summary.c2888
-rw-r--r--camel/camel-gpg-context.c1817
-rw-r--r--camel/camel-html-parser.c807
-rw-r--r--camel/camel-http-stream.c612
-rw-r--r--camel/camel-lock-helper.c391
-rw-r--r--camel/camel-lock.c423
-rw-r--r--camel/camel-mime-filter-canon.c190
-rw-r--r--camel/camel-mime-filter-enriched.c603
-rw-r--r--camel/camel-mime-parser.c1930
-rw-r--r--camel/camel-mime-parser.h148
-rw-r--r--camel/camel-mime-utils.c4320
-rw-r--r--camel/camel-movemail.c540
-rw-r--r--camel/camel-multipart-signed.c794
-rw-r--r--camel/camel-operation.c730
-rw-r--r--camel/camel-sasl-digest-md5.c906
-rw-r--r--camel/camel-sasl-gssapi.c346
-rw-r--r--camel/camel-sasl-kerberos4.c230
-rw-r--r--camel/camel-sasl-ntlm.c706
-rw-r--r--camel/camel-service.c1060
-rw-r--r--camel/camel-service.h202
-rw-r--r--camel/camel-smime-context.c1054
-rw-r--r--camel/camel-store.c1205
-rw-r--r--camel/camel-tcp-stream-raw.c518
-rw-r--r--camel/camel-tcp-stream-ssl.c1256
-rw-r--r--camel/camel-tcp-stream.c208
-rw-r--r--camel/camel-tcp-stream.h123
-rw-r--r--camel/camel-uid-cache.c334
-rw-r--r--camel/camel-url.c592
-rw-r--r--camel/camel-vee-folder.c1789
-rw-r--r--camel/camel-vee-folder.h88
-rw-r--r--camel/camel-vee-store.c436
-rw-r--r--camel/camel-vee-store.h63
-rw-r--r--camel/camel-vtrash-folder.c230
-rw-r--r--camel/providers/groupwise/camel-gw-listener.c889
-rw-r--r--camel/providers/imap/camel-imap-command.c847
-rw-r--r--camel/providers/imap/camel-imap-folder.c2814
-rw-r--r--camel/providers/imap/camel-imap-store.c3271
-rw-r--r--camel/providers/imap4/camel-imap4-engine.c1550
-rw-r--r--camel/providers/imap4/camel-imap4-store.c1391
-rw-r--r--camel/providers/imapp/camel-imapp-store.c1016
-rw-r--r--camel/providers/local/camel-local-folder.c631
-rw-r--r--camel/providers/local/camel-local-folder.h109
-rw-r--r--camel/providers/local/camel-maildir-folder.c278
-rw-r--r--camel/providers/local/camel-maildir-store.c544
-rw-r--r--camel/providers/local/camel-mbox-folder.c557
-rw-r--r--camel/providers/local/camel-mbox-folder.h66
-rw-r--r--camel/providers/local/camel-mbox-store.c837
-rw-r--r--camel/providers/local/camel-mh-folder.c233
-rw-r--r--camel/providers/local/camel-spool-folder.c217
-rw-r--r--camel/providers/local/camel-spool-store.c465
-rw-r--r--camel/providers/nntp/camel-nntp-folder.c532
-rw-r--r--camel/providers/nntp/camel-nntp-private.h64
-rw-r--r--camel/providers/nntp/camel-nntp-store.c1408
-rw-r--r--camel/providers/nntp/camel-nntp-store.h114
-rw-r--r--camel/providers/nntp/camel-nntp-stream.c462
-rw-r--r--camel/providers/nntp/camel-nntp-summary.c504
-rw-r--r--camel/providers/pop3/camel-pop3-store.c682
-rw-r--r--camel/providers/smtp/camel-smtp-transport.c1413
-rw-r--r--camel/providers/smtp/camel-smtp-transport.h80
-rw-r--r--camel/tests/folder/Makefile.am28
-rw-r--r--camel/tests/folder/README14
-rw-r--r--camel/tests/folder/test11.c188
-rw-r--r--camel/tests/lib/folders.c580
-rw-r--r--camel/tests/message/test2.c326
-rw-r--r--camel/tests/message/test3.c196
-rw-r--r--camel/tests/mime-filter/Makefile.am38
-rw-r--r--camel/tests/mime-filter/test1.c108
72 files changed, 58856 insertions, 0 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog
new file mode 100644
index 0000000000..329528c6ee
--- /dev/null
+++ b/camel/ChangeLog
@@ -0,0 +1,4865 @@
+2005-02-01 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #72020.
+
+ * camel-filter-driver.c (do_score): don't use
+ camel_folder_set_message_user_tag anymore, use the messageinfo
+ interface.
+ (do_adjust_score): implement missing function.
+
+2005-02-01 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #65329
+
+ * camel-vtrash-folder.c (camel_vtrash_folder_new): api chagne and
+ translate the short name.
+
+ * camel-vee-folder.c (camel_vee_folder_construct): take full-name
+ argument separately from short-name.
+ (camel_vee_folder_new): calculate short-name.
+
+2005-02-01 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #38791 and bug #36142.
+
+ * camel-gpg-context.c (gpg_ctx_op_step): use poll rather than
+ select, and listen to the cancellation fd as well. perform all
+ cancellation here.
+ (gpg_sign): remove cancellation check/processing.
+ (gpg_verify): same.
+ (gpg_encrypt): same.
+ (gpg_decrypt): same.
+ (gpg_ctx_op_step): remove the messy execption handling stuff, it
+ wasn't useful.
+ (gpg_import_keys, gpg_export_keys): clean up exception case.
+
+2005-01-31 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #70303.
+
+ * tests/mime-filter/test1.c: added new test for
+ camel-mime-filter-canon.c
+
+ * camel-mime-filter-canon.c (filter_run): separated out from
+ filter. Fix a case when it finds an embedded "From " not to
+ create "=46rom rom".
+ (complete): Properly canonicalise \n -> \r\n rather than just
+ copying it across (use filter_run now).
+
+2005-01-31 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69757.
+
+ * providers/imap/camel-imap-store.c (create_folder)
+ (parse_list_response_as_folder_info): free the
+ folder from parse_list_response.
+
+2005-01-28 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #22496.
+
+ * providers/imap/camel-imap-command.c
+ (camel_imap_command_response): check for no and bad [alert] as
+ well as ok [alert].
+
+2005-01-17 Not Zed <NotZed@Ximian.com>
+
+ * tests/lib/folders.c (test_folder_basic): dont enforce local
+ store == no inbox; maildir implements one now.
+
+2005-01-14 Not Zed <NotZed@Ximian.com>
+
+ ** Better fix for #65178.
+
+ * tests/folder/test11.c: new maildir test.
+
+ * camel-store.c (camel_store_folder_uri_equal): dont pass NULL
+ values to compare_folder_name.
+
+ * providers/local/camel-maildir-store.c (get_folder_info):
+ reimplemented, now everything isn't a subfolder of INBOX ('.')
+ anymore.
+ (get_inbox): call main entry point so we don't create multiple
+ inboxes each call.
+ (scan_dirs): re-implemented using non-recursive implementation.
+ (scan_fi, scan_free, scan_equal, scan_hash): helpers for above.
+ (md_canon_name): helper to canonicalise old-format names to new
+ ones.
+ (maildir_hash_folder_name, maildir_compare_folder_name): implement
+ custom folder hash functions so canonicalisation works.
+ (get_folder): canoncalise name before using it. special case '.'
+ aka 'inbox', so that it is implicitly created if accessed.
+ (delete_folder): don't let the caller delete ".".
+ (camel_folder_info_new): removed.
+
+2005-01-24 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #71427.
+
+ * providers/pop3/camel-pop3-store.c (pop3_connect): fix the
+ exception case for re-prompting for the password.
+
+ ** See bug #71625, and bug #56110.
+
+ * providers/imap/camel-imap-folder.c (get_content): when getting a
+ simple content for a message, don't get .TEXT, as that is the full
+ content of a message, not the part itself.
+
+2005-01-17 Not Zed <NotZed@Ximian.com>
+
+ * camel-multipart-signed.c (skip_content): in the message state
+ skip a state, otherwise we go nowhere but down an overflowed stack drain.
+
+2005-01-13 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69024.
+
+ * providers/nntp/camel-nntp-store.c
+ (nntp_store_get_subscribed_folder_info): emit a folder_changed
+ event if refreshing it added any changes.
+
+2005-01-12 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47824.
+
+ * camel-multipart-signed.c (parse_content): re-implemented to use
+ camel-mime-parser. For the annoying pathalogical case of re-used
+ multipart boundary markers.
+
+ * camel-mime-parser.c (folder_scan_step): modified the multipart
+ state so that we don't try to match the same boundary again if
+ we've already seen an end boundary for this part. This is for the
+ annoying case of a multipart inside a multipart which shares an
+ identical boundary marker (groupwise!).
+ (camel_mime_parser_tell_start_boundary): new function to indicate
+ the offset of the start of the last boundary.
+
+ * tests/lib/folders.c (test_folder_message_ops): fix test case for
+ deleting message - marks message read too.
+ (test_folder_counts): fix typo.
+
+ * tests/message/test3.c (main): remove the nonfatal fixme & put
+ extra null check for pre/postface text checks.
+
+ * tests/message/test2.c (convert): use size_t for iconv length
+ types.
+
+2005-01-08 Not Zed <NotZed@Ximian.com>
+
+ * camel-sasl-ntlm.c: convert all the unsigned long/long stuff to
+ guint32 since it seems to need 32 bit integers.
+
+ * camel-url.c (camel_url_new_with_base): cast length to int.
+
+ * providers/imap/camel-imap-command.c
+ (imap_command_strdup_vprintf): cast strlen to int.
+
+ * camel-mime-utils.c (append_quoted_pair, header_decode_text): use
+ gssize for length to match dumb g-api.
+
+
+2005-01-20 Not Zed <NotZed@Ximian.com>
+
+ * camel-lock-helper.c (main): since malloc(MAXINT+1) returns a
+ valid pointer, validate the length of the path before using it.
+ set maximum path to 65000 characters. Spotted by Max Vozeler
+ <max@hinterhof.net>
+
+2005-01-11 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #70919.
+
+ * camel-multipart-signed.c (parse_content): treat the post pointer
+ as binary, not 0 terminated.
+
+2004-12-23 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #70556.
+
+ * providers/imap/camel-imap-command.c (imap_read_untagged): scan
+ the non-literal contnet for s-expression brackets, and if we seem
+ to be outside of one, don't try the 'blank line after literal'
+ hack.
+
+2004-12-23 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder-search.c (check_header): treat a missing header as
+ if it was set to "".
+
+2004-12-02 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69533.
+
+ * providers/imap/camel-imap-command.c (imap_read_untagged): gross
+ hack, if we get a blank line after a literal, assume the server
+ (read: groupwise) made a mistake. Given the complexity of this
+ code i'm not sure it is the server anyway but what can you do.
+
+2004-12-01 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69776.
+
+ * camel-multipart-signed.c (parse_boundary): take end of data
+ argument, so it handles binary data. Use a binary data string
+ search rather than strstr.
+ (parse_content): dont bother to append a \0 on the end of the
+ data, not binary capable.
+
+2004-11-23 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69615.
+
+ * camel-smime-context.c (sm_get_passwd): removed. All callers
+ that passed it now pass NULL. This is so we don't override the
+ password function set by e-cert-db. Seems to work ok, I think.
+
+2004-11-22 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69109.
+
+ * providers/smtp/camel-smtp-transport.c (smtp_helo): if we have
+ ipv6 address and it is numeric, prefix it with "IPv6:"
+
+2004-11-30 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69982 (maybe).
+
+ * providers/nntp/camel-nntp-stream.c (CAMEL_NNTP_STREAM_LINE):
+ rename to STREAM_LINE_SIZE so it doesn't override the STREAM_LINE
+ enum. Sigh.
+ (camel_nntp_stream_init): fix for above change.
+
+2004-11-28 S.Çağlar Onur <caglar@uludag.org.tr>
+
+ ** See bug #69446.
+
+ * evolution-2.0.2/camel/camel-charset-map.c (camel_charset_iso_to_windows)
+ * evolution-2.0.2/camel/camel-filter-search.c (check_header)
+ * evolution-2.0.2/camel/camel-folder-search.c (check_header)
+ * evolution-2.0.2/camel/camel-folder-summary.c (message_info_new,summary_build_content_info,camel_system_flag)
+ * evolution-2.0.2/camel/camel-html-parser.c (camel_html_parser_attr)
+ * evolution-2.0.2/camel/camel-mime-filter-enriched.c (param_parse,camel_mime_filter_enriched_init)
+ * evolution-2.0.2/camel/camel-mime-parser.c (folder_scan_step,main)
+ * evolution-2.0.2/camel/camel-mime-utils.c (camel_header_param,camel_header_set_param,camel_content_type_is,camel_transfer_encoding_from_string,camel_content_type_format,camel_content_type_simple,camel_header_decode_date,header_raw_find_node)
+ * evolution-2.0.2/camel/camel-sasl-digest-md5.c (decode_data_type)
+ * evolution-2.0.2/camel/providers/imap/camel-imap-command.c (camel_imap_response_free)
+ * evolution-2.0.2/camel/providers/imap/camel-imap-folder.c (camel_imap_folder_new,camel_imap_folder_selected,imap_refresh_info,camel_imap_folder_new,camel_imap_folder_selected)
+ * evolution-2.0.2/camel/providers/imap/camel-imap-store.c (imap_get_capability,imap_connect_online,get_folder_online,get_folder_offline,get_subscribed_folders,folder_hash,get_folders)
+ * evolution-2.0.2/camel/providers/imap4/camel-imap4-engine.c (engine_parse_capability)
+ * evolution-2.0.2/camel/providers/pop3/camel-pop3-store.c (get_folder)
+ * evolution-2.0.2/camel/tests/lib/folders.c: (test_folder_message_ops)
+ some strcasecmp() calls changed with g_ascii_strcasecmp() for Turkish
+ character conversiton problems [ http://www.i18nguy.com/unicode/turkish-i18n.html ]
+
+2004-11-10 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69109.
+
+ * camel-service.c (cs_getnameinfo): honour the NI_NAMEREQD flag.
+
+ * providers/smtp/camel-smtp-transport.c (smtp_helo): change the
+ nameinfo flags a bit so we know when we got a numeric name and
+ need to wrap it in [].
+
+2004-11-19 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c (connect_to_server): if we
+ have CAMEL_IMAP_BRAINDAMAGED set, then treat the server as
+ braindamaged ok. Testing for #69533.
+
+2004-11-18 Not Zed <NotZed@Ximian.com>
+
+ * providers/nntp/camel-nntp-stream.c:
+ * providers/nntp/camel-nntp-store.c:
+ * providers/nntp/camel-nntp-summary.c: Make debug run based on
+ 'nntp' debug option.
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_try_authenticate):
+ retry if the password attempt failed.
+
+ ** See bug #68556.
+
+ * providers/nntp/camel-nntp-store.c (xover_setup): don't overwrite
+ exception if we get a failure.
+ (camel_nntp_command): if we continue, then set the return code to
+ -1, so we re-loop rather than abort.
+
+2004-11-09 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_get_message): before
+ short-circuiting the check for child content, check the child
+ content info is actually correct.
+
+2004-11-08 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #69145.
+
+ * providers/imap/camel-imap-folder.c (get_message): remove spec
+ argument, always calculate it from the content-info.
+ (content_info_incomplete): recursively check the content-info for
+ completeness, not just one level.
+
+2004-11-08 Jeffrey Stedfast <fejj@novell.com>
+
+ Fix for bug #69241.
+
+ * camel-gpg-context.c (gpg_decrypt): We need to extract just the
+ application/pgp-encrypted part from the multipart/encrypted that
+ gets passed in. Added checks to verify that the input part is the
+ correct type as well. Once we have the application/pgp-encrypted
+ part, we need to use camel_data_wrapper_decode_to_stream() in case
+ the part was encoded in any way.
+
+2004-10-27 Julio M. Merino Vidal <jmmv@menta.net>
+
+ * camel-operation.c (camel_operation_shutdown): fix the arguments
+ to pthread_key_delete.
+
+2004-10-12 Not Zed <NotZed@Ximian.com>
+
+ ** See bug ???
+
+ * providers/nntp/camel-nntp-store.c (connect_to_server): if we
+ have a username, try to authenticate before doing anything else.
+
+ ** See bug #67895.
+
+ * providers/nntp/camel-nntp-summary.c (add_range_xover)
+ (add_range_head): use raw_command_auth since we might need auth
+ here.
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_raw_command_auth):
+ new almost-raw command that also does auth.
+ (xover_setup, connect_to_server, camel_nntp_command): use
+ raw_command_auth since we might need auth here.
+
+2004-10-12 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #67898 and probably others.
+
+ * providers/imapp/camel-imapp-store.c (connect_to_server):
+ * providers/pop3/camel-pop3-store.c (connect_to_server):
+ * providers/imap4/camel-imap4-store.c (connect_to_server_wrapper):
+ * providers/imap/camel-imap-store.c (connect_to_server):
+ * providers/nntp/camel-nntp-store.c (connect_to_server):
+ * providers/smtp/camel-smtp-transport.c (connect_to_server):
+ Fallback to hard-coded port number if the name lookup fails and no
+ port was supplied.
+
+2004-10-11 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #67211.
+
+ * camel-mime-utils.c (camel_header_raw_check_mailing_list):
+ initialise the match start/end pointers, since some regexec's
+ don't seem to do it.
+
+2004-10-09 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listner.c (add_esource) :
+ add the source uid to list of selected calendar and tasks
+ so that groupwise calendar and tasks are automatically selected
+ (remove_esource) : remove the uids from corresponding gconf keys
+ Fixes #62053
+
+2004-10-08 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #67170.
+
+ * providers/nntp/camel-nntp-store.c
+ (nntp_store_get_cached_folder_info): compare newsgroup names case
+ sensitively.
+
+2004-10-05 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-service.c (camel_getaddrinfo): Check msg->result for error
+ and set an exception if appropriate.
+ (camel_getnameinfo): Same.
+
+2004-10-04 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-service.c (camel_getaddrinfo): Add a non-const cast for
+ hints when changing the ai_family member in the IPv6-disabled
+ case. Fixes bug #67028.
+
+2004-10-05 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #67527.
+
+ * camel-service.c (cs_getaddrinfo, cs_getnameinfo): don't loop on
+ EAI_AGAIN, it doesn't appear to mean the same as EAGAIN does with
+ system calls (i guess 'no shit sherlock' really).
+
+2004-09-28 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #66509.
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_command): if we
+ get an error selecting the folder, disconnect/include it in the
+ re-try loop.
+ (camel_nntp_command): don't set the exception based on errno,
+ exception processing is already done. don't clear it if we're on
+ the 3rd retry.
+
+2004-09-27 Not Zed <NotZed@Ximian.com>
+
+ * providers/nntp/camel-nntp-store.c (nntp_get_folder_info): don't
+ do any locking here.
+ (nntp_store_get_folder_info_all): move the locking here.
+ (nntp_store_get_subscribed_folder_info): and some here too.
+
+ * providers/nntp/camel-nntp-store.c:
+ * providers/nntp/camel-nntp-folder.c: Remove nntp command_lock and
+ just use the service connect lock for serialisation.
+
+2004-09-14 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_try_authenticate):
+ s/not/no/ in the error string. Fixes bug #65828.
+
+2004-09-24 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-store.c (camel_vee_store_finalise): free the unmatched
+ uid values as well.
+
+ * camel-vee-folder.c (vee_folder_remove_folder): lock main folder
+ summary lock before doing the unmatched stuff, so the order is
+ right.
+
+2004-08-25 Ed Catmur <ed@catmur.co.uk>
+
+ ** See bug #63881.
+
+ * camel-vee-store.c:
+ * camel-vee-folder.c: move the unmatched
+ folder onto the camel-vee-store object. Removede the global
+ unmatched folder and associated locks/etc, fixed all the code up
+ to work with the new unmatched folder, if present.
+
+2004-09-27 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-spool-folder.c (camel_spool_folder_new):
+ make sure body indexing is turned off always, missed the ~ bit.
+
+ * providers/local/camel-spool-store.c (camel_folder_info_new):
+ dont take unread count.
+ (spool_fill_fi): copied from mbox more or less.
+ (scan_dir): use fill_fi to setup counts.
+ (spool_new_fi): replace camel_foldeR_info_new with one that does
+ most of the work, also generates uri's properly.
+ (get_folder_info_mbox): make the 'system' inbox name translatable.
+
+ * providers/local/camel-mbox-folder.h: make the
+ camel_mbox_folder_get* functions properly public.
+
+ * providers/local/camel-local-folder.h: pass the object to the
+ virtual methods now, fix all callers.
+
+ * providers/local/camel-spool-folder.c (spool_get_full_path)
+ (spool_get_meta_path): implement, this needs to work differnetly
+ to the parent classes implementations :-/.
+
+2004-09-21 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #63521.
+
+ * camel-movemail.c (camel_movemail): don't clear exception on entry.
+
+ * camel-folder-search.c (match_words_message): use local exception.
+
+ * camel-operation.c (camel_operation_cancel_check): soak up all
+ cancellation requests as soon as we get one.
+ (camel_operation_uncancel): soak up all cancellation reqeusts when
+ we uncancel.
+
+ * camel-uid-cache.c (camel_uid_cache_save): open the file O_TRUNC
+ rather than O_EXCL, otherwise a crash would mean this file never
+ gets updated.
+ (camel_uid_cache_save): block cancellation around writes otherwise
+ we could be interupted from old cancellation.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): don't clear exception here, just
+ don't pass it to summary load.
+
+ * providers/pop3/camel-pop3-store.c (pop3_connect): only clear the
+ exception when we received one we handled.
+
+ * camel-filter-driver.c (close_folder): if exception is already
+ set, don't pass it to folder.sync().
+
+ * camel-lock.c (camel_lock_folder): don't clear the exception
+ here, if it came in set its a programming error.
+
+ * camel-filter-driver.c (camel_filter_driver_filter_message): if
+ the exception is set after evaluating the expression, stop
+ immediately.
+
+2004-09-13 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47821.
+
+ * camel-service.c: removed the old hostent based hostname interfaces.
+
+ * camel-sasl-kerberos4.c (krb4_challenge): new hostname interfaces.
+
+ * camel-sasl-gssapi.c (gssapi_challenge): new hostname interfaces.
+
+ * camel-sasl-digest-md5.c (digest_md5_challenge): use new hostname
+ interfaces.
+ (generate_response): just take hostname directly, not hostent.
+
+ * camel-mime-utils.c (camel_header_msgid_generate): use new
+ hostname interfaces.
+
+ * providers/smtp/camel-smtp-transport.c (connect_to_server): fixed
+ to use new addrinfo apis.
+
+ * providers/pop3/camel-pop3-store.c (connect_to_server): fixed to
+ use new addrinfo apis.
+
+ * camel-tcp-stream-ssl.c (stream_connect): try all addresses
+ supplied.
+
+ * camel-tcp-stream.c (camel_tcp_stream_get_remote_address)
+ (camel_tcp_stream_get_local_address): return a sockaddr now, and
+ also the address length. Fixed all implementations and callers.
+ (camel_tcp_stream_connect): use addrinfo rather than hostent for
+ host AND port info. Fixed all implementations and callers.
+
+2004-09-22 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder-summary.c (camel_folder_summary_decode_token):
+ handle a zero-length token read rather than failing.
+
+2004-09-21 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #66199.
+
+ * camel-http-stream.c (stream_read): handle relative url's in
+ redirect.
+ (camel_http_stream_set_proxy): generate the basic auth token for
+ basic proxy auth if we have a user and password.
+
+ * camel-http-stream.c: turn off debug.
+
+2004-09-15 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #0xffff.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): only emit folder_created if we
+ actually created it.
+
+2004-09-03 Not Zed <NotZed@Ximian.com>
+
+ * camel-tcp-stream-ssl.c (stream_connect): make ssl connection
+ async and cancellable, and minor api update to async connection.
+
+2004-08-27 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #64023.
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_try_authenticate):
+ forget the password if it was wrong.
+
+2004-08-26 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listner.c
+ (add_calendar_tasks_sources) : change the "CheckList" to "Tasks"
+ as there is another foder called chekclist in groupwise not
+ related to tasks Fixes #64092
+
+2004-08-25 Frederic Crozat <fcrozat@mandrakesoft.com>
+
+ * camel-folder.c (folder_getv): Init one variable (remove warning)
+ and don't redefine it.
+
+2004-08-23 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #63189.
+
+ * providers/imap/camel-imap-store.c (get_subscribed_folders): only
+ LSUB folders we're interested in, and check full name of each path
+ element.
+ (imap_is_subfolder): helper for above.
+
+2004-08-23 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap/camel-imap-store.c (get_folders): Check for an
+ exception from get_folders_online() here so that we don't send
+ commands to an IMAP server after a disconnect for example. See bug
+ #63504 for an example.
+
+2004-08-23 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-tcp-stream-openssl.c (open_ssl_connection): Call
+ SSL_CTX_set_default_verify_paths() to initialise the certificate
+ database paths. Thanks to Anton Altaparmakov for this fix.
+
+2004-08-21 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c (account_changed) :
+ if any of the settings required for soap interaction changes
+ try connecting to server and resetup the ESources
+ * providers/groupwise/camel-groupwise-provider.c ": don't
+ check soap ssl setting by default to be in consistent with
+ IMAP
+
+2004-08-16 Not Zed <NotZed@Ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c
+ (camel_provider_module_init): pass an exception handle to
+ camel_provider_get.
+
+2004-08-13 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-summary.c (imap4_summary_fetch_all):
+ Use g_ptr_array_sized_new() rather than using set_size() after
+ creating a GPtrArray so that array->len starts out at 0.
+ (imap4_summary_fetch_flags): Same.
+
+2004-08-13 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c (account_changed):
+ remove the cal/taksks sources when account is disbaled. Also do
+ not try to to create sources when a disabled account is changed to
+ Novell Groupwise
+ (camel_gw_listener_construct): do not add the disbaled accounts
+ to exitsting groupwise accounts list
+
+2004-08-13 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-groupwise-provider.c: update ssl
+ setting label and title of the soap settings section. Fixes #62747
+
+2004-08-13 Rodney Dawes <dobey@novell.com>
+
+ * camel-sasl-gssapi.c: Handle et/comm_err.h as well as the
+ normal comm_err.h
+
+2004-08-11 Jeffrey Stedfast <fejj@novell.com>
+
+ Fix for bug #62771
+
+ * camel-mime-utils.c (append_quoted_pair): New function to append
+ a string of text that may contain quoted-pairs.
+ (header_decode_text): Now takes a ctext argument specifying
+ whether or not to expect comments and to handle them.
+ (camel_header_decode_string): Pass FALSE as ctext argument.
+ (camel_header_format_ctext): New function to format text|comment
+ header field bodies.
+
+2004-08-10 Not Zed <NotZed@Ximian.com>
+
+ * providers/groupwise/camel-gw-listener.c
+ (get_addressbook_names_from_server): fix for e_passwords api
+ change, and handle reprompting as well.
+
+2004-08-06 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-summary.c (untagged_fetch_all): Call
+ camel_operation_progress().
+ (imap4_summary_fetch_all): Setup info we need for progress
+ reporting.
+ (imap4_summary_fetch_flags): Same.
+
+2004-08-04 Rodney Dawes <dobey@novell.com>
+
+ * camel-charset-map.c: #include <gal/util/e-iconv.h>
+
+2004-08-03 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-url-scanner.c (camel_url_scanner_scan): In the case of
+ start() or end() failing, loop starting with the first character
+ immediately following the failed match position. Fixes bug #62136.
+
+2004-08-03 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_construct): Pass a
+ reconnect func.
+
+ * providers/imap4/camel-imap4-engine.c
+ (camel_imap4_engine_iterate): Reconnect if needed.
+ (camel_imap4_engine_new): Now takes a reconnect func.
+
+2004-07-30 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-engine.c
+ (camel_imap4_engine_capability): This needs to prequeue the
+ CAPABILITY command rather than queue it normally for the case of
+ reconnecting.
+ (camel_imap4_engine_namespace): Same.
+
+2004-07-30 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (camel_header_encode_string): Similar fix as
+ below in a later if-statement. Thanks to Suresh for spotting this
+ one.
+
+2004-07-28 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-mime-utils.c (camel_header_encode_string): Fixed an ABR
+ that may have been responsible for bug #62029.
+
+2004-07-29 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * proivders/groupwise/camel-groupwise-provider.c: Add an entry to
+ specify POA address and a check box to say whehter ssl has to be
+ used for SOAP or not
+ (groupwise_auto_detect_cb): new function to automatically fill POA
+ address with host specified in receiving mail page
+ (camel_provider_module_init): use the new auto_detect funcntion
+
+ * providers/groupwise/camel-gw-listener.c (modify_esource)
+ (account_changed, add_calendar_tasks_sources)
+ (remove_calendar_tasks_sources, get_addressbook_names_from_server)
+ (add_addressbook_sources, modify_addressbook_sources)
+ (remove_addressbook_sources): while forming the uri, use the value
+ from new poa_address setting instead of url->host. Also use ssl
+ setting from the new check box provider instaed of imap one
+ (get_addressbook_names_from_server): display an error to user when
+ connection cpuld not be established with server during account
+ setup
+
+2004-07-26 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-charset-map.c (camel_charset_best_mask): Changed the logic
+ slightly to only match certain charsets if the locale matches
+ (Macedonians don't want to use koi8-r for example).
+
+2004-07-27 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #61841.
+
+ * providers/groupwise/camel-groupwise-provider.c: added junk settings.
+
+2004-07-27 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-engine.c
+ (camel_imap4_engine_prequeue): Changed to be the same prototype as
+ engine_queue().
+ (engine_prequeue_folder_select): Updated.
+
+ * providers/imap4/camel-imap4-store.c (connect_to_server): Use
+ engine_prequeue() for STARTTLS in case we are reconnecting and
+ already have a command queue.
+ (imap4_try_authenticate): Use prequeue() here too.
+ (imap4_reconnect): Moved all the connect logic in here.
+ (imap4_connect): just lock and call reconnect().
+
+2004-07-27 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ Fixes #61454
+
+ * providers/groupwise/camel-groupwise-provider.c
+ (camel_provider_module_init): do not set transport object for
+ groupwise provider. We want user to use SMTP itself instead of
+ "Novell Groupwise"
+
+2004-07-26 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-store.c (connect_to_server): Don't
+ instantiate an engine here. Instead, take an engine as an argument
+ (it has a service pointer) and connect using that. Also, if
+ connect fails, don't unref the engine.
+ (connect_to_server_wrapper): Now also takes an engine argument
+ rather than a service argument.
+ (imap4_try_authenticate): Now also takes an engine argument.
+ (imap4_connect): Pass the engine to connect/auth functions rather
+ than the store.
+ (imap4_query_auth_types): Updated.
+ (imap4_disconnect): Don't unref the engine here.
+ (camel_imap4_store_init): Create the engine here.
+ (imap4_get_folder_info): Can't check engine == NULL to know to
+ connect (that was a broken check anyway).
+
+ * providers/imap4/camel-imap4-engine.c (camel_imap4_engine_new):
+ Now simply takes a service argument rather than a session and url.
+ (camel_imap4_engine_next_token): Set the state to DISCONNECTED.
+ (camel_imap4_engine_eat_line): Same.
+ (camel_imap4_engine_line): Same.
+ (camel_imap4_engine_literal): Same.
+
+2004-07-22 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #61761.
+
+ * providers/pop3/camel-pop3-engine.c (camel_pop3_engine_iterate):
+ if we get an io error, move every current/active and queued
+ command to the done queue and mark as failed.
+
+2004-07-19 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap/camel-imap-store.c (get_subscribed_folders): Free
+ result after parsing it. Fixes a leak.
+
+2004-07-19 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-filter-canon.c (filter): only copy 5 chars after the
+ F if we actually have "From ", otherwise we might have F.{,4}\n
+ instead and break eol canonicalisation. For #53355.
+
+2004-07-16 Not Zed <NotZed@Ximian.com>
+
+ * camel-gpg-context.c: Added some debug stuff.
+
+2004-07-15 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap/camel-imap-folder.c (imap_transfer_online): Don't
+ grab the connect_lock before calling refresh_info so that we avoid
+ the deadlock in bug #61551.
+
+2004-07-14 Jeffrey Stedfast <fejj@novell.com>
+
+ Fix for bug #61538
+
+ * camel-process.c (camel_process_fork): Same.
+
+ * camel-filter-driver.c (pipe_to_system): Fixed strings
+
+2004-07-12 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c (add_esource)
+ (modify_esource, add_addressbook_source)
+ (modify_addressbook_sources): pass "use_ssl" value to these
+ functions and set it on e-source
+ (add_calendar_tasks_source): read the "use_ssl" param from camel
+ url and pass it to add_esource) calls
+ (get_addressbook_names_from_server): use "https" or http depending
+ upon whther ssl is enabled or not
+ (account_changed): compare urls from account instead of uris
+ formed to know wheter somehting in the account changed
+
+2004-07-09 Not Zed <NotZed@Ximian.com>
+
+ ** This is no guarantee of security, but its just a helper to
+ prevent old memory accidentally being included/used elsewhere.
+
+ * camel-smime-context.c (sm_decrypt): mark the output stream
+ 'secure'.
+
+ * camel-gpg-context.c (gpg_decrypt): set the output stream to
+ secured, so we automagically blank it out on finalise.
+
+ * camel-stream-mem.c (camel_stream_mem_set_secure): set the
+ memory-stream 'secured', all we do at the moment is blank out the
+ buffer on finalise.
+ (camel_stream_mem_set_byte_array, camel_stream_mem_finalize):
+ clear memory if owner and secured. kill dead comment.
+ (clear_mem): utilitiy to set memory to 0xABADF00D
+
+2004-07-08 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #61186.
+
+ * camel-cipher-context.c (camel_cipher_sign):
+ (camel_cipher_verify, camel_cipher_encrypt, camel_cipher_decrypt):
+ Add preliminary progress reporting.
+
+2004-07-07 Chris Toshok <toshok@ximian.com>
+
+ * providers/groupwise/Makefile.am (INCLUDES): use
+ CAMEL_GROUPWISE_CFLAGS.
+ (libcamelgroupwise_la_LIBADD): use CAMEL_GROUPWISE_LIBS.
+
+2004-07-02 Christian Neumair <chris@gnome-de.org>
+
+ * camel-smime-context.c: s/Can't/Cannot/.
+
+2004-06-30 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-search.c (imap4_body_contains): Set
+ the size of the ptrarray to prevent potentially realloc'ing
+ several times.
+
+2004-06-29 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-command.c
+ (camel_imap4_command_newv): Aded a new %formatter 'V' which takes
+ a string vector (needed for SEARCH).
+
+ * providers/imap4/camel-imap4-search.[c,h]: New source files
+ implementing search functionality.
+
+ * providers/imap4/camel-imap4-folder.c (imap4_sync_flag): Use the
+ new public version of imap4_get_uid_set().
+ (imap4_transfer_messages_to): Same.
+ (camel_imap4_folder_new): Create a search context.
+ (camel_imap4_folder_finalize): Unref the search context.
+ (camel_imap4_folder_class_init): Override the search methods.
+ (imap4_search_by_expression): New.
+ (imap4_search_by_uids): New.
+ (imap4_search_free): New.
+
+ * providers/imap4/camel-imap4-utils.c (camel_imap4_get_uid_set):
+ Moved here from camel-imap4-folder.c
+
+2004-06-29 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-store.c (vee_rename_folder): add any parents of the
+ new name before we actually do the rename so the rename has
+ somewhere to go to. #60775.
+
+2004-06-28 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-maildir-folder.c (maildir_folder_getv):
+ override CAMEL_FOLDER_NAME arg so we can translate "." into
+ "Inbox".
+
+ * providers/local/camel-maildir-store.c (camel_folder_info_new):
+ take url argument directly, fixes a memleak.
+ (camel_folder_info_new): make the toplevel "." into "Inbox"
+ always.
+ (maildir_rename_folder): dont let users rename inbox.
+
+2004-06-27 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-url-scanner.c (camel_url_web_end): More fixes.
+
+2004-06-25 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-url-scanner.c (camel_url_web_end): Fixed to handle :pass
+ in proto://user:pass@host. Fixes bug #60104.
+
+2004-06-24 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/pop3/camel-pop3-store.c (connect_to_server): Error out
+ and set an exception if camel_pop3_engine_new() returns NULL
+ (which it can do now).
+
+ * providers/pop3/camel-pop3-engine.c (get_capabilities): No longer
+ reads the greeting.
+ (camel_pop3_engine_new): Reads the greeting itself and returns
+ NULL if an error occurs (like stupid braindamaged piece of shit
+ POP servers that spew debug prinfs).
+
+ * providers/local/camel-spool-folder.c (spool_lock): If we fail to
+ lock the folder, close the lockfd and reset it to -1. Fixes bug
+ #54680.
+
+2004-06-23 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_get_folder_info):
+ First LIST/LSUB the toplevel folder, and then LIST/LSUB the
+ subfolders (it needs to be 2 commands to work properly).
+ (imap4_delete_folder): CLOSE the folder we are about to DELETE if
+ it is currently SELECTED.
+
+ * providers/imap/camel-imap-provider.c (imap_url_equal): Same.
+
+ * providers/imap4/camel-imap4-provider.c (imap4_url_equal): Check
+ the protocol.
+
+ * providers/imap4/camel-imap4-store.c (imap4_build_folder_info):
+ Hide password, etc info in the fi->uri's.
+ (imap4_create_folder): Don't bother to use
+ imap4_get_folder_info(), we can construct the fi ourselves.
+ (imap4_delete_folder): Emit the folder_deleted signal and
+ construct an fi ourselves.
+ (imap4_subscribe_folder): Same.
+ (imap4_unsubscribe_folder): Same.
+
+ * providers/imap4/camel-imap4-provider.c: Specify that the
+ fragment is the path.
+
+2004-06-21 Christian Kellner <gicmo@xatom.net>
+
+ * camel-service.c (service_setv): Really set the path if tag is
+ CAMEL_SERVICE_PATH.
+
+2004-06-21 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-mime-filter-enriched.c (enriched_to_html): Fixed a number
+ of issues described in bug #49497.
+
+2004-06-18 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel.c (camel_shutdown): Call camel_mime_utils_shutdown() and
+ camel_operation_shutdown().
+
+ * camel-operation.c (camel_operation_shutdown): New function.
+
+ * camel-mime-utils.c (camel_mime_utils_shutdown): New function to
+ clean up the compiled regexes.
+
+ * camel-stream-buffer.c (set_vbuf): Need to re-init sbf->ptr and
+ sbf->end too, or we'll be sorrryy!
+
+2004-06-18 Not Zed <NotZed@Ximian.com>
+
+ * camel-exception.h (CAMEL_EXCEPTION_INITIALISER): setup for
+ static inititialisation.
+
+2004-06-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (uidset_init): init
+ tail->last to (guint32) -1, so that index = tail->last + 1 will
+ start at 0 at the top of uidset_add() :-)
+
+2004-06-17 Rodney Dawes <dobey@novell.com>
+
+ * camel-mime-filter-tohtml.c: Add support for the webcal, callto, and
+ h323 URIs when we get them in mails
+
+2004-06-17 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-mime-filter-tohtml.c: Don't foolishly unmunge From_
+ lines. First off, we don't even know if our input stream came from
+ an mbox file and secondly, the ">From " may have been intentional
+ by the author. We Just Don't Know (tm).
+
+2004-06-15 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_build_folder_info):
+ Make sure we have elements in the array, if not then we're done
+ (return a NULL fi).
+
+2004-06-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (imap4_get_uid_set): Fixed
+ to work properly. It was getting ranges wrong before sometimes
+ which was making me lose mail! Ugh.
+
+ Thanks to Christian Kellner for pointing out these bugs (and
+ submitting the original patch for service_setv)
+
+ * camel-service.c (service_setv): Don't use (tag &
+ CAMEL_ARG_IGNORE) to determine if we should ignore this
+ tag. CAMEL_ARG_IGNORE is not a bit flag.
+
+ * providers/imap/camel-imap-store.c (imap_setv): Don't use (tag &
+ CAMEL_ARG_IGNORE) to determine if we should ignore this
+ tag. CAMEL_ARG_IGNORE is not a bit flag.
+
+2004-06-16 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (imap4_refresh_info): Only
+ force a re-update of all FLAGS if this folder wasn't in the
+ SELECTED state. Otherwise, simply send a NOOP.
+
+ * providers/imap4/camel-imap4-summary.c: Added a 'first' member to
+ the imap_fetch_all_t struct so we can use that as a base offset in
+ our GPtrArray, allowing us to limit resource consumption which
+ could otherwise get quite large. Also added a ChangeInfo member
+ that was needed for changes to untagged_fetch_all().
+ (imap4_fetch_all_add): Use fetch->first as a base offset and
+ change int i to guint32 i. Also updated to sue the fetch->changes.
+ (imap4_fetch_all_update): Same.
+ (untagged_fetch_all): Same - this is where it is really valuable,
+ since we can avoid adding elements to the GPtrArray that we won't
+ even use. Also needed to change code a big in case index <
+ fetch->first (which could happen if a server notified us of a
+ FLAGS change for a message we didn't request info about).
+ (imap4_fetch_all_free): Free the ChangeInfo.
+ (imap4_summary_fetch_all): Init fetch->changes and fetch->first.
+ (imap4_summary_fetch_flags): Same.
+ (camel_imap4_summary_flush_updates): Only request envelope info if
+ first <= summary->exists. Avoids needless queries.
+ (info_uid_sort): #if 0'd
+ (camel_imap4_summary_flush_updates): No need to sort the summary -
+ this should never have been needed. I can't remember why I did
+ this...
+
+2004-06-15 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_verify): Use
+ camel_multipart_signed_get_content_stream() rather than getting
+ the first part and canonicalising it ourselves. Fixes bug #60159.
+
+ * providers/imap4/camel-imap4-stream.c (camel_imap4_stream_init):
+ Init have_unget to FALSE. Don't set unget to NULL, it's no longer
+ a pointer.
+ (camel_imap4_stream_finalize): No need to g_free() unget anymore.
+ (camel_imap4_stream_next_token): Check have_unget rather than
+ unget != NULL. Set have_unget to FALSE if we get an unget'd token.
+ (camel_imap4_stream_unget_token): Don't malloc space for an unget
+ token. The unget token is no longer a pointer.
+
+2004-06-14 Not Zed <NotZed@Ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (smtp_data): use
+ g_ascii_strcasecmp.
+ (smtp_connect): do not re-helo after we've authenticated. This
+ was a misreading of the spec. We must only re-helo after a
+ transport layer negotiation.
+
+2004-06-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_noop): Flush summary
+ updates for the currently selected folder.
+ (imap4_noop): Sync the currently selected folder before sending
+ NOOP.
+
+ * providers/imap4/camel-imap4-summary.c
+ (camel_imap4_summary_set_exists): Don't bother with
+ exists_changed. We don't need it afterall.
+ (camel_imap4_summary_flush_updates): Instead of updating flags if
+ update_flags or exists_changed is set, only bother if update_flags
+ is set or if exists is smaller than the summary count (since
+ updating flags is also sueful for determining which messages have
+ been removed).
+
+2004-06-11 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-folder.c (imap4_refresh_info): Force
+ updating of the emsage flags (normally this only happens if
+ something has changed that warrants rescanning them).
+
+2004-06-11 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (untagged_fetch): Handle
+ getting FLAGS even though we didn't request it (server can send us
+ FLAGS info if another client changed them recently, for
+ example). Also fixed to handle the fact that not every bit of info
+ has to be in a single untagged FETCH response - it may come in
+ several untagged responses.
+
+ * providers/imap4/camel-imap4-summary.c (envelope_decode_address):
+ Decode the email address name token.
+ (envelope_decode_nstring): rfc2047 decode strings if requested.
+ (decode_envelope): Request that the subject string be rfc2047
+ decoded.
+
+2004-06-11 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-maildir-store.c (get_folder_info): if we
+ scan from "" or top == NULL, then we really want to scan from "."
+ instead.
+
+ * camel-url.c (camel_url_new_with_base): don't check the for a
+ character after the # before extracting the fragment. An empty
+ fragment is still allowed and # should never be added to the path.
+ See Rfc1808 2.4.1.
+
+2004-06-11 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-summary.c
+ (camel_imap4_summary_flush_updates): Don't bother scanning summary
+ info if EXISTS was 0.
+ (camel_imap4_summary_set_uidvalidity): Emit the folder_changed
+ event after clearing the summary.
+ (camel_imap4_summary_expunge): Emit the folder_changed event after
+ removing the message from the summary.
+ (camel_imap4_summary_set_exists): Only set exists_changed if the
+ new and old exists values are different.
+ (imap4_fetch_all_add): Emit a folder_changed signal if any new
+ info's were added.
+ (imap4_fetch_all_update): Emit a folder_changed event if any uids
+ were removed or otherwise updated.
+ (camel_imap4_summary_expunge): Use seqid-1 to determine the actual
+ summary index.
+ (camel_imap4_summary_flush_updates): Added some logic to
+ distinguish between EXISTS value changing because it changed and
+ because messages got expunged.
+
+ * providers/imap4/camel-imap4-folder.c (imap4_sync): Flush updates
+ after an EXPUNGE and don't unset expunge if we didn't delete
+ anything (the logic was wrong anyway).
+ (imap4_refresh_info): Implemented.
+
+2004-06-10 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-engine.c
+ (camel_imap4_engine_handle_untagged_1): Don't always try and parse
+ a RESP-CODE in the BYE case as the RESP-CODE is optional.
+
+2004-06-10 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_build_folder_info):
+ If flags does not include FOLDER_INFO_FAST, get the total/unread
+ counts for the folder-info.
+
+ * providers/imap4/camel-imap4-engine.c (engine_parse_status):
+ Removed.
+ (camel_imap4_engine_handle_untagged_1): Don't handle untagged
+ STATUS responses anymore. Let the STATUS requestor handle them
+ instead.
+
+ * providers/imap4/camel-imap4-utils.c
+ (camel_imap4_untagged_status): New function to parse untagged
+ status events.
+
+2004-06-10 Not Zed <NotZed@Ximian.com>
+
+ * camel-filter-driver.c (camel_filter_driver_filter_message): add
+ some :filter debug.
+ (open_folder): only ignore the get_folder exception if we have a
+ default folder, otherwise don't. See #59727.
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_command): move the
+ stream based checking into the loop, after we connect. Fixes a
+ crash.
+
+2004-06-09 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-gpg-context.c (gpg_verify): Fixed a case where it was
+ possible to double-free the gpg context.
+ (gpg_verify): If we don't have a public key, then the signature is
+ just BAD always.
+
+2004-06-09 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_verify): Don't assign trust to be
+ UNKNOWN if gpg sent us a NODATA status.
+
+2004-06-07 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-folder.c (camel_imap4_folder_new):
+ Load the entire summary, not just the summary header. This way
+ when the user opens the folder, we don't do a complete re-sync
+ with the server unnecessarily.
+
+ * providers/imap4/camel-imap4-summary.c (untagged_fetch_all): New
+ info's always have a uid of "", so don't checkagainst NULL.
+
+2004-06-06 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_rename_folder):
+ Implemented, mostly. Still need to update state on the renamed
+ folder object if instantiated and rename the on-disk cache
+ directory.
+
+ * providers/imap4/camel-imap4-folder.c
+ (camel_imap4_folder_finalize): Free the cachedir.
+ (camel_imap4_folder_new): Init the cachedir and load the saved
+ summary before updating it against the server summary.
+
+ * providers/imap4/camel-imap4-store.c
+ (camel_imap4_store_finalize): Free the storage_path.
+ (imap4_construct): Init the storage_path.
+
+2004-06-04 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap4/camel-imap4-folder.c (camel_imap4_folder_new):
+ Initialise the folder->summary and force an update of the message
+ info cache by selecting the folder and flushing the updates to the
+ imap4 summary object.
+
+ * providers/imap4/camel-imap4-store.c (imap4_get_folder_info):
+ Lock the connect_lock before we check if the engine is NULL and
+ after we connect (assuming we need to), initialise the engine
+ pointer again.
+ (imap4_get_folder_info): Doh. Need to escape the "'s in the LIST
+ command string.
+ (imap4_get_folder): Same.
+
+ * camel-stream-buffer.c (stream_flush): Fixed to work
+ properly. After we've flushed the buffer, we want to set sbf->ptr
+ to sbf->buf, we don't want to do sbf->ptr += written, that'll just
+ corrupt our next write.
+
+ * providers/imap4/camel-imap4-store.c (connect_to_server_wrapper):
+ Duh. If the user doesn't care about SSL/TLS - use USE_SSL_NEVER,
+ not USE_SSL_ALWAYS.
+
+ * camel-tcp-stream-ssl.c (stream_flush): Always just return 0,
+ don't try to PR_Sync() - fsync on a socket causes an error.
+
+ * providers/imap4/camel-imap4-command.c
+ (camel_imap4_command_step): Set exceptions when write/flush fail.
+
+ * providers/imap4/camel-imap4-engine.c
+ (camel_imap4_engine_take_stream): Set an exception in the case
+ where we get an unexpected greeting from the server.
+
+ * providers/imap4/camel-imap4-store.c (imap4_create_folder):
+ store->dir_sep no longer exists, so query the engine for the
+ directory separator for the parent_folder.
+ (imap4_build_folder_info): CamelFolderInfo no longer has a path
+ component.
+
+2004-06-03 Not Zed <NotZed@Ximian.com>
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_command):
+ disconnect if we get an io error or user cancellation.
+ (nntp_disconnect_online): reset current folder.
+ (connect_to_server): and here too just to make sure.
+
+ * providers/nntp/camel-nntp-folder.c (nntp_folder_sync_online):
+ only save the summary, don't update from server, thats what
+ refresh info does.
+ (nntp_folder_download_message): fix exception handling.
+ (nntp_folder_cache_message): same.
+ (nntp_folder_get_message): ditto, plus major cleanup.
+ (nntp_folder_download_message): take combined uid so it can cache
+ and lookup properly. duh.
+
+ * providers/nntp/camel-nntp-store.c
+ (nntp_store_get_subscribed_folder_info): if not fast, then open
+ the folder, and update it. Yeah i've given up trying to worry
+ about performance vs usability.
+
+ * providers/nntp/camel-nntp-summary.c (camel_nntp_summary_check):
+ update the storesummary if we update the folder summary. Hmm,
+ isn't duplicated data meant to be a bad thing? :P
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_store_set_folder):
+ removed, now handled by nntp_command.
+ (nntp_connected): removed, now handled by nntp_command.
+
+ * camel-string-utils.c (camel_tolower): added ascii to-lower
+ function.
+ (camel_toupper): and upper, for completeness.
+
+ * camel-store-summary.c (CAMEL_STORE_SUMMARY_VERSION): bumped file
+ version by 1. This is a mess, version 1 files treated the
+ bitfield 'flags' with bit number values not bits. Messy.
+
+ * providers/nntp/camel-nntp-store-summary.c (store_info_save):
+ write last/first count.
+ (CAMEL_NNTP_STORE_SUMMARY_VERSION): bump version to 1.
+ (store_info_load): if we're loading >= version 1, then load
+ last/first counts.
+
+ * providers/nntp/camel-nntp-store.c
+ (nntp_store_get_folder_info_all): pass the whole line to
+ store_info_from_line, dont strip last/first info.
+ (nntp_store_info_update): renamed from info_new_from_line. only
+ add if not present. handle updates, try and handle unread counts
+ and readonly status.
+
+2004-06-02 Not Zed <NotZed@Ximian.com>
+
+ * providers/nntp/camel-nntp-store.c: setup xover once we've
+ started.
+
+ * providers/nntp/camel-nntp-summary.c: (xover_setup): moved to
+ nntp store.
+
+ * providers/nntp/camel-nntp-folder.c (folder_check)
+ (folder_check_free, camel_nntp_folder_new): remove async summary
+ stuff.
+
+ * providers/nntp/camel-nntp-store.c (camel_nntp_command): take
+ exception argument again, and folder argument. do retry logic and
+ auth logic differently.
+ (camel_nntp_raw_command): raw command interface, dont try
+ reconnect or anything fancy. pass i/o errors straight out, etc.
+ (camel_nntp_try_authenticate): change to return return codes &
+ take exception.
+
+ * providers/nntp/camel-nntp-summary.c (camel_nntp_summary_new):
+ just take path argument.
+ (camel_nntp_summary_check): take a store, and a folder name.
+ (add_range_head, add_range_xover): remove the time based update
+ events, they never had any effect anyway. Take store argument.
+ (xover_setup): take store argument.
+
+ * camel-folder-search.c (search_match_threads): remove debug.
+
+2004-06-01 Not Zed <NotZed@Ximian.com>
+
+ ** A few fixes for better rfc compliance, and cleaner code.
+
+ * camel-mime-utils.c (header_encode_param): a bunch of logic
+ cleanups with new util functions.
+ (header_decode_init): setup a new type ATTR_CHAR, for
+ attribute-char.
+
+ * tests/misc/test2.c (main): new test for rfc2184 stuff.
+
+ * camel-mime-utils.c (header_convert): helper to convert between
+ charsets.
+ (rfc2184_decode): fix a bunch of logic problems and use the helper
+ above to simplify code.
+ (decode_param_token): removed, not needed.
+ (header_decode_rfc2184_param): removed, not needed.
+ (header_decode_param): removed, not needed. ugh.
+ (header_decode_param_list): completely rewritten, hence lack of
+ need of above.
+
+2004-05-31 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-filter-gzip.c (camel_mime_filter_gzip_finalize):
+ Don't leak the zlib stream internals.
+
+2004-05-27 Jeffrey Stedfast <fejj@novell.com>
+
+ Fixes bug #59191.
+
+ * providers/imap/camel-imap-store.c (camel_imap_store_readline):
+ Don't override the exception with g_strerror() since presumably
+ camel_imap_store_is_connected() will have set that for us (and
+ will have a much more accurate exception anyway).
+
+2004-05-26 Dan Winship <danw@novell.com>
+
+ * camel-store.c (camel_store_get_trash): If the store is not a
+ vtrash store, just invoke the virtual method. (In particular,
+ don't assume that the trash folder's name is CAMEL_VTRASH_NAME).
+ If it is a vtrash store, just let camel_store_get_folder() do the
+ work since it's duplicated there anyway. #57114
+ (camel_store_get_junk): Likewise.
+
+2004-05-25 Sivaiah nallagatla <snallagatla@novell.com>
+ * providers/groupwise/came;-gw-listener.c (add_addressbook_sources):
+ set port property on e-source
+
+2004-05-25 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c (lookup_account_info):
+ return NULL when there is no existing gw account with same uid
+
+2004-05-25 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c (camel_folder_info_build_path): removed.
+
+ * camel-store.h (CamelFolderInfo): removed 'path', fixed all
+ callers.
+ (struct _CamelStore): removed 'dir_sep' fixed all uses.
+
+2004-05-25 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c (add_addressbook_sources)
+ (add_calendar_tasks_sources) : remove /soap part from uri.
+ make port as a e-source property instead of putting it in uri
+ to amek the uris same as that of mail for password sharing
+
+2004-05-24 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-utils.c (camel_header_newsgroups_decode):
+ fix/rearrange logic. It was using the wrong working pointer.
+
+2004-05-24 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * camel-provider.h: Added CAMEL_PROVIDER_CONF_HIDDEN to
+ conf item type enum. This is used by groupwise provider
+
+ * providers/groupwise/camel-groupwise-provider.c: Added
+ CAMEL_PROVIDER_CONF_HIDDEN to groupwise_conf_entries to pass the
+ auth-domain value
+
+ * providers/imap/camel-imap-store.c (imap_auth_loop): Read the
+ auth-domain property from imap url and pass it camel sesstion apis
+
+2004-05-22 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c: oops, forgot folder_created. Removed locking, the
+ object bag serialises stuff for us.
+ (camel_store_get_junk): same.
+
+ ** Another unread count bug, #58814.
+
+ * camel-store.c (camel_store_class_init): added a folder_opened
+ event.
+ (camel_store_get_folder): emit a folder_opened event whenever we
+ [re] open the physical folder.
+
+2004-05-21 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap/camel-imap-store.c (connect_to_server): Added a
+ workaround for broken IMAP. Hopefully by forcing only the IMAP4
+ command subset, we can work around their suckage.
+
+2004-05-21 Not Zed <NotZed@Ximian.com>
+
+ * camel-session.c (camel_session_get_password): added a 'domain'
+ argument, and rearragned arguments to be prettier and more
+ consistent. Fixed all callers.
+ (camel_session_forget_password): added a domain argument. Fixed
+ all callers.
+
+ ** See #58376.
+
+ * camel-folder.c (set_message_flags): if system flags change, then
+ don't trigger a folder changed event.
+
+ * camel-folder-summary.h (CAMEL_MESSAGE_SYSTEM_MASK): added this
+ to indicate which flags are internal/apps not interested in.
+
+ * camel-folder.c (filter_free): rearrange and use some helpers.
+ (folder_changed): if we're frozen, dont go firing off threads to
+ do any processing on each change, wait until we're called
+ unfrozen. Slight code rearragnement.
+ (filter_filter): add progress to junk learn/unlearn, and separate
+ them.
+
+2004-05-19 Suresh Chandrasekharan <suresh.chandrasekharan@sun.com>
+
+ Fix for #58738 ja_JP.UTF-8: Evolution crashes when certain
+ ASCII/non-ASCII combination is used in mail subject
+
+ * camel-mime-utils.c (camel_header_encode_string) Use
+ camel_mime_is_lwsp for determining word separators,
+ according to rfc822 (which's also same for rfc2047).
+ g_unichar_isspace as word separator is illegal.
+
+2004-05-20 Jeffrey Stedfast <fejj@novell.com>
+
+ Fixes bug #42295 and the infinite loop part of bug #58766 (these 2
+ bugs are almost identical, except the server responses are broken
+ in different ways).
+
+ * providers/imap/camel-imap-folder.c (imap_update_summary): Remove
+ the kludge to re-SELECT the folder to force a re-FETCH of
+ message-info's. This 1) doesn't do what it was meant to do and 2)
+ has a tendency to cause infinite loops with broken servers such as
+ Courier-IMAP.
+ (imap_update_summary): Rework the loop that adds messages to the
+ summary such that if we encounetr an error, we break out and set
+ an exception (we can keep the messages up to the point of failure,
+ but none after that because otherwise our uid-to-seqid mapping
+ would be inconsistant with that of the server and could
+ potentially cause data loss).
+
+2004-05-20 Not Zed <NotZed@Ximian.com>
+
+ * camel-exception.c (camel_exception_setv): re-arrange the code so
+ exception debug will print the expanded description.
+ (camel_exception_set): print exception debug.
+
+ * providers/pop3/camel-pop3-folder.c (pop3_get_message): same.
+
+ * providers/local/camel-mh-folder.c (mh_get_message) *
+ providers/local/camel-mbox-folder.c (mbox_get_message): *
+ providers/local/camel-maildir-folder.c (maildir_get_message):
+ Don't use INVALID_UID for errors which are more system related.
+ And sync up all the error messages.
+
+ * providers/nntp/camel-nntp-folder.c (nntp_folder_get_message):
+ oops, poke the right uid to get the article number.
+ (nntp_folder_cache_message): & here too. Somehow fixes #58700,
+ but i don't know why.
+
+2004-05-19 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder.c (transfer_message_to): copy the messageinfo
+ rather than reference the source folder's one, we poke its flags
+ variable if the message is deleted. And dont re-delete the
+ message if its already deleted.
+
+ * providers/nntp/camel-nntp-folder.c (nntp_folder_get_message):
+ use the article number instead of the messageid. Some servers are
+ just broken.
+ (nntp_folder_cache_message): same. See #58655.
+
+ * camel-smime-context.c (sm_verify_cmsg): import the certs as
+ UsageEmailRecipient as well as signer, and also save the certs
+ always.
+
+ ** See #58641.
+
+ * camel-vee-folder.c (vee_sync): don't rebuild auto-type vfolders.
+ they should always be consistent and it saves a lot of unecessary
+ work.
+
+ * camel-store.c (store_sync): we don't want to sync any vfolders
+ as part of the store sync call. its used for a different purpose
+ in vFolders. oh well its a hack.
+
+2004-05-18 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-maildir-store.c (fill_fi): do the same
+ load of mailbox if we're in slow mode as we did for maildir.
+ #58294.
+
+ * camel-disco-folder.c (disco_expunge_uids): check for NULL
+ implementation before calling it.
+ (disco_sync): similar. Fixes crash #58278.
+
+2004-05-17 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-folder-search.c (search_match_threads): Fixed a string type-o.
+
+ * camel-smime-context.c (sm_verify_cmsg): Fixed some spelling mistakes.
+
+ * camel-gpg-context.c (gpg_decrypt): If the encrypted block was
+ also signed, set the signature verification status on the Validity
+ structure as well.
+
+2004-05-17 Not Zed <NotZed@Ximian.com>
+
+ ** Bug #56050.
+
+ * providers/imap/camel-imap-store.c (imap_get_trash)
+ (imap_get_junk): similar to below.
+
+ * providers/local/camel-local-store.c (local_get_trash)
+ (local_get_junk): set state file on trash/junk to something we know
+ about.
+
+2004-05-17 Not Zed <NotZed@Ximian.com>
+ * providers/local/camel-mh-store.c (fill_fi): if we have no folder
+ in-memory, load it if we're not doing it fast to get really up to
+ date unread counts. #57616.
+
+2004-05-14 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c (camel_store_create_folder): don't allow creation
+ of Trash or Junk folders.
+ (camel_store_rename_folder): similar for rename. #57250.
+
+2004-05-13 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder-summary.c (summary_build_content_info): set secure
+ flag if we have a known security type.
+ (flag_names[]): Added secure bit.
+ (summary_build_content_info_message): set secure flag as
+ appropriate.
+ (summary_build_content_info_message): dont set attachments for
+ simple types (non text), only base it on multipart/* stuff.
+ (summary_build_content_info): same.
+
+ * camel-folder-summary.h: added SECURE flag. Indicates signed or
+ encrypted content.
+
+2004-05-12 Jeffrey Stedfast <fejj@novell.com>
+
+ * providers/imap/camel-imap-folder.c (camel_imap_folder_selected):
+ Ignore PERMANENTFLAGS if it gives us an empty set. Works around
+ broken IMAP servers like the one in bug #58355.
+
+2004-05-12 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder-search.c (search_threads): changed to match_threads.
+ (camel_folder_search_search): remove thread matching stuff from here.
+
+2004-05-11 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-smime-context.c (sm_signing_cmsmessage): Fixed a
+ type-o. Fixes bug #58348.
+
+2004-05-10 Jeffrey Stedfast <fejj@novell.com>
+
+ * camel-mime-filter-gzip.[c,h]: New class for zipping/unzipping
+ gzip streams.
+
+ * camel-mime-filter-yenc.[c,h]: New class for encoding/decoding
+ the crack known as YEncode.
+
+2004-05-07 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder-thread.c (thread_summary): properly set the parent
+ nodes for the re-parented phantom-node children. Wasn't that fun
+ to debug!
+
+ * camel-folder-thread.h: make order and re bitfields, saves 4
+ bytes/node.
+
+2004-05-06 Not Zed <NotZed@Ximian.com>
+
+ * camel-digest-folder.c (digest_search_by_expression)
+ (digest_search_by_uids):
+ * providers/nntp/camel-nntp-folder.c (nntp_folder_search_by_expression)
+ (nntp_folder_search_by_uids):
+ * providers/imap/camel-imap-folder.c (imap_search_by_expression)
+ (imap_search_by_uids):
+ * providers/local/camel-local-folder.c (local_search_by_expression)
+ (local_search_by_uids): use camel_folder_search_search & some minor cleanups.
+
+ * camel-folder-search.c (search_threads): keep track of the match
+ threads option for this search.
+ (camel_folder_search_match_expression): Removed, not used anymore.
+ (camel_folder_search_search): new api entry point for searching, a
+ bit easier to use and needed for thread matching.
+
+ * camel-folder-search.c (camel_folder_search_search): new search
+ api entry point, take a full summary and optionally a subset of
+ uids to match against.
+ (search_match_all): use the uids' passed in to only search a
+ subset of uid's.
+
+ * providers/imap/camel-imap-store.c (connect_to_server): set
+ nodelay and keepalive on the socket.
+
+ * camel-file-utils.c (camel_read): put a timeout on the select.
+ Logic shuffle to match the ssl stuff.
+ (camel_write): Similar.
+
+ * camel-tcp-stream-ssl.c (stream_connect): remove timeout, use
+ CONNECT_TIMEOUT directly.
+ (stream_read): put a timeout on the poll. IO_TIMEOUT. And a
+ little logic shuffle.
+ (stream_write): similar.
+ (CONNECT_TIMEOUT): make this 4 minutes === tcp-raw timeout.
+
+2004-05-05 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-folder.c (get_message_simple): dont
+ set X-Evolution-Source here anymore, set in caller.
+ (imap_get_message): move the busted server get into the retry
+ loop. only leave the simple cache test outside of it.
+
+2004-05-04 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-session.h: Get rid of #ifdef ENABLE_THREADS stuff, that
+ was done away with a while back and is causing problems for 3rd
+ parties trying to use camel unless they explicityly #define
+ ENABLE_THREADS in their code.
+
+2004-05-04 Christian Kellner <gicmo@xatom.net>
+
+ * providers/groupwise/camel-gw-listener.h: Fixed typo.
+
+2004-05-04 Not Zed <NotZed@Ximian.com>
+
+ ** See #57979.
+
+ * camel-vee-folder.c (subfolder_renamed_update): when the
+ subfolder gets renamed, remove all the old uid's and add the new
+ ones (since the uid is based on the subordinate folder name).
+ (subfolder_renamed): listen to renamed folder events.
+
+ * providers/local/camel-mbox-store.c (get_folder_info): if we're
+ getting a single folder with no children, make sure we fill the
+ counts out. Fixes some rename strangeness.
+
+ * camel-vee-folder.c (camel_vee_folder_add_folder): hook onto the
+ folder renamed signal.
+ (camel_vee_folder_finalise): unhook folder_renamed signal.
+ (camel_vee_folder_remove_folder): same.
+
+2004-05-03 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #57881.
+
+ * camel-vee-folder.c (camel_vee_folder_add_folder): use the
+ folder's change log and frozen count, not our copy.
+ (vee_thaw): dont maintain our frozen count.
+ (vee_freeze): same.
+ (folder_changed_change): if we get changed messages that dont
+ match, make sure they're also propagated as a change too.
+
+ * camel-private.h: remove the freeze_count from
+ camelveefolderprivate. We already have that in the camel folder
+ private.
+
+2004-04-30 Priit Laes <amd@tt.ee>
+
+ * providers/nntp/camel-nntp-summary.c: Fix typo. #53466.
+
+2004-04-28 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c: Change the UID_SET_LIMIT to
+ 768 (something <1000 octets as suggested by rfc2683). Fixes bug
+ #57389.
+
+ * camel-smime-context.c: Mark exception strings for translation
+ and fixed a spelling mistake.
+
+2004-04-27 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #57659.
+
+ * camel-vee-store.c (vee_get_folder_info): translate Unmatched in
+ the folder display name.
+
+ * camel-store.c (add_special_info): dont translate full_name or
+ path, only do that for name.
+
+2004-04-23 Sarfraaz Ahmed <asarfraaz@novell.com>
+
+ * camel-provider.h: Added HAS_LICENSE flag for allowing camel
+ providers to display license file.
+
+2004-04-22 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_expunge_uids_online)
+ (imap_expunge_uids_resyncing): Send a flag list rather than
+ \Deleted by itself. See #57381.
+
+2004-04-21 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (imap4_sync_changes): Don't
+ bother doing any work if perm_flags is 0.
+
+2004-04-21 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/Makefile.am: added CAMEL_LIBS to LIBADD
+
+ * providers/groupwise/camel-gw-listener.c
+ (get_addressbook_names_from_server): added new function to get
+ addres book names from server.
+ (add_addressbook_sources) (modify_addressbook_sources) : changed
+ the implementation to use address book returned form above call
+ while creating e-sources
+
+2004-04-21 Not Zed <NotZed@Ximian.com>
+
+ * providers/nntp/camel-nntp-folder.c (camel_nntp_folder_new): set
+ the meta data file on nntp folders.
+
+ * camel-disco-folder.c (disco_refresh_info_online): implement
+ dummy virtual method.
+
+ * providers/nntp/camel-nntp-folder.c
+ (nntp_folder_refresh_info_online): implement. Fixes #57280.
+
+2004-04-19 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (untagged_fetch): Fixed to
+ not expect ]'s as part of the BODY atom token.
+
+2004-04-16 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-vee-store.c (change_folder): (flags & 0) will never be
+ true. Fixes bug #56982.
+
+2004-04-15 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-utils.c: Changed imap_atom_specials[]
+ to not treat ']' as an atom char (as per rfc3501). Fixes bug
+ #50985.
+
+2004-04-14 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-store.c
+ (imap_check_folder_still_extant): Use %F instead of %S so that the
+ folder name gets properly converted from using '/' path delimeters
+ to whatever the native character the server uses. Should fix bug
+ #56715.
+
+2004-04-14 Not Zed <NotZed@Ximian.com>
+
+ * camel-disco-store.c (set_status): do offline mail syncing (only
+ for open folders so far). If we fail doing syncing or store sync,
+ don't abort.
+
+2004-04-13 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-store.c (get_folder_online):
+ Rearranged some error checking code to fix bug #56405.
+
+2004-04-13 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder.c (folder_getv): implement the new counts, and get
+ them all atomically so they're only calculated once and can return
+ consistent results.
+
+ * camel-folder.h: Added CAMEL_FOLDER_DELETED, CAMEL_FOLDER_JUNKED,
+ and CAMEL_FOLDER_VISIBLE args, to support client display of
+ various values.
+
+2004-04-13 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-folder.h (camel_folder_delete_message): Fix NotZed's fix
+ to not mark messages as unseen. Fixes bug #56879.
+
+2004-04-13 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c
+ (add_addressbook_sources): change the auth type string from
+ Password to plain/password
+
+2004-04-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fix for bug #56878.
+
+ * camel-gpg-context.c (gpg_verify): Don't rely on the exit code of
+ gpg, we already save enough state to decide if the sig is valid
+ without it. Modified to only set BAD if gpg->validsig and
+ gpg->nopubkey are both FALSE. If we get a NO_PUBKEY status
+ message, then it simply means that the the sender could not be
+ verified.
+ (gpg_ctx_parse_status): Listen for NO_PUBKEY status messages.
+
+2004-04-11 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c
+ (add_addressbook_sources) (modify_addressbook_sources) : add
+ bookname to uri
+
+2004-04-09 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-provider.c: Fix capitalisation of the
+ "mailcheck" section title and move it to the top (so it matches
+ with the UI).
+
+ * providers/imap/camel-imap-folder.c (imap_get_message): Fetch the
+ entire message in one fell swoop even if the message size is
+ larger than 5k *if* the message is a single part. Fixes bug #56686.
+
+2004-04-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-store.c (get_folder_online): Changed
+ (!flags & _CREATE) to (!(flags & _CREATE))
+ (get_folder_online): Do what create_folder() does and if the
+ parent folder has \NoInferiors set but contains no messages,
+ delete the parent folder and recreate it before creating the child
+ folder. Fixes bug #56651.
+
+2004-04-08 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder.h (camel_folder_delete_message): always set the
+ seen flag when we delete a message. I demand that this may, or
+ may not, fix #56549.
+
+ * camel-folder.c: include camel-debug.h
+ (camel_folder_set_message_flags): fixed the doco slightly (well
+ reversed the flag and set explanation) and give an example.
+
+ * providers/local/camel-mbox-folder.c
+ (mbox_set_message_user_flag): message changed to folder_changed.
+ (mbox_set_message_user_tag): ditto.
+
+ * camel-vee-folder.c (camel_vee_folder_finalise): dont hook onto
+ message_changed.
+ (camel_vee_folder_add_folder): or unhook.
+ (camel_vee_folder_remove_folder): "
+ (message_changed): or proxy.
+
+ * camel-folder.c (camel_folder_class_init): removed the
+ message_changed event - its redundant, and covered by
+ folder_changed, and just makes life difficult for everything using
+ message_changed/folder_changed (and nothing uses it anyway).
+ (message_changed): removed.
+ (set_message_user_flag): emit a folder_changed event instead of
+ message changed.
+ (set_message_user_tag): "
+ (set_message_flags): "
+
+ * camel-object.h: revered the CAMEL_OBJECT_TYPE change, it should
+ be a global variable access.
+
+2004-04-06 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c: Added mutex locking where
+ appropriate.
+
+ * providers/imap4/camel-imap4-store.c: Added mutex locking where
+ appropriate.
+
+ * camel-folder-summary.h (CAMEL_FOLDER_SUMMARY_TYPE): Defined.
+
+ * camel-object.h (CAMEL_OBJECT_TYPE): Fixed.
+
+ * providers/imap4/camel-imap4-folder.c
+ (camel_imap4_folder_utf7_name): Implemented.
+
+ * providers/imap4/camel-imap4-store.c (imap4_build_folder_info):
+ Use camel_folder_info_build() to build the folder-info tree.
+
+2004-04-06 Not Zed <NotZed@Ximian.com>
+
+ * camel-debug.c: #if 0 out the sys/debugreg stuff.
+
+ ** See bug #56110.
+
+ * providers/imap/camel-imap-folder.c (get_content): more debug!
+ (get_content): if we have no content-type header set on a sub-part
+ of a multipart/digest, then we need to set it to message/rfc822 as
+ in the multipart/digest rfc (2046 or so?).
+
+ * camel-folder.c (camel_folder_get_message): output this stuff as
+ folder debug.
+
+ * providers/imap/camel-imap-folder.c (imap_get_message): add some
+ imap:folder debug.
+ (get_content): get xx.TEXT rather than xx if we're from a message
+ parent part.
+
+ ** See bug #56464.
+
+ * camel-folder.c (camel_folder_transfer_messages_to): do not lock
+ the source here.
+ (transfer_message_to): call the main entry point for get message
+ and append message.
+
+ ** See bug #56050.
+
+ * camel-vee-store.c (vee_delete_folder): delete the state file if
+ it exists.
+
+ * camel-object.c (camel_object_state_write): create the parent dir
+ if we need to. Also spit a warning if we fail in the end.
+
+ * camel-vee-folder.c (camel_vee_folder_new): set the persistent
+ state file location.
+ (vee_sync): write the state file when we sync.
+
+2004-04-05 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c (get_folders): dont add
+ folders to folders_out here, only in get_folders_add_folders.
+
+ * camel-store.c (camel_folder_info_build): simplify 'list append'
+ since we have next pointer at the head of the struct.
+
+ * providers/imap/camel-imap-store.c (create_folder): fixed
+ "containes" spelling count.
+ (get_folder_online): "
+ (get_folders_add_folders): duh, add the folder info to the
+ folders_out array.
+
+2004-04-02 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-folder.c (camel_folder_get_deleted_message_count): New
+ function to get the deleted message count (used for Outbox count
+ which is total minus deleted).
+
+2004-04-02 Not Zed <NotZed@Ximian.com>
+
+ * camel-exception.c (w): turn this on, this should always be on,
+ it points to real bugs in the code.
+
+ * camel-folder-summary.c (summary_build_content_info): dont set
+ attachments if its a signature block.
+ (summary_build_content_info_message): same.
+
+2004-04-01 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-exception.c (w): Wrap annoying exeption warnings with w().
+
+2004-03-30 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-store.c (imap4_get_folder_info):
+ Partially implemented.
+ (imap4_get_folder): Implemented.
+
+2004-03-30 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (header_decode_param): Use
+ header_decode_text() rather than rfc2047_decode_word() to decode
+ the brokenly rfc2047 encoded param value string in case it is
+ actually made up of multiple rfc2047 encoded words. Thanks to
+ Andrei Nigmatulin for the fix.
+
+2004-03-30 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c (camel_store_get_folder_info): added some debug to
+ dump the whole folderinfo tree if store:folder_info is set.
+
+ * providers/imapp/camel-imapp-driver.c: #if 0 out some code, to
+ kill warnings.
+
+ * camel-url-scanner.c: include ctype.h for isspace (wonder if it
+ should use utf8 funcs?).
+
+2004-03-29 Not Zed <NotZed@Ximian.com>
+
+ ** See #56146.
+
+ * providers/imap/camel-imap-store.c (get_folders): check the
+ top-level folders list for duplicates as well.
+ (get_folders_add_folders): split out the folder return merging
+ code from get_folders. Absolute mess of crap to deal with more
+ busted servers.
+
+ * camel-debug.c (camel_debug_start, camel_debug_end): some helpers
+ to wrap debug output for atomicicity.
+
+2004-03-29 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap4-folder.c (camel_imap4_folder_new):
+ Implemented.
+
+ * providers/imap4/camel-imap4-engine.c (engine_parse_namespace):
+ If the namespace begins with "INBOX", canonicalise the INBOX
+ portion (ie, make it all caps).
+
+ * providers/imap4/camel-imap4-store.c (imap4_noop): Implemented.
+
+2004-03-29 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-store.c (get_folder_status): Updated
+ to actually parse the STATUS response into a list of item/value
+ pairs.
+ (create_folder): Updated for above change.
+ (get_folder_counts): Only call get_folder_status() once (we can
+ get both values with a single call now). Fixes a FIXME and might
+ also fix bug #55784.
+
+ * providers/local/camel-mbox-store.c (get_folder_info): Removed
+ debugging printfs.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): Since the folder was *just*
+ created, it shouldn't have any subfolders so set the
+ CAMEL_FOLDER_NOCHILDREN flag (altho, ideally, we wouldn't be
+ guessing these flags at all - rather we'd call get_folder_info()
+ or some such). Fixes bug #56010.
+
+2004-03-29 Radek Doulik <rodo@ximian.com>
+
+ * camel-mime-filter-tohtml.c (html_convert): close pre tag in case
+ we just flush. I am not sure if it's still worth to check for
+ inlen == 0 and handle it specially, but didn't want to make too
+ big changes.
+
+ Fixes #55817
+
+2004-03-29 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c (get_folder_counts): use
+ object_bag_peek instead of _get, since we dont want to clash/wait
+ for reservations. More #56045 related fixes.
+ (get_folder_counts): revert the lookup/hashtable stuff for the
+ folder, and use object_bag_peek.
+
+ * camel-object.c (camel_object_bag_peek): new method to get an
+ object bag entry without worrying about if its reserved or not.
+
+ * camel-gpg-context.c (gpg_verify): get the content-type off of
+ the multipart-signed, not its container. This seems wrong
+ ... but might fix #56084.
+
+ * providers/imap/camel-imap-store.c (get_folder_counts): remove
+ locking here, we're locked whne we enter.
+ (fill_fi): call refresh_info unlocked. More for #56045.
+
+2004-03-28 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap-folder.[c,h]: New source files
+ implementing the CamelFolder class for the new IMAP4
+ implementation.
+
+ * providers/imap4/camel-imap-summary.[c,h]: New source files
+ implementing the CamelFolderSummary class for the new IMAP4
+ implementation.
+
+2004-03-26 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_update_summary):
+ Reverted imap.web.de fix.
+
+2004-03-26 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-maildir-summary.c (flagbits[]): Added new
+ maildir flags D for draft and commented P for forwarded.
+
+ * providers/imap/camel-imap-store.c (get_folder_counts): Instead
+ of looking up the folder in the object bag which will handle
+ reservations and perhaps deadlock, just get the list of opened
+ folders and use them if they're available. Should fix #56045.
+
+2004-03-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap-engine.c
+ (camel_imap_engine_parse_resp_code): No longer need to split ']'
+ tokens from atom tokens due to a fixup in the ABNF grammar in
+ rfc3501.
+
+ * providers/imap4/camel-imap-specials.c: Changed ATOM_SPECIALS to
+ include ']' (this is an addition in rfc3501).
+
+ * providers/imap4/camel-imap-store.[c,h]: New Store class for
+ IMAP. Implemnted a bunch of but still got a ways to go.
+
+2004-03-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_update_summary): If the
+ server is imap.web.de, just ask for all the headers rather than
+ "HEADER.FIELDS.NOT (RECEIVED)". Actually, maybe we should always
+ just query for the entire header block?
+ (imap_get_message): If the server is brain-damaged (that's a
+ technical term), always fetch the message in whole, never bother
+ to try and fetch partial messages (Courier-IMAP gives us the wrong
+ BODY responses fairly often).
+
+ * providers/imap/camel-imap-store.c (connect_to_server): Set
+ store->braindamaged to TRUE if we find the string "Courier-IMAP"
+ in the greeting.
+
+2004-03-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fix for bug #55018.
+
+ * providers/imap/camel-imap-store.c (create_folder): Don't allow
+ the user to create folders with #, %, * or the directory separator
+ in the folder name (added the checks for %, * and #).
+ (get_folder_online): Add a check to make sure the folder name is
+ sane before sending a CREATE (ie. we want to allow getting of
+ folders with discouraged characters in them if they exist, but we
+ don't want to allow the user to create them).
+
+2004-03-25 Martyn Russell <ginxd@btopenworld.com>
+
+ * providers/smtp/camel-smtp-provider.c: Removed newline character
+ from the provider description
+
+2004-03-25 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-groupwise-provider.c: removed ldap
+ seetings and added a settng for SOAP port
+
+ * providers/groupwise/camel-gw-listner.h
+ (add_calendar_tasks_sources, remove_calendar_tasks_sources),
+ (modify_calendar_tasks_sources): read port number from url instead
+ of hardcoding. Also removed code for adding e-sources for ldap
+ address book and adding now e-sources for groupwise address book
+
+2004-03-24 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap4/camel-imap-engine.c (camel_imap_engine_literal):
+ New convenience wrapper function.
+ (engine_parse_status): Fixed to handle literal mailbox strings.
+
+ * providers/imap4/camel-imap-command.c (camel_imap_command_newv):
+ Changed how %L works - create the CamelIMAPLiteral for our caller
+ instead of expecting them to create it for us. We can autodetect
+ what type of object (stream vs data-wrapper) the literal is, so
+ it's trivial to do.
+
+2004-03-23 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_get_message): Reworked
+ the else bit to fix a bug where if we had the BODY structure, we simply
+ wouldn't try fetching the actual message.
+
+2004-03-23 Not Zed <NotZed@Ximian.com>
+
+ * camel-exception.c (camel_exception_setv): use camel debug to add
+ some debug here.
+
+ * camel.c (camel_init): call camel_debug_init().
+
+ * camel-debug.c (camel_debug_init, camel_debug): new util stuff
+ for extended debug options.
+
+ * providers/imap/camel-imap-folder.c (imap_get_message): if we're
+ supposed to be online, check we are online before proceeding.
+ Actually major restructure so we re-try the fetch a couple of
+ times first before failing. i.e. silent reconnect. See #55381.
+
+ * providers/imap/camel-imap-store.c (get_folder_info_online):
+ connect lock around this. was getting a race with mem corruption
+ otherwise.
+
+2004-03-22 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-utils.c (camel_header_newsgroups_decode)
+ (camel_header_newsgroups_free): decode newsgroups header into a
+ list of newsgroups.
+
+ ** See #55887.
+
+ * providers/nntp/camel-nntp-summary.c (camel_nntp_summary_check):
+ NOOP if we're offline.
+
+ * providers/nntp/camel-nntp-store.c (nntp_connected): spit a
+ warning if we try to do stuff whilst offline, rather than crash.
+
+2004-03-19 Not Zed <NotZed@Ximian.com>
+
+ * camel-disco-store.c (disco_connect): ref the diary before
+ replaying it. it could get unreffed during replay if there's an
+ error and we disconnect.
+
+ * camel-store.c (camel_store_get_folder): no longer use
+ folder_lock, we already have adequate serialisation code here or
+ below here. I hope.
+ (camel_store_get_folder_info): same here.
+
+ * providers/imap/camel-imap-store.h: remove async_thread thing.
+
+2004-03-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_get_message): Checking
+ that mi->content->type != NULL is no longer good enough to tell if
+ a ContentInfo is complete (ie. contains the parsed BODY
+ response). We need to check that th ContentInfo has children if
+ the part is a multipart of message/rfc822 part. Apparently Zucchi
+ didn't test his camel-folder-summary changes using IMAP.
+
+2004-03-17 Radek Doulik <rodo@ximian.com>
+
+ * camel-folder-summary.h: use 1<<30 for CAMEL_MESSAGE_JUNK_LEARN,
+ 1<<17 was already used by imap provider and maybe others
+
+2004-03-17 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-maildir-store.c: Reverted jeff's fix for
+ #55018, since it wasn't tested, and doesn't work. Wrote an
+ alternate implementation, and tested it at least once. Turns out
+ it was a one line error, it still wasn't tested. Oh well, its
+ rewritten now.
+
+ * See bug #55618.
+
+ * camel-disco-diary.c (camel_disco_diary_new): seek to the end of
+ the file after we open it. c99 apparently says the file merely
+ adds to the end of the file when you write, not that it is opened
+ and positioned at the end of the file (linux's man pages are out
+ of date).
+
+ * camel-folder-summary.c (content_info_new): setup the content
+ type as well, from the headers.
+
+ * providers/imap/camel-imap-summary.c
+ (camel_imap_summary_add_offline): copy size from the source info.
+
+2004-03-15 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-folder.c (folder_rename): Always use '/' to derive the
+ basename of the folder. folder->full_name is always the UNIX-path
+ evrsion of the folder name, no matter what the actual path
+ delimeter is on the underlying store.
+
+ * providers/imap/camel-imap-store.c (get_folder_online): If the
+ initial SELECT fails and CREATE is specified, clear the exception
+ before attempting to CREATE, this way we don't have an exception
+ set even if the CREATE passes. Fixes bug #55607.
+
+2004-03-16 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-message.c (camel_mime_message_init): rever previous
+ patch. We don't want to set the default mime/type, it'll break
+ stuff.
+
+2004-03-15 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-mbox-store.c (delete_folder): same as
+ below for path.
+
+ * providers/local/camel-local-store.c (delete_folder): NULL out
+ str before looking up the state file using it. Otherwise we
+ double-free str.
+
+ * camel-mime-parser.c (folder_scan_skip_line): we want to scan
+ till in-end-1. If we've been called we're either at the end of
+ data, or we know we have an end of line character within memory.
+ Another case in Bug #53355.
+
+ * providers/imap/camel-imap-folder.c (get_content, get_message):
+ set the mime-type field on the content the same way as
+ construct_from_stream does. Bug #55472.
+
+ * camel-mime-message.c (camel_mime_message_dump): utility function
+ to dump message content to stdout.
+ (camel_mime_message_init): default mime type to message/rfc822.
+
+ * camel.c (camel_init): change camel verbose debug to be an int, a
+ bitmask of debug options.
+
+ * camel-mime-utils.c (camel_header_location_decode): drop embedded
+ whitespace characters, and don't do unquoting, etc. See rfc2557
+ 4.4.2 and rfc2017 3.1.
+
+2004-03-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (smtp_set_exception): Now
+ takes an argument to specify whether disconnecting when respbuf is
+ NULL is safe (to prevent us from recursively disconnecting or
+ disconnecting during a connect).
+
+2004-03-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fix for bug #53497.
+
+ * providers/smtp/camel-smtp-transport.c (smtp_helo): Instead of
+ unreffing the streams, call camel_service_disconnect().
+ (smtp_mail): Same.
+ (smtp_rcpt): Same.
+ (smtp_data): Same.
+ (smtp_send_to): Ignore exceptions for smtp_rset(). Also, check
+ that we are connected before we try to send (in Evolution's
+ current usage scenario, this isn't a problem but in the future if
+ we ever try to fire off several messages via the same smtp
+ connection, it may be - especially if RSET failed during the
+ previous send).
+
+2004-03-12 Radek Doulik <rodo@ximian.com>
+
+ * camel-folder.c (folder_changed): clearn the learn bit only if
+ set.
+
+2004-03-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fixes for bug #55018.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): Use camel_url_to_string() here
+ too, so we properly encode the fragment.
+
+ * providers/local/camel-mbox-store.c (get_folder_info): Use
+ CamelURL to properly encode the fi->uri. Pass the CamelURL into
+ scan_dir() so that scan_dir() can re-use it (rather than having to
+ malloc/parse/free for each file/dir)
+ (scan_dir): Use camel_url_to_string().
+
+ * providers/local/camel-maildir-store.c (get_folder_info): Same as
+ mbox.
+ (scan_dir): Same as mbox. We also need to set the
+ CAMEL_FOLDER_NOSELECT flag if appropriate.
+
+ * providers/local/camel-mh-store.c (get_folder_info): Same as mbox
+ and maildir.
+ (folders_scan): Now takes a url argument which we pass off to
+ folder_info_new().
+ (recursive_scan): Same.
+ (folder_info_new): Use camel_url_to_string().
+
+2004-03-11 Radek Doulik <rodo@ximian.com>
+
+ * camel-folder.c (camel_folder_set_message_flags): watch for
+ setting JUNK flag, if JUNK_LEARN is not set as well then reset
+ JUNK_LEARN bit
+ (folder_changed): look for junk changes in uid_changed's messages,
+ if these changes request junk filter learning
+ (CAMEL_MESSAGE_JUNK_LEARN bit set) then prepare junk and nonjunk
+ uid arrays, clear CAMEL_MESSAGE_JUNK_LEARN bit so that we don't
+ process it again
+ (folder_changed): start filter thread if there's junk and/or
+ nonjunk arrays
+ (filter_filter): if junk/nonjunk arrays are non-NULL, call junk
+ filter report to learn junk/non-junk messages
+ (filter_free): free junk/nonjunk uids and arrays
+
+ * camel-folder-summary.h: added CAMEL_MESSAGE_JUNK_LEARN to
+ CamelMessageFlags, used when setting CAMEL_MESSAGE_JUNK flag to
+ say that we request junk plugin to learn that message as
+ junk/non-junk
+
+2004-03-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (connect_to_server): If
+ errno is EINTR, set USER_CANCEL instead of SERVICE_UNAVAILABLE or
+ whatever.
+ (smtp_helo): Same.
+ (smtp_auth): Same.
+ (smtp_mail): Same.
+ (smtp_rcpt): Same.
+ (smtp_data): Same.
+ (smtp_rset): Same.
+ (smtp_quit): Same.
+ (smtp_set_exception): Here too.
+ (smtp_auth): If the AUTH response code is not 334, then use
+ smtp_set_exception() to get the most accurate error report we can.
+
+2004-03-11 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-object.c (cobject_state_read): Sanity check that count is
+ <1024 and also use g_try_malloc so that we can recover if malloc
+ fails.
+
+2004-03-11 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c (no_such_folder): removed
+ this. not sure what it was doing there, a 1 line funciton used
+ once.
+ (get_folder_online): pass exception to camel_imap_command. if we
+ got a user cancel, pass it up. See #55388.
+ (hash_folder_name, compare_folder_name): more g_ascii_strcasecmp
+ stuff.
+
+2004-03-11 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-store.c (vee_get_folder_info): we need to add the
+ folderinfo always if we're recursive from top. Should fix #52965
+ and maybe the other vfolders not showing on startup bug.
+
+ * providers/imap/camel-imap-store.c (get_one_folder_offline):
+ (parse_list_response_as_folder_info): turn off NOINFERIORS always,
+ translate to nochildren.
+ (imap_store_refresh_folders): check we're updating an imap folder,
+ we could also have trash folders in the store too.
+
+2004-03-08 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-store.c (vee_get_folder_info): setup virtual/system
+ flags as appropriate.
+ (change_folder): setup flags properly.
+
+ * providers/nntp/camel-nntp-store.c
+ (nntp_store_get_subscribed_folder_info): mark all folders as
+ system folders.
+
+ * providers/local/camel-mh-store.c (fill_fi): add this to setup
+ folderinfo.
+ (folder_info_new): call fill_fi to fill unread/total.
+ (recursive_scan, folders_scan): ahh yeah, so wtf was i thinking,
+ store->flags != get_folder_info flags!!!!
+
+ * providers/local/camel-maildir-store.c (camel_folder_info_new):
+ remove unread count arg & setup total.
+ (fill_fi): setup total field.
+ (scan_dir): remove the code that checked the directory directly -
+ use fill_fi instead. It will more accurately reflect what you get
+ when you visit the folder.
+ (camel_folder_info_new): mark "." as a system folder.
+ (scan_dir): try to setup children/no children flags properly.
+
+ * providers/local/camel-mbox-store.c (fill_fi): setup total field.
+ (scan_dir): init total.
+ (get_folder_info): "
+
+2004-03-05 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c
+ (parse_list_response_as_folder_info): mark INBOX as a system
+ folder. Can't be renamed/deleted.
+ (fill_fi): setup total field.
+ (get_folder_counts): ditto.
+
+ * camel-store.c (add_special_info): set the system folder flag.
+
+ * camel-store.h: time to fix up the camelfolderinfo mess. fix
+ some member names, and add some type fields. Fixed all uses.
+
+2004-03-04 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #53355.
+
+ * providers/imap/camel-imap-folder.c (get_content): if the parent
+ isn't a message/rfc822 type, we don't want to get the section.TEXT
+ for multipart/signed, we just want to get section.
+
+2004-03-03 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-gw-listener.c
+ (add_calendar_tasks_sources): use "Calendar" and "Checklist" for the
+ folder names, instead of "Default".
+ (remove_calendar_tasks_sources): remove the correct folder.
+
+2004-03-03 Not Zed <NotZed@Ximian.com>
+
+ * camel-operation.c (camel_operation_uncancel): attempt at
+ uncancelling a cancelled operation.
+
+ * camel-stream-filter.c (do_write, do_write): fun dun diddley un
+ fun. Since we're writing a const buffer, we need to copy it
+ first. See #54937.
+
+2004-02-27 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #54755.
+
+ * camel-vtrash-folder.c (vtrash_append_message)
+ (vtrash_transfer_messages_to): error/fail out if the user tries to
+ copy messages to the trash.
+ (vtrash_transfer_messages_to): use the destination bit not the
+ source bit for moving messages to a vtrash folder.
+
+ * camel-gpg-context.c (gpg_ctx_parse_status): ignore NODATA
+ response, otherwise we abort in a meaningless way. See #52939.
+
+ * providers/imap/camel-imap-utils.c: use g_ascii_str[n]casecmp
+ everywhere.
+ * providers/imap/camel-imap-utils.c (imap_body_decode): fix the
+ sense of the nil check for the subtype of a mutlipart. See
+ #53355.
+
+2004-02-26 Not Zed <NotZed@Ximian.com>
+
+ * camel-session.c (camel_session_check_junk_for_imap)
+ (camel_session_set_check_junk_for_imap): removed.
+
+ * providers/imap/camel-imap-provider.c: Add filter_junk and
+ filter_junk_inbox options to the receive option page.
+
+ * providers/imap/camel-imap-store.c (imap_setv, imap_getv): handle
+ FILTER_JUNK and FILTER_JUNK_INBOX parameters.
+ (imap_setv): conver to switch rather than if statement.
+ (construct): handle url args for filter_junk and
+ filter_junk_inbox.
+
+ * providers/imap/camel-imap-folder.c (camel_imap_folder_new): Set
+ the folder's flags based on the stores junk settings.
+ (imap_update_summary): remove the test for
+ session_check_junk_for_imap, its handled per-store now.
+
+ * camel-folder.c (folder_changed): only check for FILTER_RECENT or
+ FILTER_JUNK to see if we need to do filtering.
+
+ * camel-folder.h (CAMEL_FOLDER_FILTER_JUNK): renamed from
+ CAMEL_FOLDER_SUPRESS_JUNK_TEST (and obviously inverted logic).
+
+2004-03-02 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-summary.c
+ (mbox_summary_encode_x_evolution): Overrides the parent method. We
+ don't want to encode user flags/tags or the size of the header
+ will change and force a complete rewrite of the mbox file.
+
+2004-02-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-filter-driver.c (camel_filter_driver_filter_folder): Free
+ the uids *after* reporting Complete, otherwise we get an FMR if
+ our caller didn't pass in the uids.
+
+ * camel-sasl-gssapi.c (gssapi_challenge): #ifdef out another
+ gss_release_buffer() call as this function causes memory
+ corruption if using Heimdal's implementation of Kerberos5. Yay
+ Heimdal.
+
+2004-02-25 Radek Doulik <rodo@ximian.com>
+
+ * camel-folder.c (get_unread_message_count): do not avoid junk
+ mails in unread count
+
+2004-02-25 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-summary.h (CAMEL_IMAP_MESSAGE_RECENT):
+ moved the RECNET flag into the folder area (bit 17-30).
+
+2004-02-24 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #53876.
+
+ * providers/imap/camel-imap-command.c (camel_imap_command): ref
+ the folder before unreffing store->current_folder, incase they're
+ the same. Do a select anyway.
+
+ * providers/imap/camel-imap-folder.c (imap_refresh_info): keep the
+ connect_lock for longer, imap_rescan for one assumes its locked.
+ Fixes a race selecting the folder for refresh.
+
+2004-02-24 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-stream-process.c: #include <signal.h>, we don't need limits.h
+
+ * camel-store.c (camel_store_folder_uri_equal): New function to do
+ what camel_store_uri_cmp() was supposed to do.
+
+2004-02-23 Rodney Dawes <dobey@ximian.com>
+
+ * providers/local/camel-mbox-store.c (scan_dir): If our folder has
+ a subdir, but no actual children, then we need to unset the flag for
+ CAMEL_FOLDER_CHILDREN
+
+ Fixes #54470
+
+2004-02-23 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-store.c (camel_store_uri_cmp): Removed. Useless/broken
+ function.
+
+2004-02-20 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-provider.c (camel_provider_list): Init list to NULL to
+ prevent the crash in bug #54574.
+
+2004-02-19 Chris Toshok <toshok@ximian.com>
+
+ * camel-smime-context.c: wrap this file with #ifdef ENABLE_SMIME
+ instead of #ifdef HAVE_NSS.
+
+2004-02-19 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-store.c (get_folder): Add sanity
+ checking to the folder name if we are going to create it, just
+ like we do in create_folder().
+
+2004-02-19 Not Zed <NotZed@Ximian.com>
+
+ * providers/*/camel-*-provider.c
+ (camel_provider_module_init): Fixes for api changes.
+
+ * camel-provider.c (camel_provider_load): no longer take session
+ argument. the providers are global resources.
+ (camel_provider_init): dont return anything anymore. (error?)
+ call from camel_init now. Use a recursive lock too.
+
+ * camel-session.c (camel_session_register_provider)
+ (camel_session_list_providers, camel_session_get_provider): Moved
+ to camel-provider, camel_provider_register/list/get.
+ (vee_provider): moved to camel-provider.c
+
+2004-02-18 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_verify): Use the trust to decide the
+ validity signature status. (Better way of solving yesterday's
+ problem)
+
+ * camel-cipher-context.h: Revert change from yesterday.
+
+2004-02-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_verify): Set the trust.
+
+ * camel-cipher-context.h: Add a trust metric to signatures.
+
+2004-02-17 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-store.c (vee_sync): implment, make sync a noop on vee
+ stores. Speeds up exit, so we don't try and sync and re-sync
+ folders multiple times.
+
+ ** See bug #53861.
+
+ * providers/nntp/camel-nntp-summary.c (add_range_xover): Fix from
+ Edd Dumbill <edd@usefulinc.com> to avoid aborting on irrelevently
+ truncated lines.
+
+2004-02-16 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #51045.
+
+ * providers/imap/camel-imap-store.c (fill_fi): similar to mbox
+ version.
+ (get_folder_counts): use fill_fi to try and get folder counts if
+ we're not doing the hard slog.
+ (get_one_folder_offline): use fill_fi to try to get folder counts
+ from open folders or summaries.
+
+ * providers/local/camel-maildir-store.c (fill_fi): similar to mbox
+ version.
+ (scan_dir): use fill_fi to get the unread count now.
+
+ * providers/local/camel-mbox-store.c (fill_fi): helper to lookup
+ unread count either from active folder or from summary file, if
+ it's available.
+ (scan_dir, get_folder_info): use helper above to get folder info.
+
+ * devel-docs/camel-folder-summary.txt: New document describing the
+ format/conventions in the CamelFolderSummary file.
+
+ * providers/nntp/camel-nntp-summary.c (summary_header_load/save):
+ * providers/imapp/camel-imapp-summary.c (summary_header_load/save):
+ * providers/imap/camel-imap-summary.c (summary_header_load/save):
+ Handle versions, per-class version number (1).
+
+ * providers/local/camel-mbox-summary.c (summary_header_load/save):
+ Handle versions properly, add a per-class version (1). Write out the
+ folder size as a size_t rather than 32 bit int.
+
+ * providers/local/camel-local-summary.c (summary_header_load/save):
+ read/write the per-class version number (1).
+
+ * camel-folder-summary.c (summary_header_load): do version
+ checking differently, allow the version to be bumped without
+ aborting the load. Added unread/deleted/junk counts to base
+ header.
+ (summary_header_save): Save out the new-format header. Version
+ bumped to 13.
+
+ * camel.c (camel_init): return 0 rather than spit a compiler warning.
+
+ * camel-file-utils.c (camel_file_util_encode_*_t): macro-ise the
+ type encoder/decoders. Also add size_t encoder/decoder.
+
+2004-02-13 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-store.c (get_folder_online): Same.
+
+ * providers/local/camel-mh-store.c (get_folder): Same as maildir
+ changes.
+
+ * providers/local/camel-maildir-store.c (get_folder): Make
+ exceptions strings consistanmt with the mbox exception strings
+ (and vise versa). Also handle the CAMEL_STORE_FOLDER_EXCL flag.
+
+ * providers/local/camel-mbox-store.c (get_folder): Check
+ CAMEL_STORE_FOLDER_EXCL flag.
+
+ * providers/local/camel-local-store.c (get_folder): Simplified by
+ using camel_mkdir instead of doing it manually.
+
+ * camel-store.c (camel_store_get_folder): If the folder exists in
+ the cache and the O_EXCL flag was passed, return NULL and set an
+ exception.
+
+ * camel-store.h: Added a new CAMEL_STORE_FOLDER_EXCL flag for use
+ with get_folder().
+
+2004-02-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-file-utils.c (camel_file_util_encode_string): Since
+ decoding a string doesn't allow strings longer than 65536,
+ truncate strings that are longer than 65536 here. Fixes bug
+ #54319.
+
+2004-02-09 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #53978.
+
+ * providers/local/camel-mbox-store.c: added ".lock" to the list of
+ ignored extensions.
+ (ignore_file): ignore anything ending in ~ too.
+
+ ** See bug #51319.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): re-enable indexing when folder is
+ first opened. Also load defaults if no meta-data present.
+
+2004-02-06 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-provider.c (camel_provider_init): Use strrchr, not strchr.
+
+ * camel-gpg-context.c (gpg_ctx_parse_status): Forget the need_id,
+ not the userid. Fixes bug #53908.
+
+ * camel-store.c (add_special_info): Set a CAMEL_FOLDER_VIRTUAL bit
+ on the special folder info so our UI can know if it is virtual or
+ not (meant for vTrash/vJunk).
+ (camel_store_get_folder_info): Don't add vTrash/vJunk if the
+ NO_VIRTUAL flag bit is set. Used by the subscriptions editor.
+
+2004-02-06 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder.c (camel_folder_set_message_flags): changed to
+ return a boolean to indicate if the flags were actually changed or
+ not. Fixed all implementors.
+
+2004-02-05 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-mbox-store.c (rename_folder): rename
+ .cmeta file too, and fix the recovery order.
+
+ * providers/local/camel-local-store.c (rename_folder): rename the
+ .cmeta file too.
+
+ * providers/local/camel-local-folder.c (local_rename): fix this to
+ use local_get_full_path stuff.
+
+ * camel-store.c (camel_store_rename_folder): fix umr's comparing
+ old and new names.
+
+2004-02-05 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #53553.
+
+ * camel-provider.c (camel_provider_init): changed to return a
+ hashtable of url protocols to CamelProviderModule structs, rather
+ than simple strings.
+
+ * camel-session.c (get_provider): if we load a provider module,
+ mark it as loaded.
+ (ensure_loaded): Check the module loaded flag before trying to
+ load it.
+
+ * providers/local/libcamellocal.urls: Remove spoold from the list,
+ since it doesn't exist anymore. Actually fixes #53553, the rest
+ is to robustify the code.
+
+2004-02-05 Not Zed <NotZed@Ximian.com>
+
+ * camel-session.c (CS_CLASS): dont typecheck cast.
+
+ * camel-store.c (camel_vjunk_folder_new): removed, use
+ vtrash_new(junk).
+ (setup_special): changed to get_special, with a type now, and
+ dont add vtrash folders to the sources.
+ (get_trash, get_junk): down to 1 liners, call get_special
+
+ * camel-vtrash-folder.c (CF_CLASS): dont use cast typecheck macros
+ here, makes debugging easier and removes redundant checks.
+ (camel_vtrash_folder_init): dont set flags here.
+ (camel_vtrash_folder_new): takes a new argument, type, for junk
+ folders too, removed name arg (taken from type).
+ (vtrash_transfer_messages_to): parameterise flag processing.
+
+2004-02-04 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-store.c: Get rid of some unnecessary
+ CAMEL_OBJECT() casts.
+
+ * providers/imap/camel-imap-folder.c (camel_imap_folder_new): The
+ folder_name argument passed to this function is ALWAYS in the UNIX
+ path form (ie. using '/' as the dir sep) and so when getting the
+ short_name, don't use imap_store->dir_sep as the dir sep, always
+ use '/'. Fixes bug #53755 for the IMAP case.
+
+2004-02-04 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fix for bug #53755 (local folders case)
+
+ * providers/local/camel-mbox-store.c (xrename): No longer takes an
+ exception arg, we just set errno. Our caller can take care of
+ setting an exception.
+ (rename_folder): Don't pass an exception to xrename(), we always
+ overwrote if an error occured it anyway.
+ (rename_folder): Rename the .sbd as well.
+
+2004-02-04 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c (camel_store_sync): added 'expunge' parameter,
+ easier 'empty trash on exit' call.
+ (store_sync): duh, actually pass expunge to folder_sync.
+
+2004-02-04 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c
+ (parse_list_response_as_folder_info): if we can't add the folder
+ to the summary {i.e. duplicate}, then ignore it. See #53836.
+
+ * camel-store.c (camel_store_delete_folder): changed order around,
+ first try to delete and only remove from the object bag if the
+ delete worked. If vjunk/vtrash enabled, don't allow those to be
+ deleted.
+ (cs_delete_cached_folder): helper to delete the folder if its in
+ the cache, remove it from trash/junk, etc.
+ (camel_store_unsubscribe_folder): changed similarly to
+ delete_folder.
+
+ * camel-vee-store.c (vee_delete_folder): dont do any trash/junk
+ processing anymore.
+
+2004-02-03 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c: use the folders object bag to manage the trash
+ folder and junk folders. Remove the init_trash and init_junk
+ stuff, just use get_trash and get_junk to mean the same thing.
+ Get rid of the hacked up vjunk and vtrash "uri" stuff too.
+
+ * camel-object.c (camel_object_bag_add): null out the pair->func,
+ otherwise we get an uninitalised memory read during unhook event.
+
+2004-02-03 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-vee-folder.c (vee_folder_build_folder): Use macro casts
+ from int to pointer.
+
+ * camel-folder.c (camel_folder_change_info_add_source): Same as below.
+ (camel_folder_change_info_add_source_list): Same.
+
+ * camel-folder-search.c (camel_folder_search_execute_expression):
+ Use GINT_TO_POINTER() to cast 1 to a pointer for
+ g_hash_table_insert().
+
+ * camel-vee-folder.c (vee_folder_remove_folder): 64bit fixes.
+ (folder_added_uid): Same.
+ (vee_folder_build_folder): Here too.
+ (folder_changed_add_uid): And here.
+ (folder_changed_remove_uid): Same.
+
+2004-02-03 Jeffrey Stedfast <fejj@ximian.com>
+
+ * tests/misc/url-scan.c: New test suite for url scanning.
+
+ * camel-url-scanner.c: Added single/double quotes to url_braces[]
+ in case the user is quoting the url.
+ (camel_url_web_end): Add "-;:" to list of punctuation to strip off
+ the end of urls. Also fixed to handle user@domain's
+ (camel_url_addrspec_start): Strip open brace characters from the
+ beginning of the addr.
+ (camel_url_web_start): Make sure "www" wasn't part of something
+ not a url (like "Ewww.Gross") by check that pos[-1] is either an
+ open brace or whitespace.
+ (camel_url_addrspec_end): Don't allow toplevel domain addr-specs
+ (if we encounter something that looks like it is a toplevel domain
+ addr, it is more likely to be bogus than correct).
+
+2004-02-02 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fixes for bug #53091.
+
+ * providers/imap/camel-imap-store.c (create_folder): Set the new
+ folder's fi->flags to CAMEL_FOLDER_NOCHILDREN since we know it
+ doesn't have any (we just created it!).
+ (subscribe_folder): Same.
+
+ * camel-store.c (folder_info_clone_rec): Copy the flags too. Fixes
+ the local folder case of bug #53091.
+
+2004-01-31 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/pop3/camel-pop3-store.c (pop3_try_authenticate): Same
+ as below.
+
+ * providers/smtp/camel-smtp-transport.c (smtp_connect): Instead of
+ using the form %s@%s in the password prompt, use %s on host %s,
+ hopefully this will be less confusing to users who have usernames
+ of the form user@vhost.
+
+2004-01-30 Rodney Dawes <dobey@ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c: Fix spelling error in
+ provider description
+
+ Fixes #53572
+
+2004-01-30 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel.c (camel_init): Protect against multiple camel_init()
+ calls. Remember if we've already been called.
+
+2004-01-30 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c (camel_store_rename_folder): copy the old_name
+ that comes in, since it might be the actual folder_name, which
+ will go away during processing. Related to #53123.
+
+2004-01-29 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #53269.
+
+ * providers/nntp/camel-nntp-store.c
+ (nntp_store_get_cached_folder_info): don't dereference last before
+ checking if its null.
+
+ * camel-object.c (camel_object_bag_rekey): added a doc comment.
+
+ ** See bug #53520.
+
+ * camel-session.c (get_service): free the url once done, it now
+ gets copied by the service.
+
+ * camel-service.c (construct): copy the url that comes in, don't
+ just '0Wn34z' it. clena up exception handling too.
+
+2004-01-29 Not Zed <NotZed@Ximian.com>
+
+ * camel-object.c (cobject_state_write): output scan->name and
+ scan->value for writing metadata, rather than meta->name/value
+ which just duplicates the last entry, related to #53195.
+
+ * camel-url.c (camel_url_free): zero out passwd/user/host before
+ freeing them.
+
+2004-01-28 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-store.c (rename_folder): Make sure
+ the new dir path exists before trying to rename files to avoid
+ ENOENT errors. Also save errno when we encounter errors so that we
+ can report the true error later rather than an error we may get
+ while reverting changes. Also, it is OK if the ibex files don't
+ exist, so check for that errno case.
+
+2004-01-28 Sivaih Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c (account_changed)
+ (modify_esources) : add port and /soap parts to relative uri while
+ updating it when account changed
+
+2004-01-27 Radek Doulik <rodo@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (camel_imap_folder_new): set
+ CAMEL_FOLDER_SUPRESS_JUNK_TEST flag
+ (imap_update_summary): update CAMEL_FOLDER_SUPRESS_JUNK_TEST flag
+
+ * camel-folder.c (folder_changed): use
+ CAMEL_FOLDER_SUPRESS_JUNK_TEST flag instead of check_junk_for_imap
+
+ * camel-folder.h (CAMEL_FOLDER_SUPRESS_JUNK_TEST): added new flag
+
+2004-01-27 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #53373.
+
+ * camel-store.c (camel_store_rename_folder): use object_bag_rekey
+ to rename the object. object_bag use was broken.
+
+ * camel-object.c (camel_object_bag_rekey): new api to atomically
+ re-key
+
+2004-01-23 Radek Doulik <rodo@ximian.com>
+
+ * camel-folder.c (folder_changed): use check_junk_for_imap flag
+
+ * camel-session.c: add check_junk_for_imap flag
+
+2004-01-23 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-folder.c (IMAP_SMALL_BODY_SIZE):
+ removed the meaningless fixme, a butt-retrieved-number is as good
+ as any in this case.
+ (imap_get_message): revert peterw's change of 2002-07-15, instead
+ of checking for online mode here, let get_message do it when you
+ retrieve the parts. This lets a multi-fetch (i.e. large) message
+ work more betterer in offline mode.
+
+2004-01-22 Jeremy Katz <katzj@redhat.com>
+
+ * camel-mime-part.c: Fix prototype to be consistent.
+
+2004-01-21 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #52996.
+
+ * camel-data-cache.c (camel_data_cache_add): put a do-loop around
+ the object_bag_reserve stuff, otherwise we can add/abort out of
+ sync (i.e. when object_bag_reserve returned a pointer we mustn't
+ call add/abort).
+
+ * camel-object.c (camel_object_bag_*): Added some inline doco.
+
+2004-01-20 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #52817.
+
+ * camel-session.c (camel_session_get_password): merged reprompt
+ and secret into a flags field, and add more options. Fixed all
+ callers.
+
+ ** See bug #52899.
+
+ * camel-gpg-context.c (gpg_ctx_parse_status): use need_id as the
+ password key, not userid.
+
+2004-01-19 Siviaah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.c
+ (remove_calender_tasks_sources, modify_calender_tasks_sources):
+ add port and "/soap" to source uri
+
+2004-01-19 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-store.c (change_folder): use a CamelURL to properly
+ encode the url we generate.
+ (vee_get_folder_info): ditto.
+ (vee_get_folder_info): removed unused variable/warning.
+
+ * camel-session.c (vee_provider): Update the provider flags for
+ URL_FRAGMENT_IS_PATH.
+
+ * providers/imapp/camel-imapp-utils.c (imap_parse_addfress_list):
+ namespaces fixes for HEADER_ADDRESS*
+
+2004-01-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (imap_sync_online): Limit the
+ flags we set (or unset) to the folder's permanent flags.
+
+2004-01-16 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-message.c (camel_mime_message_build_mbox_from): Same.
+
+ * camel-internet-address.c (internet_decode): Same.
+
+ * camel-mime-utils.[c,h]: Namespaced camel_header_address_t enums
+ s/HEADER_ADDRESS_/CAMEL_HEADER_ADDRESS_/g
+
+2004-01-16 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-groupwise-provider.c: added some conf
+ entries for LDAP address setup
+
+ * providers/groupwise/camel-gw-listener.c: added
+ add_ldap_addressbook_source, modify_ldap_addressbook_source,
+ remove_ldap_addressbook_source functions for setting up LDAP
+ address book. Also setting "username" property on cal/tasks
+ ESources
+
+2004-01-15 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #52881.
+
+ * camel-object.c (camel_object_bag*): Support reserving different
+ keys from the same thread. Oh the pain.
+
+ * camel-vee-store.c (vee_get_folder_info): implement child flags
+ properly. Changed to build tree itself rather than calling
+ camel_folder_info_build.
+ (vee_get_folder): if we're adding a folder with dummy parents,
+ create and add the dummy parent folders too (as real folder
+ objects). We are the only owner of the ref, so this sort of leaks
+ the folder, but they're small.
+
+2004-01-14 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c
+ (camel_provider_module_init): missing renaming.
+
+2004-01-14 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-gw-listener.c (add_esource): set the
+ "auth" property on the ESource's we create, to get authentication.
+
+2004-01-14 Not Zed <NotZed@Ximian.com>
+
+ ** Patch from Timo Sirainen <tss@iki.fi> to honour read-only
+ status for imap folders.
+
+ * providers/imap/camel-imap-folder.c (camel_imap_folder_selected):
+ check for read-only status response.
+ (imap_sync_online): only call sync_offline if we're read-only.
+ (imap_expunge_uids_resyncing): NOOP for read-only.
+
+2004-01-14 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-local-folder.c (local_getv): remove debug
+ printf.
+
+ ** See bug #52835.
+
+ * camel-smime-context.c (sm_get_passwd): removed debugging. If we
+ get called multiple times in a row, then forget the old password
+ and re-prompt - it was a bad password. Zero out password memory
+ too.
+
+2004-01-13 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c: missing renaming.
+
+2004-01-13 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/camel-gw-listener.[ch]: renamed
+ groupwise-config-listener.[ch] to these file names and also
+ changed code to use CamelURL insted of EUri
+
+ * providers/groupwise/camel-groupwise-provider.c: use the renamed
+ config listener apis
+
+ * providers/groupwise/Makefile.am: changed the source file names
+
+2004-01-12 Meilof Veeningen <meilof@wanadoo.nl>
+
+ * providers/nntp/camel-nntp-folder.[ch]: now based on discofolder,
+ cache_message and append_message implemented, only retrieve messages
+ when we are subscribed, some stubs
+
+ * providers/nntp/camel-nntp-provider.c: newsgroup name display
+ settings, password authentication, fix for check_equal where the
+ protocols wouldn't be checked
+
+ * providers/nntp/camel-nntp-store.[ch]: base on discostore with
+ online/offline support, subscriptions, downloading changed parts of
+ the newsgroup list, some stubs, authentication, automatic reconnect
+
+ * providers/nntp/camel-nntp-store-summary.[ch]: NNTP store
+ summary based on IMAP code
+
+ * providers/nntp/camel-nntp-summary.c: save summary after xover
+
+ * providers/nntp/camel-nntp-grouplist.h: added CamelNNTPGroupList
+ structs
+
+ * providers/nntp/Makefile.am: added store summary
+
+2004-01-12 Not Zed <NotZed@Ximian.com>
+
+ ** See bug 52725.
+
+ * providers/imap/camel-imap-folder.c (get_content): pass in
+ transfer encoding when setting up wrapper part.
+
+ * providers/imap/camel-imap-wrapper.c (camel_imap_wrapper_new):
+ Added an encoding type parameter, set on data wrapper.
+
+2004-01-10 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (rfc2047_encode_word): if e_iconv() returns
+ -1, check that errno != EINVAL - if errno *is* EINVAL, it just
+ means that our convlen wasn't long enough to include the whole
+ sequence. This is fine, we'll just start where we left off next
+ loop thru. Fixes bug #52593 (the buffer was duplicated because
+ state wasn't flushed).
+ (camel_header_encode_string): Fixed a type-o in loop where
+ encoding=0, don't g_string_append_len starting at 'word'
+ inptr-start bytes long - 'word' could be NULL and/or inptr-start
+ could be longer than inptr-word.
+
+2004-01-09 Rodney Dawes <dobey@ximian.com>
+
+ * providers/groupwise/Makefile.am (EXTRA_DIST): libcamelgroupwise.urls
+ instead of libcamelimap.urls
+
+2004-01-09 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/groupwise-config-listener.c
+ (is_groupwise_account): added null check for source url to take
+ care of accounts with Server Types as "None"
+
+2004-01-09 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c (imap_forget_folder): fix
+ removal of journal file, and remove the cmeta state file too.
+
+ * providers/imap/camel-imap-folder.c (imap_getv): count up so we
+ call parent class if we missed any, rather than only if we didn't
+ miss any.
+ (imap_rename): rename the object state file.
+ (camel_imap_folder_new): set the object state file for persistent
+ properties.
+
+ * camel-disco-folder.c (disco_getv): support
+ (PERSISTENT_)PROPERTIES & OFFLINE_SYNC.
+ (disco_setv): implement OFFLINE_SYNC.
+ (camel_disco_folder_get_type): setup disco properties list.
+ (cdf_folder_changed): honour the offline_sync setting on the
+ current folder.
+ (disco_sync): save object state.
+ (disco_setv): save object state if it changed.
+
+ * camel-data-wrapper.c (camel_data_wrapper_set_mime_type_field):
+ move assertions here.
+ (set_mime_type_field): change order slightly to properly handle
+ setting the same object. removed assertions from internal method.
+
+2004-01-08 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/Makefile.am : add groupwise to SUBDIRS
+
+ * providers/groupwise/camel-groupwise-provider.c: new camel
+ provider for groupwise
+
+ * providers/groupwise/groupwise-config-listener.[ch] : new class
+ to add e-sources for groupwise calender and tasks
+
+ * providers/groupwise/Makefile.am : added new files to Makefile.am
+
+2004-01-05 JP Rosevear <jpr@ximian.com>
+
+ * camel-utf8.c: include sys/types.h for freebsd
+
+2004-01-05 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c (groupwise_url_hash,
+ groupwise_url_equal): renamed from imap_*.
+
+2004-01-05 Not Zed <NotZed@Ximian.com>
+
+ * camel-tcp-stream-raw.c (socket_connect): check the right return
+ of the socket call, dont set fd to the value of the -1 check!
+
+2003-12-27 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-tcp-stream-raw.c (socket_connect): Save errno and check
+ the return of the socket() call.
+
+2003-12-24 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c
+ (camel_provider_module_init): g_module_open the other providers we
+ depend on to avoid load ordering problems.
+
+2003-12-22 Rodrigo Moya <rodrigo@ximian.com>
+
+ * providers/groupwise/camel-groupwise-provider.c: removed useless
+ configuration options, and added other options from the IMAP
+ provider.
+ (camel_provider_module_init): register a SMTP transport object
+ also, and removed SASL registration, since we don't support it.
+
+2003-12-19 Sivaiah Nallagatla <snallagatla@novell.com>
+
+ * providers/groupwise/Makefile.am:
+ * providers/groupwise/libcamelgroupwise.urls:
+ * providers/groupwise/camel-groupwise-provider.c: added Camel
+ provider for Groupwise accounts, based on the IMAP one.
+
+2003-12-11 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_ctx_parse_status): Don't immediately
+ prompt for a passwd after receiving the NEED_PASSPHRASE status
+ message. Instead, parse the userid that gpg needs a passwd for and
+ store it on our context. Wait for a GET_HIDDEN status message
+ before prompting, this way if the user has their gpg configured to
+ use gpg-agent, the user won't get 2 passwd prompts.
+ (gpg_sign): Fixed to not free a gpg context that we have not
+ allocated (could happen in a fail case).
+
+2003-12-10 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-store.c (get_folder_info): Use
+ CAMEL_FODLER_NOCHILDREN rather than NOINFERIORS because
+ NOINFERIORS means the folder cannot contain subfolders. However,
+ our mbox structure always allows subfolders.
+ (scan_dir): Same.
+
+ * camel-store.h: Add a CAMEL_FOLDER_NOCHILDREN flag.
+
+ * providers/imap/camel-imap-store.c (get_folders): Same as below.
+
+ * providers/imap/camel-imap-utils.c (imap_parse_list_response):
+ s/CAMEL_IMAP_FOLDER_NOCHILDREN/CAMEL_FOLDER_NOCHILDREN/
+
+2003-12-10 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.c (sm_verify): also check
+ application/pkcs7-signature (bloody applemail).
+ (camel_smime_context_describe_part): as above, and fix the logic.
+ dont think its used anyway. Bug #51750.
+
+2003-12-10 Not Zed <NotZed@Ximian.com>
+
+ * Makefile.am (libcamel_la_SOURCES): put the
+ camel-smime-context.[ch] back in here, remove it from EXTRA_DIST,
+ and make it compile optinally the same way camel-tcp-stream-ssl.c
+ does (#ifdef ...).
+
+ * camel-smime-context.c (sm_verify_cmsg): add signer info to
+ certvalidity.
+
+ * camel-cipher-context.c (CamelCipherValidity): Added certinfo to
+ validity for signing and encrypting, so we can find the keys later
+ for a gui.
+ (camel_cipher_validity_add_certinfo): add signer or
+ encrypter info to the validity.
+ (camel_cipher_validity_envelope): add sign/encrypt keys.
+
+2003-12-10 Not Zed <NotZed@Ximian.com>
+
+ * camel-stream-process.c (do_exec_command): remove dthe clearenv
+ stuff, not sure why its there. s/setenv/putenv/ for portability.
+ See Bug #51767.
+
+2003-12-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (header_decode_rfc2184_param): Revert the
+ s/is_/camel_mime_is_/ changes or we get really long variable
+ names.
+ (header_decode_param): Same.
+ (header_decode_param_list): Here too.
+
+2003-12-09 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #51899.
+
+ * providers/imap/camel-imap-store.c (get_folders): add the first
+ fi to the info's hash, so we dont duplicate it if we come across
+ it again (which we generally will).
+
+2003-12-09 Not Zed <NotZed@Ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (smtp_send_to): encode the
+ address before sending it out, rather than using the raw/utf8
+ version.
+
+ * camel-internet-address.c
+ (camel_internet_address_encode_address): check for quoting the
+ local part of the address before outputting it.
+ (cia_encode_addrspec): quote local part if need be.
+ (camel_internet_address_encode_address): make folding optional
+ based on whether inlen is null or not.
+
+ * camel-mime-utils.[ch]: rename is_* to camel_mime_is_* and export
+ the type functions.
+
+2003-12-08 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-utils.c (imap_atom_specials): add } to
+ the atom specials list. This isn't correct, but some busted
+ servers expect it. Bug #50728.
+
+2003-12-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fixes bug #51881
+
+ * providers/local/camel-mbox-store.c (delete_folder): Same.
+
+ * providers/local/camel-local-store.c (delete_folder): Unlink the
+ cmeta file too.
+
+2003-12-06 JP Rosevear <jpr@ximian.com>
+
+ * */Makefile.am: Remove hard coded disable deprecated flags
+
+2003-12-05 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_ctx_op_start): Properly set the
+ O_NONBLOCK flag along with any previously set flags.
+
+ * camel-filter-search.c (run_command): Don't set O_NONBLOCK on the
+ pipe (1. we don't need to, and 2. we should have been setting
+ O_NONBLOCK|prev_flags but we weren't, and so the pipe got
+ O_RDONLY|O_NONBLOCK even tho we wanted to write to it).
+
+ * camel-filter-driver.c (pipe_to_system): Same.
+
+2003-12-04 Radek Doulik <rodo@ximian.com>
+
+ * camel-folder.c (folder_changed): check recent messages for junk
+ mail
+
+ * camel-session.c (camel_session_check_junk): new wrapper method
+ for check_junk flag
+ (camel_session_set_check_junk): ditto
+
+ * camel-session.h (struct _CamelSession): added check_junk flag
+ (if to check incoming mail for junk messages)
+
+2003-12-03 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-store.c (add_special_info): Free fi->path if we are gonna
+ replace it with the vinfo path.
+
+ * providers/local/camel-mbox-store.c (create_folder): Treat
+ parent_name == NULL and parent_name == "" the same.
+
+ * camel-store.c (camel_store_get_folder_info): Only add
+ vTrash/vJunk info's if we've requested the toplevel folder tree,
+ otherwise we get vTrash/vJunk folders in odd places in the folder
+ tree.
+ (add_special_info): Use the provider->url_flags to determine if
+ the url uses the fragment or not for the path.
+
+2003-12-02 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-local-folder.c (local_setv): remove some
+ debug printfs.
+
+ ** See bug #51576.
+
+ * camel-url.c (camel_url_decode): robustify url decoding for bad
+ input, addresses a crash.
+
+ ** See bug #51478.
+
+ * camel-gpg-context.c (gpg_ctx_free): handle the context being
+ NULL, and exit silently.
+
+2003-12-01 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-process.c (camel_process_fork): Start at fd = 3.
+
+ * camel-gpg-context.c (gpg_ctx_op_start): Same.
+
+ * camel-filter-driver.c (pipe_to_system): Same.
+
+ * camel-filter-search.c (run_command): Start at fd = 3.
+
+2003-12-01 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-stream-process.c (do_exec_command): Same.
+
+ * camel-process.c (camel_process_fork): Same.
+
+ * camel-filter-search.c (run_command): Same as below.
+
+ * camel-filter-driver.c (pipe_to_system): Same as below.
+
+ * camel-gpg-context.c (gpg_ctx_op_start): Use fcntl() to set
+ FD_CLOEXEC on each fd rather than close()ing it. Apparently
+ Linux's older pthread implementations use sockets and so this
+ fouls threading up. GO LINUX! GO!
+
+2003-12-01 Radek Doulik <rodo@ximian.com>
+
+ * camel-store.c (add_special_info): set SUBSCRIBED and NOINFERIORS
+ flags to special folders, renamed method from
+ add_vtrash_or_vjunk_info
+ (camel_store_get_folder_info): call add_special_info directly
+
+2003-11-28 Radek Doulik <rodo@ximian.com>
+
+ * camel-store.c (camel_store_get_folder_info): use old code from
+ mail-ops to add vtrash/vjunk info
+
+2003-11-28 David Woodhouse <dwmw2@redhat.com>
+
+ * providers/imap/camel-imap-provider.c: Enable GUI option for
+ 'custom command' connection.
+ * providers/imap/camel-imap-store.c: Don't g_free strings in
+ .rodata. It's considered rude.
+
+2003-11-28 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-utils.c (mail_list_magic[]): Added list-unsubscribe
+ header match.
+
+2003-11-26 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.c (sm_verify_cmsg): take a stream rather
+ than a part for the content.
+ (sm_verify): get the content directly as a stream.
+
+ * camel-multipart-signed.c
+ (camel_multipart_signed_get_content_stream): new api to get the
+ content stream which will match the signed version.
+
+2003-11-26 JP Rosevear <jpr@ximian.com>
+
+ * Makefile.am: make sure we always dist the smime stuff
+
+2003-11-26 JP Rosevear <jpr@ximian.com>
+
+ * Makefile.am (libcamelinclude_HEADERS): conditionally compile
+ s/mime support
+
+2003-11-25 Not Zed <NotZed@Ximian.com>
+
+ * camel-cipher-context.c (camel_cipher_validity_envelope): change
+ args to make it suit storing the validity in a tree.
+ (camel_cipher_validity_init): init the list header.
+ (camel_cipher_validity_clone): call validity_new so it gets init
+ properly.
+ (camel_cipher_validity_free): free any children nodes recursively.
+
+ * camel-cipher-context.h (CamelCipherValidity): added next/prev
+ and list header.
+
+2003-11-18 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-session.c (camel_session_finalise): Don't destroy the
+ providers as it mans we can never ever have more than 1 session
+ object. See bug #51119 for details.
+
+2003-11-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-store.c (create_folder): Fixed an
+ exception to give a more meaningful description.
+ (get_folder_info): We always want to scan at least one level deep.
+
+2003-11-14 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-store.c (get_folder_info): Handle ""
+ as a request for a full dirscan as well (not just NULL). Makes it
+ consistant with other stores.
+ (scan_dir): Set the fi->flags appropriately.
+
+ * providers/local/camel-mbox-folder.c
+ (camel_mbox_folder_get_full_path): Removed temporary hack.
+
+2003-11-14 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-parser.c (SCAN_BUF): oops, put the mempool stuff
+ back, we don't use e-memory afterall. Damn plane hacking.
+
+2003-11-13 Not Zed <NotZed@Ximian.com>
+
+ * camel-mime-parser.c: Remove mempool code, we use the stuff in
+ e-util.
+ (PRESERVE_HEADERS): new compile option, if on, we preserve headers
+ and folding exactly rather than unfolding all input. THIS BREAKS
+ EVERYTHING right now, so don't turn it on.
+
+ * camel-gpg-context.c (gpg_decrypt): reset the input memstream
+ before passing it to the gpg engine.
+
+ * tests/smime/pgp-mime.c (main): redirect /dev/null to stdin so it
+ doesn't hang waiting for input.
+ (main): removed from build - this tests multipart/signed
+ explictly, but now the details of this is handled directly by the
+ cipher context.
+
+ * tests/smime/pgp.c (main): fixes for api changes.
+ (main): redirect /dev/null to stdin so it doesn't hang waiting for
+ input.
+
+ * tests/message/test1.c (main): update for api changes.
+
+ * camel-smime-context.c (sm_verify): look at the content object's
+ mime type, not the container's type.
+
+2003-11-11 Not Zed <NotZed@Ximian.com>
+
+ * camel-cipher-context.c (camel_cipher_validity_set_valid): take
+ into account the @valid argument and set validity properly.
+ (camel_cipher_validity_clone): new method to copy validities.
+
+ * camel-smime-context.c (sm_signing_cmsmessage): removed a todo.
+
+2003-11-10 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.c (sm_verify_cmsg): split out the CMSMessage
+ verification code so it can be used from enveloped or externally
+ signed data.
+
+ * camel-cipher-context.c (camel_cipher_verify): only take a
+ mimepart, internally handle multiparts and the hash.
+
+2003-11-07 Not Zed <NotZed@Ximian.com>
+
+ * camel-cipher-context.c: make ciphervalidity a public structure,
+ added encrypt status.
+ (camel_cipher_decrypt): changed to return a ciphervalidity. fixed
+ implementations.
+ (camel_cipher_validity_*): Fixed implementations to match new
+ structure, some of this is now redundant.
+
+2003-11-06 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.c (camel_smime_context_describe_part):
+ implement.
+
+2003-11-05 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (connect_to_server): Don't
+ bother trying to see if the server advertises EHLO. Simply always
+ try EHLO and fall back to HELO if EHLO fails. Fixes bug #50535.
+
+2003-11-05 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.c (camel_smime_context_describe_part): new
+ (unfinished) api to peek inside smime parts to tell us whats in
+ it.
+
+2003-11-03 Not Zed <NotZed@Ximian.com>
+
+ * camel-gpg-context.c (gpg_encrypt): Make this output the
+ full multipart/encrypted part, not just the encrypted content part.
+
+ * camel-cipher-context.c (camel_cipher_sign): change to output
+ full mime part, not just a stream.
+ (camel_cipher_canonical_to_stream): utility function to
+ canonicalise a mimepart to a stream.
+
+ * camel-smime-context.c (sm_encode_cmsmessage): removed.
+ (sm_sign): change interface to output a full mime-part, not just a
+ part of a mime part in multipart/signed mode.
+
+2003-11-04 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-gpg-context.c (gpg_ctx_parse_status): We might need to
+ convert the passwd from UTF-8 into the locale charset. Fixes bug
+ #50485.
+
+2003-10-31 Not Zed <NotZed@Ximian.com>
+
+ * camel-cms-context.[ch]: removed, now redundant.
+
+2003-10-30 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.c (sm_get_passwd): implement something basic.
+
+ * camel-cipher-context.h: Added a note about api inconsistencies.
+
+2003-10-30 Not Zed <NotZed@Ximian.com>
+
+ * camel-multipart-encrypted.c (camel_multipart_encrypted_decrypt):
+ fix for cipher_decrypt changes.
+
+ * camel-gpg-context.c, camel-cipher-context.c: moved all the init
+ code to the end to save having to keep forward declarations
+ around.
+ (camel_cipher_decrypt): changed to take mimepart input and return
+ a mimepart.
+ (gpg_decrypt): fix for changed args.
+
+2003-10-29 Not Zed <NotZed@Ximian.com>
+
+ * camel-smime-context.[ch]: replaced entirely with a new
+ implementation which inherits from camel-cipher-context, and add
+ to build.
+
+ * camel-multipart-encrypted.c (camel_multipart_encrypted_encrypt):
+ fix for cipher_encrypt api changes.
+ (camel_multipart_encrypted_decrypt): use g_ascii_strcasecmp.
+
+ * camel-gpg-context.c (gpg_encrypt): Fix to handle input/output as
+ parts not streams
+
+ * camel-cipher-context.c (camel_cipher_encrypt): change to take
+ mimeparts rather than streams as input/output. And remove the
+ 'sign' argument, it is implied if userid is supplied.
+
+2003-10-28 Not Zed <NotZed@Ximian.com>
+
+ * tests/smime/pgp.c (main): fix for ciphercontext api changes.
+
+ * camel-multipart-signed.c (camel_multipart_signed_verify): pass
+ in the part to cipher_verify directly.
+ (camel_multipart_signed_sign): let the cipher context setup the
+ part details.
+
+ * camel-gpg-context.c (gpg_sign): put the signature stream into a
+ mimepart, with appropriate headers/encoding.
+ (swrite): write out a mimepart rather than a stream.
+ (gpg_verify): handle changed args.
+
+ * camel-cipher-context.c (camel_cipher_sign): write the signature
+ to a mimepart rather than a simple stream.
+ (camel_cipher_verify): take the signature as a mimepart not a
+ stream.
+
+2003-10-22 Not Zed <NotZed@Ximian.com>
+
+ * camel-utf8.c (camel_ucs2_utf8, camel_utf8_ucs2): helpers for
+ ucs2 stuff. ucs2 is 16 bit truncated unicode.
+
+2003-10-28 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c: We should check List-Post before List-Id
+ (List-Post has to contain the mailing-list posting address,
+ whereas List-Id does not.) WAlso moved X-Loop to after List-Id to
+ make FreeBSD lusers happy. Fixes bug #32297.
+
+2003-10-28 Jeffrey Stedfast <fejj@ximian.com>
+
+ * Fixes bug #35083
+
+ * providers/imap/camel-imap-store.c (connect_to_server): Same
+ here.
+
+ * providers/pop3/camel-pop3-store.c (connect_to_server): Same as
+ the smtp changes.
+
+ * providers/smtp/camel-smtp-transport.c (connect_to_server): If
+ HAVE_SSL is undefined, don't default to raw connections if the
+ option to connect via ssl is set. Instead set an exception and
+ return fail.
+
+2003-10-27 Frederic Crozat <fcrozat@mandrakesoft.com>
+
+ * camel-mime-utils.c: (camel_header_decode_date):
+ better detection of broken date to give to broken_date_parser.
+
+2003-10-24 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-text-index.c (text_index_name_add_buffer): If a word is
+ longer than CAMEL_TEXT_INDEX_MAX_WORDLEN, then ignore it. This
+ fixes bug #50096.
+
+2003-10-23 Jeffrey Stedfast <fejj@ximian.com>
+
+ * *.c: Removed unneeded CAMEL_OBJECT() casts.
+
+2003-10-21 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-local-folder.c (local_getv, local_setv):
+ use the right tag name for the index_body arg.
+ (local_sync): write any persistent metadata - to make it
+ persistent.
+ (camel_local_folder_construct): turn off indexing, for now, it
+ should be done in local_setv.
+
+ * providers/local/camel-local-folder.h: change body_index to a bool
+ type.
+
+ * camel-object.c (cobject_state_read, cobject_state_write): handle
+ bool types
+ (cobject_state_write): make sure we free all arg types.
+
+ * camel-arg.c (camel_argv_build):
+ (camel_arggetv_build): handle bool type.
+
+ * camel-arg.h: Added BOO (bool) type.
+
+2003-10-15 Not Zed <NotZed@Ximian.com>
+
+ * camel-store.c (camel_folder_info_build): Fix so we output the
+ tree in sorted depth-first order, rather in reverse.
+
+2003-10-16 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-sasl-kerberos4.c: Fixed a #include.
+
+2003-10-10 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-local-provider.c: set the url fragment
+ flag for local providers.
+
+ * camel-provider.h: Move the URL_PART_NEED bits to the high 16
+ bits, to allow for easier changes in the future. Added a
+ URL_PART_FRAGMENT flag for providers that use fragment = folder
+ path.
+
+2003-10-09 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (header_decode_date): Allow timezone offsets
+ to be up to 14 hours ahead of UTC. Fixes bug #49357.
+
+ * broken-date-parser.c (get_tzone): Same.
+
+2003-10-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (smtp_helo): Removed an
+ unused variable.
+
+2003-09-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/smtp/camel-smtp-transport.c (smtp_helo): If the
+ localhost lookup results in a numeric IPv6 host, use the form
+ "[IPv6:<addr>]" as specified in rfc2821. Fixes bug #46006.
+
+2003-09-23 Ettore Perazzoli <ettore@ximian.com>
+
+ * providers/local/camel-local-provider.c: Set the IS_STORAGE bit
+ in the mbox provider, since it can now contain a hierarchy of
+ folders.
+
+2003-09-23 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-store.c (get_folder): If the CREATE
+ flag is set and the parent .sbd directory does not exist, create
+ it.
+
+ * camel-mime-utils.c (append_8bit): Don't forget to flush the
+ iconv conversion.
+
+ * tests/message/test4.c (main): Don't try dot-files.
+
+2003-09-22 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #41610
+
+ * providers/pop3/camel-pop3-folder.c (cmd_tocache): protect a
+ divide by 0 for 0 length messages.
+
+2003-09-22 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-provider.c: Added "offline_sync"
+ option, which lets you synchronise all mail to local storage
+ automagically.
+
+ * camel-disco-folder.c (cdf_folder_changed): hook onto the folder
+ changed single, for all new messages, check that they are online
+ using another thread, if the offline_sync option has been enabled
+ for this store.
+
+2003-09-21 Not Zed <NotZed@Ximian.com>
+
+ * camel-session.c (session_thread_destroy): call proper entry
+ point for freeing the message.
+
+2003-09-18 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder.c (filter_filter): register the filtering process
+ for progress, and do progress of the filtering process.
+
+2003-09-17 Not Zed <NotZed@Ximian.com>
+
+ * camel.c (camel_init): init camel operation.
+
+ * camel-operation.c (camel_operation_reset): removed, not used,
+ not worth it.
+ (camel_operation_mute): new method to stop all status updates
+ permanently.
+ (*): Changed to use thread specific data and a list rather than a
+ hashtable.
+ (cancel_thread): removed.
+ (camel_operation_register): return the previously registered op.
+
+2003-09-22 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/nntp/camel-nntp-store.c (connect_to_server): Fix the
+ code that creates a new ssl stream to pass the correct arguments
+ and the proper flags.
+
+ * providers/imapp/camel-imapp-folder.c (imap_sync): Cast the
+ CamelFolder to a CamelIMAPPFolder to hush some compiler warnings.
+
+ * camel-mime-utils.h: Define a struct _CamelContentDisposition
+ (allows the imapp code to compile)
+
+ * providers/imapp/camel-imapp-driver.c: #include <string.h>
+
+2003-09-18 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (camel_transfer_encoding_to_string): New
+ function to replace the one from camel-mime-part.c
+ (camel_transfer_encoding_from_string): Same.
+ (camel_content_transfer_encoding_decode): Renamed from
+ camel_header_content_encoding_decode().
+
+ * camel-mime-part.c (camel_mime_part_encoding_to_string): Removed.
+ (camel_mime_part_encoding_from_string): Removed.
+
+ * camel-data-wrapper.[c,h]: updated for CamelTransferEncoding
+ namespace changes
+
+ * camel-folder-summary.c: updated for CamelTransferEncoding
+ namespace changes
+
+ * camel-mime-filter-bestenc.[c,h]: updated for CamelTransferEncoding
+ namespace changes
+
+ * camel-mime-message.c: updated for CamelTransferEncoding
+ namespace changes
+
+ * camel-mime-part-utils.c: updated for CamelTransferEncoding
+ namespace changes
+
+ * camel-multipart-signed.c: updated for CamelTransferEncoding
+ namespace changes
+
+ * camel-smime-context.c: updated for CamelTransferEncoding
+ namespace changes
+
+ * providers/imapp/camel-imapp-utils.c: updated for
+ CamelTransferEncoding namespace changes
+
+ * tests/lib/messages.c: updated for CamelTransferEncoding
+ namespace changes
+
+ * tests/message/test1.c: updated for CamelTransferEncoding
+ namespace changes
+
+2003-09-18 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.[c,h]: namespaced the encoding/decoding
+ routines.
+
+ * camel-mime-filter-basic.c: updated for namespace changes to the
+ encoding/decoding routines in camel-mime-utils.c
+
+ * camel-multipart.c: updated for namespace changes to the
+ encoding/decoding routines in camel-mime-utils.c
+
+ * camel-sasl-digest-md5.c: updated for namespace changes to the
+ encoding/decoding routines in camel-mime-utils.c
+
+ * camel-sasl.c: updated for namespace changes to the
+ encoding/decoding routines in camel-mime-utils.c
+
+ * camel-vee-folder.c: updated for namespace changes to the
+ encoding/decoding routines in camel-mime-utils.c
+
+ * providers/imap/camel-imap-search.c: updated for namespace
+ changes to the encoding/decoding routines in camel-mime-utils.c
+
+ * providers/pop3/camel-pop3-folder.c: updated for namespace
+ changes to the encoding/decoding routines in camel-mime-utils.c
+
+2003-08-26 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-parser.[c,h]: s/HSCAN_/CAMEL_MIME_PARSER_STATE_/g and
+ s/_header_state/_camel_mime_parser_state/g
+
+ * camel-filter-driver.c: Same.
+
+ * camel-folder-summary.c: Here too.
+
+ * camel-http-stream.c: And here.
+
+ * camel-mime-message.c: ...
+
+ * camel-mime-part-utils.c: ...
+
+ * camel-mime-part.c: ...
+
+ * camel-movemail.c: ...
+
+ * camel-multipart-signed.c: ...
+
+ * camel-multipart.c: ...
+
+ * providers/local/camel-mbox-folder.c: ...
+
+ * providers/local/camel-mbox-summary.c: ...
+
+ * providers/local/camel-mh-summary.c: ...
+
+ * providers/nntp/camel-nntp-summary.c: ...
+
+ * providers/pop3/camel-pop3-folder.c: ...
+
+2003-08-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.[c,h]: Namespaced.
+
+ * camel-data-wrapper.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-digest-folder.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-filter-driver.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-filter-search.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-folder-search.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-folder-summary.[c,h]: updated for namespace changed made
+ to camel-mime-utils.[c,h]
+
+ * camel-http-stream.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-http-stream.h: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-internet-address.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-medium.[c,h]: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-mime-message.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-mime-parser.[c,h]: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-mime-part-utils.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-mime-part.[c,h]: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-movemail.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-multipart-encrypted.c: updated for namespace changed made
+ to camel-mime-utils.[c,h]
+
+ * camel-multipart-signed.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-multipart.c: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * camel-search-private.[c,h]: updated for namespace changed made
+ to camel-mime-utils.[c,h]
+
+ * camel-types.h: updated for namespace changed made to
+ camel-mime-utils.[c,h]
+
+ * providers/imap/camel-imap-folder.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/imap/camel-imap-store-summary.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/imap/camel-imap-utils.c: updated for namespace changed
+ made to camel-mime-utils.[c,h]
+
+ * providers/imapp/camel-imapp-utils.[c,h]: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/local/camel-local-summary.[c,h]: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/local/camel-maildir-summary.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/local/camel-mbox-summary.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/local/camel-spool-summary.h: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/nntp/camel-nntp-summary.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/nntp/camel-nntp-utils.c: updated for namespace changed
+ made to camel-mime-utils.[c,h]
+
+ * providers/pop3/camel-pop3-folder.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+ * providers/sendmail/camel-sendmail-transport.c: updated for
+ namespace changed made to camel-mime-utils.[c,h]
+
+ * providers/smtp/camel-smtp-transport.c: updated for namespace
+ changed made to camel-mime-utils.[c,h]
+
+2003-09-16 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-folder.c
+ (camel_mbox_folder_get_full_path): Implement a temp hack so trunk
+ works until we merge in new-ui-branch.
+
+ * camel-stream-filter.c (do_flush): Don't warning about how we
+ haven't written anything to the stream, this is not an
+ error. fflush() doesn't care if you try to fflush() a stream
+ without writing to it, so we shouldn't care either.
+
+2003-09-15 Not Zed <NotZed@Ximian.com>
+
+ * providers/imapp/camel-imapp-store.c (store_resp_list)
+ (imap_login, try_sasl, imap_try_authenticate): removed dead code.
+
+ * providers/imapp/camel-imapp-stream.c: return -1 if stream not
+ set.
+
+ * providers/imapp/camel-imapp-engine.c (iterate_completion): put
+ done request on the done queue, so all requests are always
+ somewhere.
+ (camel_imapp_engine_command_free): just spit warnings of active
+ messages being freed, but abort if the item isn't in any list.
+ Also remove the node from its list before going on.
+ (iterate_untagged, iterate_continuation, iterate_completion):
+ staticifiy.
+
+ * providers/imapp/camel-imapp-provider.c
+ (camel_imapp_module_init): move camel_exception_setup call here.
+
+ * providers/imapp/camel-imapp-driver.c
+ (camel_imapp_driver_get_type): remove execption setup here, it
+ isn't early enough.
+ (camel_imapp_driver_list): handle exceptions.
+
+2003-09-12 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/local/camel-mbox-folder.c
+ (camel_mbox_folder_get_full_path): Implements
+ CamelLocalFolder::get_full_path() (publicly namespaced so that
+ CamelMboxStore can re-use them).
+ (camel_mbox_folder_get_meta_path): Same.
+
+ * providers/local/camel-mbox-store.c (get_folder): Changed the way
+ the path is constructed, since we now handle subdirectories and
+ stuff.
+ (delete_folder): Try deleting the Folder.sbd directory. We also
+ need to manage our own meta files since CamelLocalStore's impl
+ constructs paths differently than what we need.
+ (create_folder): Implemented.
+ (rename_folder): Implemented.
+ (scan_dir): Scan an mbox tree
+ (get_folder_info): Implemented using scan_dir().
+
+ * providers/local/camel-local-store.c (delete_folder): Set fi->url
+ to the correct value, meaning we need to prefix it with the
+ protocol and the folder_name is not actually part of the path, it
+ is a separate component to the url.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): Use the new class virtual method
+ to construct the full folder path and all the meta files.
+ (local_get_full_path): Implemented default get_full_path method.
+ (local_get_meta_path): Implemented default get_meta_path method.
+
+2003-09-11 Dan Winship <danw@ximian.com>
+
+ * Makefile.am (noinst_LTLIBRARIES): Remove libcamel-static.la
+
+2003-09-05 Not Zed <NotZed@Ximian.com>
+
+ * providers/imap/camel-imap-store.c (imap_noop): call
+ camel_folder_sync bypassing the folder lock. See
+ imap_store_refresh_folders too.
+
+2003-09-04 David Woodhouse <dwmw2@infradead.org>
+
+ * providers/camel-imap-store.[ch]: Add PREAUTH handling and
+ pine/mutt/etpan/etc.-style 'ssh <mailhost> exec imapd' support.
+
+2003-09-03 David Woodhouse <dwmw2@infradead.org>
+
+ * camel-stream-process.[ch]: New stream implementation for running
+ commands.
+ * Makefile.am: Compile the above
+
+2003-08-20 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47765.
+
+ * camel-folder-search.h: Removed match1 member.
+
+ * camel-folder-search.c (camel_folder_search_match_expression):
+ use current directly rather than match1. This method isn't used
+ anywhere anyway.
+ (search_not): remove match1 stuff.
+ (search_match_all): properly handle the match-all against 1
+ message as a scalar result, not an array result.
+
+2003-09-03 Not Zed <NotZed@Ximian.com>
+
+ * camel-http-stream.c (camel_http_stream_set_proxy): handle NULL
+ proxy_url - unset the proxy.
+
+2003-08-29 Not Zed <NotZed@Ximian.com>
+
+ * camel-object.c (camel_object_state_write):
+ (cobject_getv):
+ (cobject_setv, cobject_state_read, cobject_state_read)
+ (cobject_state_write): removed debug printfs.
+
+ * providers/local/camel-local-folder.c (local_getv): Need to copy
+ the local properties list before passing it out, since it's freed.
+
+2003-08-27 Not Zed <NotZed@Ximian.com>
+
+ * providers/local/camel-local-folder.c (local_getv): implement
+ PERSISTENT_PROPERTIES, for index mode.
+
+ * camel-object.c (cobject_state_read): Also add property reading,
+ and bump version to 1.
+ (cobject_state_write): add persistent property writing.
+
+2003-08-26 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder.c (folder_getv): chain up properly.
+
+ * camel-file-utils.c (camel_file_util_savename): helper to create
+ a .#filename filename.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_construct): init meta-data for local folders.
+ (local_getv): chain up properly, if args are not processed, rather
+ than don't if they aren't.
+
+2003-08-23 Not Zed <NotZed@Ximian.com>
+
+ * camel-object.c (cobject_class_init): added a new event,
+ meta_changed.
+ (camel_object_meta_set, camel_object_meta_get): meta-data api.
+ (camel_object_free_hooks): Free meta-data if it is set on the
+ object.
+
+ * providers/local/camel-local-folder.c
+ (camel_local_folder_get_type): setup a property list for local
+ folders, just 'index_body' at present.
+
+2003-08-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-filter-driver.c (pipe_to_system): Added some more error
+ checking for reading/writing to the pipes. Fixes bug #47880.
+
+2003-08-21 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-data-wrapper.c (decode_to_stream): Don't poke
+ wrapper->stream directly, use camel_data_wrapper_write_to_stream()
+ instead as this simplifies things and makes the imap data wrapper
+ implementation Just Work (tm).
+
+ * providers/imap/camel-imap-wrapper.c: changed prototype of
+ write_to_stream() to return ssize_t.
+
+2003-08-20 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-wrapper.c (imap_wrapper_hydrate): Make
+ sure to ref the stream. Fixes bug #47749.
+
+2003-08-18 Not Zed <NotZed@Ximian.com>
+
+ * camel-http-stream.c: Various fixes to make it work.
+
+ * tests/smime/pgp-mime.c (main): added missing 'ret' variable.
+
+ * providers/smtp/camel-smtp-transport.c (connect_to_server):
+ * providers/imapp/camel-imapp-store.c (connect_to_server:
+ * providers/imap/camel-imap-store.c (connect_to_server):
+ * providers/pop3/camel-pop3-store.c (connect_to_server):
+ * camel-http-stream.c (http_connect): change service->session for
+ tcp_stream_ssl_new.
+
+ * camel-tcp-stream-ssl.c: Changed service to session, and fix some
+ refcounting of it.
+ include camel-operation.h
+
+2003-08-15 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47634.
+
+ * tests/lib/messages.c (test_message_compare): check
+ write_to_stream returns.
+ (message_dump_rec): helper to dump message structure.
+
+ * camel-mime-part-utils.c
+ (simple_data_wrapper_construct_from_parser): dont set content
+ encoding here.
+ (camel_mime_part_construct_content_from_parser): set it here
+ instead, on every part. basically same as setting the
+ mime_type_field always.
+
+ * camel-multipart-signed.c (camel_multipart_signed_class_init):
+ * camel-mime-message.c (camel_mime_message_class_init):
+ * camel-multipart.c (camel_multipart_class_init): override
+ decode_to_stream to always do the same as write_to_stream, since
+ we can never be encoded.
+
+2003-08-15 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-wrapper.c (imap_wrapper_hydrate):
+ Don't attach any filters to decode base64/qp/etc.
+
+2003-08-14 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-part.c (write_to_stream): Save errno when
+ flushing/unreffing the filter stream.
+
+2003-08-13 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-part.c (write_to_stream): If the content is
+ base64/qp/uu/etc encoded but the part is 7bit/8bit/(or otherwise
+ non-encoded), set reencode to TRUE so that we decode the original
+ content stream. Fixes a bug noticed on
+ evolution-patches@ximian.com where a patch had a
+ Content-Transfer-Encoding of 7bit but was base64 encoded.
+
+2003-08-13 Not Zed <NotZed@Ximian.com>
+
+ * camel-folder-summary.c (camel_folder_summary_remove_range): Fix
+ the range check, we were stopping removal of 1 or 2 removals, for
+ some odd and completely uncomprehensible reason. Perhaps debug
+ left in?
+
+2003-08-13 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47517.
+
+ * camel-vee-folder.c (vee_sync): Always rebuild folder on any
+ sync, not just expunge ones.
+
+2003-08-11 Not Zed <NotZed@Ximian.com>
+
+ * providers/imapp/camel-imapp-store.c (imap_get_folder_info):
+ force connect manually so basics work.
+
+ ** See bug #45505.
+
+ * camel-service.c (camel_gethostbyname): duh, pthread_create
+ returns the error code directly, not via errno.
+ (camel_gethostbyaddr): Same, also properly handle the failure
+ case.
+
+2003-08-01 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47208.
+
+ * camel-filter-search.c (match_all): match-all with no arguments
+ should always return TRUE.
+
+ * camel-folder-search.c (camel_folder_search_execute_expression):
+ print a warning when we get an invalid result type & fixed a leak
+ for that case.
+
+2003-08-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ * tests/message/test4.c: New test suite for the mime parser (which
+ is where the below 2 fixes were noticed).
+
+ * camel-mime-parser.c (folder_boundary_check): Calculate 'len' by
+ subtracting the boundary start from inend rather than 'atleast'.
+ (folder_scan_content): Calculate 'inend' differently depending on
+ the EOF state.
+
+2003-08-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-filter-tohtml.c (html_convert): Rather than checking
+ *inptr == '\n', check inptr >= inend - this gets rid of an Invalid
+ Read report from valgrind.
+
+ * camel-mime-part.c (write_to_stream): Don't necessarily re-encode
+ just because the encodings differ. Need to look into making it so
+ that message/rfc822 and multipart parts ignore the
+ Content-Transfer-Encoding header and just keep their 'encoding'
+ bits set to DEFAULT.
+
+2003-08-05 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/imap/camel-imap-folder.c (get_content): Updated.
+
+ * camel-mime-message.c (camel_mime_message_init): Don't override
+ the mime_type here.
+ (process_header): Updated to use CamelDataWrapper's mime_type
+ field.
+ (find_best_encoding): Same.
+ (best_encoding): Here too.
+
+ * camel-digest-folder.c (camel_digest_folder_new): Updated for
+ CamelMimePart::content_type change.
+
+ * camel-mime-part.c (camel_mime_part_init): Override our parent
+ class's default mime_type.
+ (camel_mime_part_finalize): Don't need to unref the content_type
+ anymore.
+ (process_header): Updated to use CamelDataWrapper's mime_type
+ field.
+ (camel_mime_part_set_filename): Same.
+ (camel_mime_part_get_filename): Same.
+ (camel_mime_part_get_content_type): Same.
+ (set_content_object): Here too.
+ (write_to_stream): Updated.
+ (construct_from_parser): Updated.
+
+ * camel-mime-part.h: Remove the content_type field.
+
+2003-07-31 Jeffrey Stedfast <fejj@ximian.com>
+
+ * tests/lib/messages.c (test_message_compare_content): If the
+ chunks differ, perform a hexdump on the data being compared so
+ that we may analyse it easier.
+
+ * camel-multipart-signed.c (write_to_stream): Return ssize_t.
+
+ * camel-mime-utils.h: Added the CamelMimePartEncodingType enum
+ here.
+
+ * camel-mime-part.h: Removed the CamelMimePartEncodingType enum
+ from here.
+
+ * camel-mime-part.c (write_to_stream): Updated to return
+ ssize_t. Also minor changes to only re-encode the content stream
+ if the charset or encoding changed (this way we write it out in
+ the original raw form if nothing changed).
+
+ * camel-mime-part-utils.c
+ (simple_data_wrapper_construct_from_parser): Drastically
+ simplify. We no longer scan html content to try and find the
+ charset, nor do we care about converting the content to UTF-8 and
+ handling broken windows charsets.
+
+ * camel-mime-message.c (find_best_encoding): Use
+ decode_to_stream() here. Also updated to not assume the content
+ charset is UTF-8 since it is very likely not the case anymore
+ since data-wrappers no longer are converted to UTF-8 at parse
+ time.
+
+ * camel-folder-summary.c (summary_build_content_info_message): Use
+ decode_to_stream instead here too.
+
+ * camel-folder-search.c (match_words_1message): Use
+ decode_to_stream instead of write_to_stream so we can search the
+ contents.
+
+ * camel-data-wrapper.c (camel_data_wrapper_init): Set the default
+ encoding to DEFAULT.
+ (write_to_stream): Updated to return ssize_t
+ (camel_data_wrapper_decode_to_stream): New virtual function to
+ decode a data wrapper to a stream (results in nearly identical
+ behaviour to the old write_to_stream method).
+ (decode_to_stream): Default implementation of above virtual
+ method. Decodes base64/qp/etc streams.
+
+ * camel-data-wrapper.h: Removed the rawtext bit and added an
+ encoding member.
+
+2003-08-01 Jeffrey Stedfast <fejj@ximian.com>
+
+ * tests/smime/pgp-mime.c: Same.
+
+ * tests/smime/pgp.c: Updated to build and to import some custom
+ gpg keys for use with testing.
+
+2003-07-30 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-message.c (write_to_stream): Also updated.
+
+ * camel-data-wrapper.c (write_to_stream): This should return ssize_t
+
+ * camel-multipart-signed.c (write_to_stream): Updated.
+
+ * camel-multipart.c (write_to_stream): Same.
+
+ * camel-mime-part.c (write_to_stream): Here too.
+
+2003-07-11 Suresh Chandrasekharan <suresh.chandrasekharan@sun.com>
+
+ * camel-iconv.c: Fix for #46168 'some additional locale aliases
+ required for chinese support'.
+
+2003-07-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (header_decode_word): Revert NotZed's fix for
+ bug #42170 - this causes even more problems than it solves. See
+ bug #46331 for info. Basically, each address header would be
+ converted to UTF-8 twice which means no raw 8bit address header
+ would render correctly.
+ (header_decode_mailbox): Perform a sanity check on the resultant
+ addr->str to make sure that it is valid UTF-8, if not convert it
+ to UTF-8. Fixes bug #42170.
+
+2003-07-23 Ettore Perazzoli <ettore@ximian.com>
+
+ * camel-provider.c (camel_provider_init): Print the provider
+ directory as well, for debugging.
+
+2003-07-23 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-message.c (find_best_encoding): Revert my previous
+ changes to this function.
+
+ * camel-mime-utils.h: Revert previous changes.
+
+ * camel-mime-part.h: Revert previous changes.
+
+ * camel-mime-part-utils.c: Revert previous changes.
+
+ * camel-data-wrapper.c (camel_data_wrapper_init): Revert previous
+ changes.
+ (write_to_stream): Revert previous changes.
+
+2003-07-23 Dan Winship <danw@ximian.com>
+
+ * camel-block-file.c: #include camel-file-utils.h for camel_read()
+
+ * camel-uid-cache.c (camel_uid_cache_save): Remove unused variable
+ and label.
+
+ * camel-url.c: #include camel-string-utils.h for camel_strdown
+
+ * providers/pop3/camel-pop3-store.c (pop3_try_authenticate): Cast
+ an (unsigned char *) to (char *) to fix a warning
+
+2003-07-17 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-message.c (find_best_encoding): Updated to convert
+ to/from the correct charset (since content is no longer
+ necessarily in UTF-8).
+ (best_encoding): Free the charset string when we're done with it.
+
+ * camel-stream-fs.c (stream_read): Increment the seekable stream
+ position by the number of bytes read. Oops.
+ (stream_write): Same here.
+
+2003-07-17 Timo Sirainen <tss@iki.fi>
+
+ ** See bug #42573
+
+ * providers/imap/camel-imap-folder.c (do_append): Only free the
+ response after we have finished the literal request, otherwise we
+ could try processing folder updates incorrectly.
+
+2003-07-14 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.h: Add the CamelMimePartEncodingType definition
+ here.
+
+ * camel-mime-part.h: Remove the CamelMimePartEncodingType
+ definition.
+
+ * camel-mime-part-utils.c
+ (simple_data_wrapper_construct_from_parser): Don't do any of the
+ auto-detection we used to do here anymore. Just read the content
+ into a memory buffer and record the encoding type.
+ (camel_mime_part_construct_content_from_parser): Don't mangle the
+ Content-Type struct here anymore.
+
+ * camel-data-wrapper.c (camel_data_wrapper_init): Init encoding to
+ DEFAULT.
+ (write_to_stream): If the stream needs to be decoded, decode it.
+
+2003-07-15 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-stream-fs.c (stream_read): If we read 0 bytes, then set
+ eos to TRUE.
+
+2003-07-09 Jeffrey Stedfast <fejj@ximian.com>
+
+ Get rid of the #ifdef ENABLE_THREADS since we no longer plan to
+ support/maintain this.
+
+ * providers/nntp/camel-nntp-store.c: Here.
+
+ * providers/nntp/camel-nntp-newsrc.c: And here.
+
+ * providers/nntp/camel-nntp-folder.c: Same.
+
+ * providers/local/camel-local-folder.c: And here.
+
+ * camel-block-file.c: Here too.
+
+ * camel.c: Same.
+
+ * camel-certdb.c: Here too.
+
+ * camel-charset-map.c: And here.
+
+ * camel-cipher-context.c: "
+
+ * camel-data-wrapper.c: "
+
+ * camel-digest-folder.c: "
+
+ * camel-exception.c: "
+
+ * camel-folder.c: "
+
+ * camel-folder-summary.c: "
+
+ * camel-lock-client.c: "
+
+ * camel-mime-utils.c: "
+
+ * camel-object.c: "
+
+ * camel-operation.c: "
+
+ * camel-partition-table.c: "
+
+ * camel-sasl-popb4smtp.c: "
+
+ * camel-service.c: "
+
+ * camel-session.c: "
+
+ * camel-store.c: "
+
+ * camel-store-summary.c: "
+
+ * camel-text-index.c: "
+
+ * camel-transport.c: "
+
+ * camel-vee-folder.c: "
+
+ * camel-tcp-stream-openssl.c: Removed pthread.h, it isn't needed.
+
+2003-07-09 Larry Ewing <lewing@ximian.com>
+
+ * camel.h: remove reference to camel-pgp-mime.h
+
+2003-07-08 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-pgp-mime.[c,h]: Removed.
+
+ * camel-iconv.c: Updated (new copy/paste from e-iconv).
+
+ * camel-block-file.c (camel_block_file_get_block): Use
+ camel_read() rather than libc read.
+
+ * camel-tcp-stream-raw.c (stream_read): Use camel_read().
+ (stream_write): Use camel_write().
+
+ * camel-stream-fs.c (stream_read): Use camel_read().
+ (stream_write): Use camel_write().
+
+2003-07-07 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/nntp/camel-nntp-folder.c (camel_nntp_folder_new): Use
+ camel_mkdir().
+
+ * providers/imap/camel-imap-folder.c (camel_imap_folder_new): Use
+ camel_mkdir().
+
+ * camel-session.c (get_storage_path): Use camel_mkdir().
+
+ * camel-store.c (camel_mkdir_hier): Removed.
+
+ * camel-data-cache.c (camel_data_cache_new): Updated to use
+ camel_mkdir().
+ (data_cache_path): Same.
+
+ * camel-file-utils.c (camel_mkdir): Renamed and documented.
+ (camel_file_util_safe_filename): Documented.
+ (camel_read): Moved here from camel-io.c
+ (camel_write): Same.
+
+ * camel-io.[c,h]: Removed.
+
+ * camel-uid-cache.c (camel_uid_cache_new): Use the
+ camel-file-utils.c version of mkdir.
+
+2003-07-07 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-session.c (camel_session_init): Updated for string-utils
+ namespace changes.
+
+ * camel-provider.c: Updated for string-utils namespace changes.
+
+ * camel-mime-part.c (init_header_name_table): Updated for
+ string-utils namespace changes.
+
+ * camel-mime-message.c (camel_mime_message_class_init): Updated
+ for string-utils namespace changes.
+ (camel_mime_message_init): Same.
+
+ * camel-mime-filter-enriched.c
+ (camel_mime_filter_enriched_class_init): Updated for string-utils
+ namespace changes.
+
+ * camel-folder-summary.c (camel_folder_summary_init): Updated for
+ string-utils namespace changes.
+
+ * camel-string-utils.[c,h]: Renamed from string-utils.[c,h] and
+ also namespaced all functions.
+
+2003-07-01 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-sasl-digest-md5.c (digest_response): Don't quote the
+ charset value, the qop value, nor the response value. Fixes bug
+ #45712.
+
+2003-07-01 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-utils.c (header_format_date): Use gmtime_r() instead
+ of using gmtime() and memcpy() to try and be "atomic".
+
+2003-06-30 Dan Winship <danw@ximian.com>
+
+ * camel-folder-search.c (camel_folder_search_finalize): free the
+ summary hash
+
+2003-06-24 David Woodhouse <dwmw2@infradead.org>
+
+ * camel-mime-utils.c (header_format_date): Put day of week into
+ outgoing email.
+
+
+2003-06-25 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-folder-summary.h: Added prototype for
+ camel_folder_summary_remove_range().
+
+2003-06-25 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #45386
+
+ * camel-service.c (camel_gethostbyname, camel_gethostbyaddr): Make
+ sure we have an exception that we test against.
+
+2003-06-20 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #43887
+
+ * camel-mime-filter-enriched.c (camel_enriched_to_html): simple
+ wrapper to convert enriched to html in one go.
+
+2003-06-18 Not Zed <NotZed@Ximian.com>
+
+ * camel-service.c (get_hostbyaddr, get_hostbyname): if we got
+ cancelled, the message is floating, so free it.
+ (struct _lookup_msg): Add a cancelled tag.
+ (camel_gethostbyname, camel_gethostbyaddr): if we get a
+ failure/cancel, cancel the lookup thread and detach, so we dont
+ have to wait for it to return. cleanup changed to handle the case
+ where we didn't get a reply message.
+
+2003-06-13 Jeffrey Stedfast <fejj@ximian.com>
+
+ * providers/pop3/camel-pop3-folder.c (pop3_finalize): Made static
+ (to match the prototype).
+
+2003-06-13 Larry Ewing <lewing@ximian.com>
+
+ * camel-folder-thread.c (camel_folder_thread_messages_apply):
+ don't leak the summary when reloading it. Fixes a very large
+ leak.
+
+2003-06-17 Not Zed <NotZed@Ximian.com>
+
+ * camel-vee-folder.c (vee_folder_remove_folder): Calculate ranges
+ to remove folder info's more efficiently. affects shutdown
+ performance on big vfolders signifinantly.
+ (vee_folder_build_folder): do the same here, when rebuilding a
+ folder's definition.
+
+ * camel-folder-summary.c (camel_folder_summary_remove_index): new
+ function to drop a range of index entries in one hit.
+
+2003-06-16 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #31745
+
+ * providers/imap/camel-imap-store-summary.c
+ (camel_imap_store_summary_namespace_new): Workaround a shell bug -
+ if the namespace has '#' in it, then strip it.
+
+2003-06-16 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #44322
+
+ * providers/imap/camel-imap-command.c (imap_command_strdup_vprintf):
+ If we are outputting a folder name, make sure we calculate buffer
+ size based on the raw/utf7 version
+
+ ** See bug #44121
+
+ * camel-multipart-signed.c (signed_get_part): If we can't parse
+ the content, but we have a stream, just use that as the content.
+
+2003-06-05 Jeffrey Stedfast <fejj@ximian.com>
+
+ Fix for bug #40788.
+
+ * providers/pop3/camel-pop3-engine.c (camel_pop3_engine_new): Now
+ takes a flags argument. Currently there is only 1 flag which can
+ be used to disable Pop3 server extensions.
+ (get_capabilities): Don't check for Pop3 server extensions if the
+ DISABLE_EXTENSIONS flag is set on the engine.
+ (camel_pop3_engine_iterate): If we get a response that is neither
+ +OK nor -ERR, default to treating it like a -ERR.
+
+ * providers/pop3/camel-pop3-store.c (connect_to_server): Check for
+ the disable_extensions param.
+
+ * providers/pop3/camel-pop3-provider.c: Define a checkbox to
+ disable all POP3 extension support.
+
+2003-06-11 Jeffrey Stedfast <fejj@ximian.com>
+
+ Partial fix for bug #44457.
+
+ * camel-mime-part-utils.c
+ (simple_data_wrapper_construct_from_parser): Make sure to set
+ rawtext to FALSE if we successfully convert the text to UTF-8.
+
+ * camel-data-wrapper.c (camel_data_wrapper_init): Default the
+ value of rawtext to TRUE instead of FALSE. This way if the mailer
+ decides to try displaying a non-textual part as text, it knows
+ that it needs to convert the content to UTF-8.
+
+2003-06-04 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-uid-cache.c (camel_uid_cache_new): Create the directory
+ with mode 0777 and the cache file itself with mode 0666. Let the
+ user's umask filter the permissions. Instead of saving the fd on
+ the Cache object, instead save the filename. Use camel_read()
+ instead of expecting read() to just always work without getting an
+ EINTR/etc.
+ (maybe_write_uid): Don't do anything if cache->fd == -1, this
+ means an error has occured in a previous callback. Replace the 2
+ calls to write() with camel_write() and check their return
+ values. If either of them fails, set cache->fd to -1 (GHashTable
+ doesn't give us a way to abort foreach'ing thru the table).
+ (camel_uid_cache_save): Save to a temp file instead of overwriting
+ the original. Do proper error checking, etc. Also added some
+ smarts about whether to try and overwrite the old cache even if we
+ haven't successfully saved all the uids in the cache.
+ (camel_uid_cache_destroy): Free the cache->filename, no longer
+ need to close (cache->fd).
+
+2003-06-11 Larry Ewing <lewing@ximian.com>
+
+ * camel-text-index.c (text_index_normalise): use g_utf8_strdown
+ properly.
+
+2003-06-09 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-mime-message.c (find_best_encoding): Add the
+ CAMEL_BESTENC_TEXT bit to enctype if the part is a text part.
+
+ * camel-mime-filter-bestenc.c
+ (camel_mime_filter_bestenc_get_best_encoding): If we have any
+ nul-bytes or if the content is non-text and contains any 8bit
+ octets, we need to use base64. Fixes bug #44344.
diff --git a/camel/camel-charset-map.c b/camel/camel-charset-map.c
new file mode 100644
index 0000000000..59f916c700
--- /dev/null
+++ b/camel/camel-charset-map.c
@@ -0,0 +1,361 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; -*- */
+/*
+ * Authors:
+ * Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ * Dan Winship <danw@ximian.com>
+ *
+ * Copyright 2000-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/*
+ if you want to build the charset map, compile this with something like:
+ gcc -DBUILD_MAP camel-charset-map.c `glib-config --cflags`
+ (plus any -I/-L/-l flags you need for iconv), then run it as
+ ./a.out > camel-charset-map-private.h
+
+ Note that the big-endian variant isn't tested...
+
+ The tables genereated work like this:
+
+ An indirect array for each page of unicode character
+ Each array element has an indirect pointer to one of the bytes of
+ the generated bitmask.
+*/
+
+#ifdef BUILD_MAP
+#include <iconv.h>
+#include <glib.h>
+
+static struct {
+ char *name;
+ unsigned int bit; /* assigned bit */
+} tables[] = {
+ /* These are the 8bit character sets (other than iso-8859-1,
+ * which is special-cased) which are supported by both other
+ * mailers and the GNOME environment. Note that the order
+ * they're listed in is the order they'll be tried in, so put
+ * the more-popular ones first.
+ */
+ { "iso-8859-2", 0 }, /* Central/Eastern European */
+ { "iso-8859-4", 0 }, /* Baltic */
+ { "koi8-r", 0 }, /* Russian */
+ { "koi8-u", 0 }, /* Ukranian */
+ { "iso-8859-5", 0 }, /* Least-popular Russian encoding */
+ { "iso-8859-7", 0 }, /* Greek */
+ { "iso-8859-8", 0 }, /* Hebrew; Visual */
+ { "iso-8859-9", 0 }, /* Turkish */
+ { "iso-8859-13", 0 }, /* Baltic again */
+ { "iso-8859-15", 0 }, /* New-and-improved iso-8859-1, but most
+ * programs that support this support UTF8
+ */
+ { "windows-1251", 0 }, /* Russian */
+ { 0, 0 }
+};
+
+unsigned int encoding_map[256 * 256];
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+#define UCS "UCS-4BE"
+#else
+#define UCS "UCS-4LE"
+#endif
+
+int main (void)
+{
+ int i, j;
+ int max, min;
+ int bit = 0x01;
+ int k;
+ int bytes;
+ iconv_t cd;
+ char in[128];
+ guint32 out[128];
+ char *inptr, *outptr;
+ size_t inlen, outlen;
+
+ /* dont count the terminator */
+ bytes = ((sizeof(tables)/sizeof(tables[0]))+7-1)/8;
+
+ for (i = 0; i < 128; i++)
+ in[i] = i + 128;
+
+ for (j = 0; tables[j].name; j++) {
+ cd = iconv_open (UCS, tables[j].name);
+ inptr = in;
+ outptr = (char *)(out);
+ inlen = sizeof (in);
+ outlen = sizeof (out);
+ while (iconv (cd, &inptr, &inlen, &outptr, &outlen) == -1) {
+ if (errno == EILSEQ) {
+ inptr++;
+ inlen--;
+ } else {
+ printf ("%s\n", strerror (errno));
+ exit (1);
+ }
+ }
+ iconv_close (cd);
+
+ for (i = 0; i < 128 - outlen / 4; i++) {
+ encoding_map[i] |= bit;
+ encoding_map[out[i]] |= bit;
+ }
+
+ tables[j].bit = bit;
+ bit <<= 1;
+ }
+
+ printf("/* This file is automatically generated: DO NOT EDIT */\n\n");
+
+ for (i=0;i<256;i++) {
+ /* first, do we need this block? */
+ for (k=0;k<bytes;k++) {
+ for (j=0;j<256;j++) {
+ if ((encoding_map[i*256 + j] & (0xff << (k*8))) != 0)
+ break;
+ }
+ if (j < 256) {
+ /* yes, dump it */
+ printf("static unsigned char m%02x%x[256] = {\n\t", i, k);
+ for (j=0;j<256;j++) {
+ printf("0x%02x, ", (encoding_map[i*256+j] >> (k*8)) & 0xff );
+ if (((j+1)&7) == 0 && j<255)
+ printf("\n\t");
+ }
+ printf("\n};\n\n");
+ }
+ }
+ }
+
+ printf("struct {\n");
+ for (k=0;k<bytes;k++) {
+ printf("\tunsigned char *bits%d;\n", k);
+ }
+ printf("} camel_charmap[256] = {\n\t");
+ for (i=0;i<256;i++) {
+ /* first, do we need this block? */
+ printf("{ ");
+ for (k=0;k<bytes;k++) {
+ for (j=0;j<256;j++) {
+ if ((encoding_map[i*256 + j] & (0xff << (k*8))) != 0)
+ break;
+ }
+ if (j < 256) {
+ printf("m%02x%x, ", i, k);
+ } else {
+ printf("0, ");
+ }
+ }
+ printf("}, ");
+ if (((i+1)&7) == 0 && i<255)
+ printf("\n\t");
+ }
+ printf("\n};\n\n");
+
+ printf("struct {\n\tconst char *name;\n\tunsigned int bit;\n} camel_charinfo[] = {\n");
+ for (j=0;tables[j].name;j++) {
+ printf("\t{ \"%s\", 0x%04x },\n", tables[j].name, tables[j].bit);
+ }
+ printf("};\n\n");
+
+ printf("#define charset_mask(x) \\\n");
+ for (k=0;k<bytes;k++) {
+ if (k!=0)
+ printf("\t| ");
+ else
+ printf("\t");
+ printf("(camel_charmap[(x)>>8].bits%d?camel_charmap[(x)>>8].bits%d[(x)&0xff]<<%d:0)", k, k, k*8);
+ if (k<bytes-1)
+ printf("\t\\\n");
+ }
+ printf("\n\n");
+
+ return 0;
+}
+
+#else
+
+#include "camel-charset-map.h"
+#include "camel-charset-map-private.h"
+
+#include <gal/util/e-iconv.h>
+
+#include <glib.h>
+#include <locale.h>
+#include <ctype.h>
+#include <pthread.h>
+#ifdef HAVE_CODESET
+#include <langinfo.h>
+#endif
+
+void
+camel_charset_init (CamelCharset *c)
+{
+ c->mask = (unsigned int) ~0;
+ c->level = 0;
+}
+
+void
+camel_charset_step (CamelCharset *c, const char *in, int len)
+{
+ register unsigned int mask;
+ register int level;
+ const char *inptr = in, *inend = in+len;
+
+ mask = c->mask;
+ level = c->level;
+
+ /* check what charset a given string will fit in */
+ while (inptr < inend) {
+ gunichar c;
+ const char *newinptr;
+ newinptr = g_utf8_next_char(inptr);
+ c = g_utf8_get_char(inptr);
+ if (newinptr == NULL || !g_unichar_validate (c)) {
+ inptr++;
+ continue;
+ }
+
+ inptr = newinptr;
+ if (c<=0xffff) {
+ mask &= charset_mask(c);
+
+ if (c>=128 && c<256)
+ level = MAX(level, 1);
+ else if (c>=256)
+ level = MAX(level, 2);
+ } else {
+ mask = 0;
+ level = MAX(level, 2);
+ }
+ }
+
+ c->mask = mask;
+ c->level = level;
+}
+
+/* gets the best charset from the mask of chars in it */
+static const char *
+camel_charset_best_mask(unsigned int mask)
+{
+ const char *locale_lang, *lang;
+ int i;
+
+ locale_lang = e_iconv_locale_language ();
+ for (i = 0; i < G_N_ELEMENTS (camel_charinfo); i++) {
+ if (camel_charinfo[i].bit & mask) {
+ lang = e_iconv_charset_language (camel_charinfo[i].name);
+
+ if (!lang || (locale_lang && !strncmp (locale_lang, lang, 2)))
+ return camel_charinfo[i].name;
+ }
+ }
+
+ return "UTF-8";
+}
+
+const char *
+camel_charset_best_name (CamelCharset *charset)
+{
+ if (charset->level == 1)
+ return "ISO-8859-1";
+ else if (charset->level == 2)
+ return camel_charset_best_mask (charset->mask);
+ else
+ return NULL;
+
+}
+
+/* finds the minimum charset for this string NULL means US-ASCII */
+const char *
+camel_charset_best (const char *in, int len)
+{
+ CamelCharset charset;
+
+ camel_charset_init (&charset);
+ camel_charset_step (&charset, in, len);
+ return camel_charset_best_name (&charset);
+}
+
+
+/**
+ * camel_charset_iso_to_windows:
+ * @isocharset: a canonicalised ISO charset
+ *
+ * Returns the equivalent Windows charset.
+ **/
+const char *
+camel_charset_iso_to_windows (const char *isocharset)
+{
+ /* According to http://czyborra.com/charsets/codepages.html,
+ * the charset mapping is as follows:
+ *
+ * us-ascii maps to windows-cp1252
+ * iso-8859-1 maps to windows-cp1252
+ * iso-8859-2 maps to windows-cp1250
+ * iso-8859-3 maps to windows-cp????
+ * iso-8859-4 maps to windows-cp????
+ * iso-8859-5 maps to windows-cp1251
+ * iso-8859-6 maps to windows-cp1256
+ * iso-8859-7 maps to windows-cp1253
+ * iso-8859-8 maps to windows-cp1255
+ * iso-8859-9 maps to windows-cp1254
+ * iso-8859-10 maps to windows-cp????
+ * iso-8859-11 maps to windows-cp????
+ * iso-8859-12 maps to windows-cp????
+ * iso-8859-13 maps to windows-cp1257
+ *
+ * Assumptions:
+ * - I'm going to assume that since iso-8859-4 and
+ * iso-8859-13 are Baltic that it also maps to
+ * windows-cp1257.
+ */
+
+ if (!g_ascii_strcasecmp (isocharset, "iso-8859-1") || !g_ascii_strcasecmp (isocharset, "us-ascii"))
+ return "windows-cp1252";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-2"))
+ return "windows-cp1250";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-4"))
+ return "windows-cp1257";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-5"))
+ return "windows-cp1251";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-6"))
+ return "windows-cp1256";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-7"))
+ return "windows-cp1253";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-8"))
+ return "windows-cp1255";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-9"))
+ return "windows-cp1254";
+ else if (!g_ascii_strcasecmp (isocharset, "iso-8859-13"))
+ return "windows-cp1257";
+
+ return isocharset;
+}
+
+#endif /* !BUILD_MAP */
diff --git a/camel/camel-filter-driver.c b/camel/camel-filter-driver.c
new file mode 100644
index 0000000000..af65d5ba7f
--- /dev/null
+++ b/camel/camel-filter-driver.c
@@ -0,0 +1,1526 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include "camel-filter-driver.h"
+#include "camel-filter-search.h"
+
+#include "camel-service.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-mem.h"
+#include "camel-mime-message.h"
+
+#include "camel-debug.h"
+
+#include "e-util/e-sexp.h"
+#include "e-util/e-memory.h"
+#include "e-util/e-msgport.h" /* for edlist */
+
+#define d(x)
+
+/* an invalid pointer */
+#define FOLDER_INVALID ((void *)~0)
+
+/* type of status for a log report */
+enum filter_log_t {
+ FILTER_LOG_NONE,
+ FILTER_LOG_START, /* start of new log entry */
+ FILTER_LOG_ACTION, /* an action performed */
+ FILTER_LOG_END, /* end of log */
+};
+
+/* list of rule nodes */
+struct _filter_rule {
+ struct _filter_rule *next;
+ struct _filter_rule *prev;
+
+ char *match;
+ char *action;
+ char *name;
+};
+
+struct _CamelFilterDriverPrivate {
+ GHashTable *globals; /* global variables */
+
+ CamelSession *session;
+
+ CamelFolder *defaultfolder; /* defualt folder */
+
+ CamelFilterStatusFunc *statusfunc; /* status callback */
+ void *statusdata; /* status callback data */
+
+ CamelFilterShellFunc *shellfunc; /* execute shell command callback */
+ void *shelldata; /* execute shell command callback data */
+
+ CamelFilterPlaySoundFunc *playfunc; /* play-sound command callback */
+ void *playdata; /* play-sound command callback data */
+
+ CamelFilterSystemBeepFunc *beep; /* system beep callback */
+ void *beepdata; /* system beep callback data */
+
+ /* for callback */
+ CamelFilterGetFolderFunc get_folder;
+ void *data;
+
+ /* run-time data */
+ GHashTable *folders; /* folders that message has been copied to */
+ int closed; /* close count */
+ GHashTable *forwards; /* addresses that have been forwarded the message */
+ GHashTable *only_once; /* actions to run only-once */
+
+ gboolean terminated; /* message processing was terminated */
+ gboolean deleted; /* message was marked for deletion */
+ gboolean copied; /* message was copied to some folder or another */
+
+ CamelMimeMessage *message; /* input message */
+ CamelMessageInfo *info; /* message summary info */
+ const char *uid; /* message uid */
+ CamelFolder *source; /* message source folder */
+ gboolean modified; /* has the input message been modified? */
+
+ FILE *logfile; /* log file */
+
+ EDList rules; /* list of _filter_rule structs */
+
+ CamelException *ex;
+
+ /* evaluator */
+ ESExp *eval;
+};
+
+#define _PRIVATE(o) (((CamelFilterDriver *)(o))->priv)
+
+static void camel_filter_driver_class_init (CamelFilterDriverClass *klass);
+static void camel_filter_driver_init (CamelFilterDriver *obj);
+static void camel_filter_driver_finalise (CamelObject *obj);
+
+static void camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const char *desc, ...);
+
+static CamelFolder *open_folder (CamelFilterDriver *d, const char *folder_url);
+static int close_folders (CamelFilterDriver *d);
+
+static ESExpResult *do_delete (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *mark_forward (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_copy (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_move (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_stop (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_colour (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_score (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_adjust_score(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *set_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *unset_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_shell (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_beep (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *play_sound (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *do_only_once (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+static ESExpResult *pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *);
+
+/* these are our filter actions - each must have a callback */
+static struct {
+ char *name;
+ ESExpFunc *func;
+ int type; /* set to 1 if a function can perform shortcut evaluation, or
+ doesn't execute everything, 0 otherwise */
+} symbols[] = {
+ { "delete", (ESExpFunc *) do_delete, 0 },
+ { "forward-to", (ESExpFunc *) mark_forward, 0 },
+ { "copy-to", (ESExpFunc *) do_copy, 0 },
+ { "move-to", (ESExpFunc *) do_move, 0 },
+ { "stop", (ESExpFunc *) do_stop, 0 },
+ { "set-colour", (ESExpFunc *) do_colour, 0 },
+ { "set-score", (ESExpFunc *) do_score, 0 },
+ { "adjust-score", (ESExpFunc *) do_adjust_score, 0 },
+ { "set-system-flag", (ESExpFunc *) set_flag, 0 },
+ { "unset-system-flag", (ESExpFunc *) unset_flag, 0 },
+ { "pipe-message", (ESExpFunc *) pipe_message, 0 },
+ { "shell", (ESExpFunc *) do_shell, 0 },
+ { "beep", (ESExpFunc *) do_beep, 0 },
+ { "play-sound", (ESExpFunc *) play_sound, 0 },
+ { "only-once", (ESExpFunc *) do_only_once, 0 }
+};
+
+static CamelObjectClass *camel_filter_driver_parent;
+
+CamelType
+camel_filter_driver_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (CAMEL_OBJECT_TYPE,
+ "CamelFilterDriver",
+ sizeof (CamelFilterDriver),
+ sizeof (CamelFilterDriverClass),
+ (CamelObjectClassInitFunc) camel_filter_driver_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_filter_driver_init,
+ (CamelObjectFinalizeFunc) camel_filter_driver_finalise);
+ }
+
+ return type;
+}
+
+static void
+camel_filter_driver_class_init (CamelFilterDriverClass *klass)
+{
+ /*CamelObjectClass *object_class = (CamelObjectClass *) klass;*/
+
+ camel_filter_driver_parent = camel_type_get_global_classfuncs(camel_object_get_type());
+}
+
+static void
+camel_filter_driver_init (CamelFilterDriver *obj)
+{
+ struct _CamelFilterDriverPrivate *p;
+ int i;
+
+ p = _PRIVATE (obj) = g_malloc0 (sizeof (*p));
+
+ e_dlist_init(&p->rules);
+
+ p->eval = e_sexp_new ();
+ /* Load in builtin symbols */
+ for (i = 0; i < sizeof (symbols) / sizeof (symbols[0]); i++) {
+ if (symbols[i].type == 1) {
+ e_sexp_add_ifunction (p->eval, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, obj);
+ } else {
+ e_sexp_add_function (p->eval, 0, symbols[i].name, symbols[i].func, obj);
+ }
+ }
+
+ p->globals = g_hash_table_new (g_str_hash, g_str_equal);
+
+ p->folders = g_hash_table_new (g_str_hash, g_str_equal);
+
+ p->only_once = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+free_hash_strings (void *key, void *value, void *data)
+{
+ g_free (key);
+ g_free (value);
+}
+
+static void
+camel_filter_driver_finalise (CamelObject *obj)
+{
+ CamelFilterDriver *driver = (CamelFilterDriver *) obj;
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ struct _filter_rule *node;
+
+ /* close all folders that were opened for appending */
+ close_folders (driver);
+ g_hash_table_destroy (p->folders);
+
+ g_hash_table_foreach (p->globals, free_hash_strings, driver);
+ g_hash_table_destroy (p->globals);
+
+ g_hash_table_foreach (p->only_once, free_hash_strings, driver);
+ g_hash_table_destroy (p->only_once);
+
+ e_sexp_unref(p->eval);
+
+ if (p->defaultfolder) {
+ camel_folder_thaw (p->defaultfolder);
+ camel_object_unref (p->defaultfolder);
+ }
+
+ while ((node = (struct _filter_rule *)e_dlist_remhead(&p->rules))) {
+ g_free(node->match);
+ g_free(node->action);
+ g_free(node->name);
+ g_free(node);
+ }
+
+ camel_object_unref(p->session);
+
+ g_free (p);
+}
+
+/**
+ * camel_filter_driver_new:
+ *
+ * Return value: A new CamelFilterDriver object
+ **/
+CamelFilterDriver *
+camel_filter_driver_new (CamelSession *session)
+{
+ CamelFilterDriver *d = (CamelFilterDriver *)camel_object_new(camel_filter_driver_get_type());
+
+ d->priv->session = session;
+ camel_object_ref((CamelObject *)session);
+
+ return d;
+}
+
+void
+camel_filter_driver_set_folder_func (CamelFilterDriver *d, CamelFilterGetFolderFunc get_folder, void *data)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ p->get_folder = get_folder;
+ p->data = data;
+}
+
+void
+camel_filter_driver_set_logfile (CamelFilterDriver *d, FILE *logfile)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ p->logfile = logfile;
+}
+
+void
+camel_filter_driver_set_status_func (CamelFilterDriver *d, CamelFilterStatusFunc *func, void *data)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ p->statusfunc = func;
+ p->statusdata = data;
+}
+
+void
+camel_filter_driver_set_shell_func (CamelFilterDriver *d, CamelFilterShellFunc *func, void *data)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ p->shellfunc = func;
+ p->shelldata = data;
+}
+
+void
+camel_filter_driver_set_play_sound_func (CamelFilterDriver *d, CamelFilterPlaySoundFunc *func, void *data)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ p->playfunc = func;
+ p->playdata = data;
+}
+
+void
+camel_filter_driver_set_system_beep_func (CamelFilterDriver *d, CamelFilterSystemBeepFunc *func, void *data)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ p->beep = func;
+ p->beepdata = data;
+}
+
+void
+camel_filter_driver_set_default_folder (CamelFilterDriver *d, CamelFolder *def)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+
+ if (p->defaultfolder) {
+ camel_folder_thaw (p->defaultfolder);
+ camel_object_unref (p->defaultfolder);
+ }
+
+ p->defaultfolder = def;
+
+ if (p->defaultfolder) {
+ camel_folder_freeze (p->defaultfolder);
+ camel_object_ref (p->defaultfolder);
+ }
+}
+
+void
+camel_filter_driver_add_rule(CamelFilterDriver *d, const char *name, const char *match, const char *action)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+ struct _filter_rule *node;
+
+ node = g_malloc(sizeof(*node));
+ node->match = g_strdup(match);
+ node->action = g_strdup(action);
+ node->name = g_strdup(name);
+ e_dlist_addtail(&p->rules, (EDListNode *)node);
+}
+
+int
+camel_filter_driver_remove_rule_by_name (CamelFilterDriver *d, const char *name)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+ struct _filter_rule *node;
+
+ node = (struct _filter_rule *) p->rules.head;
+ while (node->next) {
+ if (!strcmp (node->name, name)) {
+ e_dlist_remove ((EDListNode *) node);
+ g_free (node->match);
+ g_free (node->action);
+ g_free (node->name);
+ g_free (node);
+
+ return 0;
+ }
+
+ node = node->next;
+ }
+
+ return -1;
+}
+
+static void
+report_status (CamelFilterDriver *driver, enum camel_filter_status_t status, int pc, const char *desc, ...)
+{
+ /* call user-defined status report function */
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ va_list ap;
+ char *str;
+
+ if (p->statusfunc) {
+ va_start (ap, desc);
+ str = g_strdup_vprintf (desc, ap);
+ p->statusfunc (driver, status, pc, str, p->statusdata);
+ g_free (str);
+ }
+}
+
+
+#if 0
+void
+camel_filter_driver_set_global (CamelFilterDriver *d, const char *name, const char *value)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (d);
+ char *oldkey, *oldvalue;
+
+ if (g_hash_table_lookup_extended (p->globals, name, (void *)&oldkey, (void *)&oldvalue)) {
+ g_free (oldvalue);
+ g_hash_table_insert (p->globals, oldkey, g_strdup (value));
+ } else {
+ g_hash_table_insert (p->globals, g_strdup (name), g_strdup (value));
+ }
+}
+#endif
+
+static ESExpResult *
+do_delete (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ d(fprintf (stderr, "doing delete\n"));
+ p->deleted = TRUE;
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Delete");
+
+ return NULL;
+}
+
+static ESExpResult *
+mark_forward (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ /*struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);*/
+
+ d(fprintf (stderr, "marking message for forwarding\n"));
+ /* FIXME: do stuff here */
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Forward");
+
+ return NULL;
+}
+
+static ESExpResult *
+do_copy (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ int i;
+
+ d(fprintf (stderr, "copying message...\n"));
+
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == ESEXP_RES_STRING) {
+ /* open folders we intent to copy to */
+ char *folder = argv[i]->value.string;
+ CamelFolder *outbox;
+
+ outbox = open_folder (driver, folder);
+ if (!outbox)
+ break;
+
+ if (outbox == p->source)
+ break;
+
+ if (!p->modified && p->uid && p->source && camel_folder_has_summary_capability (p->source)) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (uids, (char *) p->uid);
+ camel_folder_transfer_messages_to (p->source, uids, outbox, NULL, FALSE, p->ex);
+ g_ptr_array_free (uids, TRUE);
+ } else {
+ if (p->message == NULL)
+ p->message = camel_folder_get_message (p->source, p->uid, p->ex);
+
+ if (!p->message)
+ continue;
+
+ camel_folder_append_message (outbox, p->message, p->info, NULL, p->ex);
+ }
+
+ if (!camel_exception_is_set (p->ex))
+ p->copied = TRUE;
+
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Copy to folder %s",
+ folder);
+ }
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+do_move (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ int i;
+
+ d(fprintf (stderr, "moving message...\n"));
+
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type == ESEXP_RES_STRING) {
+ /* open folders we intent to move to */
+ char *folder = argv[i]->value.string;
+ CamelFolder *outbox;
+
+ outbox = open_folder (driver, folder);
+ if (!outbox)
+ break;
+
+ if (outbox == p->source)
+ break;
+
+ if (!p->modified && p->uid && p->source && camel_folder_has_summary_capability (p->source)) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (uids, (char *) p->uid);
+ camel_folder_transfer_messages_to (p->source, uids, outbox, NULL, FALSE, p->ex);
+ g_ptr_array_free (uids, TRUE);
+ } else {
+ if (p->message == NULL)
+ p->message = camel_folder_get_message (p->source, p->uid, p->ex);
+
+ if (!p->message)
+ continue;
+
+ camel_folder_append_message (outbox, p->message, p->info, NULL, p->ex);
+ }
+
+ if (!camel_exception_is_set (p->ex)) {
+ /* a 'move' is a copy & delete */
+ p->copied = TRUE;
+ p->deleted = TRUE;
+ }
+
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Move to folder %s",
+ folder);
+ }
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+do_stop (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Stopped processing");
+ d(fprintf (stderr, "terminating message processing\n"));
+ p->terminated = TRUE;
+
+ return NULL;
+}
+
+static ESExpResult *
+do_colour (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ d(fprintf (stderr, "setting colour tag\n"));
+ if (argc > 0 && argv[0]->type == ESEXP_RES_STRING) {
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ camel_folder_set_message_user_tag (p->source, p->uid, "colour", argv[0]->value.string);
+ else
+ camel_tag_set (&p->info->user_tags, "colour", argv[0]->value.string);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Set colour to %s", argv[0]->value.string);
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+do_score (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ d(fprintf (stderr, "setting score tag\n"));
+ if (argc > 0 && argv[0]->type == ESEXP_RES_INT) {
+ char *value;
+
+ value = g_strdup_printf ("%d", argv[0]->value.number);
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ camel_folder_set_message_user_tag (p->source, p->uid, "score", value);
+ else
+ camel_tag_set (&p->info->user_tags, "score", value);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Set score to %d", argv[0]->value.number);
+ g_free (value);
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+do_adjust_score(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE(driver);
+
+ d(fprintf (stderr, "adjusting score tag\n"));
+ if (argc > 0 && argv[0]->type == ESEXP_RES_INT) {
+ char *value;
+ int old;
+
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ value = (char *)camel_folder_get_message_user_tag (p->source, p->uid, "score");
+ else
+ value = (char *)camel_tag_get(&p->info->user_tags, "score");
+ old = value?atoi(value):0;
+ value = g_strdup_printf ("%d", old+argv[0]->value.number);
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ camel_folder_set_message_user_tag (p->source, p->uid, "score", value);
+ else
+ camel_tag_set (&p->info->user_tags, "score", value);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Adjust score (%d) to %s", argv[0]->value.number, value);
+ g_free (value);
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+set_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ guint32 flags;
+
+ d(fprintf (stderr, "setting flag\n"));
+ if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
+ flags = camel_system_flag (argv[0]->value.string);
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ camel_folder_set_message_flags (p->source, p->uid, flags, ~0);
+ else
+ p->info->flags |= flags | CAMEL_MESSAGE_FOLDER_FLAGGED;
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Set %s flag", argv[0]->value.string);
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+unset_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ guint32 flags;
+
+ d(fprintf (stderr, "unsetting flag\n"));
+ if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
+ flags = camel_system_flag (argv[0]->value.string);
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ camel_folder_set_message_flags (p->source, p->uid, flags, 0);
+ else
+ p->info->flags = (p->info->flags & ~flags) | CAMEL_MESSAGE_FOLDER_FLAGGED;
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Unset %s flag", argv[0]->value.string);
+ }
+
+ return NULL;
+}
+
+static int
+pipe_to_system (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ int result, status, fds[4], i;
+ CamelMimeMessage *message = NULL;
+ CamelMimeParser *parser;
+ CamelStream *stream, *mem;
+ pid_t pid;
+
+ if (argc < 1 || argv[0]->value.string[0] == '\0')
+ return 0;
+
+ /* make sure we have the message... */
+ if (p->message == NULL) {
+ if (!(p->message = camel_folder_get_message (p->source, p->uid, p->ex)))
+ return -1;
+ }
+
+ for (i = 0; i < 4; i++)
+ fds[i] = -1;
+
+ for (i = 0; i < 4; i += 2) {
+ if (pipe (fds + i) == -1) {
+ camel_exception_setv (p->ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to create pipe to '%s': %s"),
+ argv[0]->value.string, g_strerror (errno));
+
+ for (i = 0; i < 4; i++) {
+ if (fds[i] == -1)
+ break;
+ close (fds[i]);
+ }
+
+ return -1;
+ }
+ }
+
+ if (!(pid = fork ())) {
+ /* child process */
+ GPtrArray *args;
+ int maxfd, fd;
+
+ fd = open ("/dev/null", O_WRONLY);
+
+ if (dup2 (fds[0], STDIN_FILENO) < 0 ||
+ dup2 (fds[3], STDOUT_FILENO) < 0 ||
+ dup2 (fd, STDERR_FILENO) < 0)
+ _exit (255);
+
+ setsid ();
+
+ maxfd = sysconf (_SC_OPEN_MAX);
+ for (fd = 3; fd < maxfd; fd++)
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+
+ args = g_ptr_array_new ();
+ for (i = 0; i < argc; i++)
+ g_ptr_array_add (args, argv[i]->value.string);
+ g_ptr_array_add (args, NULL);
+
+ execvp (argv[0]->value.string, (char **) args->pdata);
+
+ g_ptr_array_free (args, TRUE);
+
+ d(printf ("Could not execute %s: %s\n", argv[0]->value.string, g_strerror (errno)));
+ _exit (255);
+ } else if (pid < 0) {
+ camel_exception_setv (p->ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to create child process '%s': %s"),
+ argv[0]->value.string, g_strerror (errno));
+ return -1;
+ }
+
+ /* parent process */
+ close (fds[0]);
+ close (fds[3]);
+
+ stream = camel_stream_fs_new_with_fd (fds[1]);
+ if (camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (p->message), stream) == -1) {
+ camel_object_unref (stream);
+ close (fds[2]);
+ goto wait;
+ }
+
+ if (camel_stream_flush (stream) == -1) {
+ camel_object_unref (stream);
+ close (fds[2]);
+ goto wait;
+ }
+
+ camel_object_unref (stream);
+
+ stream = camel_stream_fs_new_with_fd (fds[2]);
+ mem = camel_stream_mem_new ();
+ if (camel_stream_write_to_stream (stream, mem) == -1) {
+ camel_object_unref (stream);
+ camel_object_unref (mem);
+ goto wait;
+ }
+
+ camel_object_unref (stream);
+ camel_stream_reset (mem);
+
+ parser = camel_mime_parser_new ();
+ camel_mime_parser_init_with_stream (parser, mem);
+ camel_mime_parser_scan_from (parser, FALSE);
+ camel_object_unref (mem);
+
+ message = camel_mime_message_new ();
+ if (camel_mime_part_construct_from_parser ((CamelMimePart *) message, parser) == -1) {
+ camel_exception_setv (p->ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Invalid message stream received from %s: %s"),
+ argv[0]->value.string,
+ g_strerror (camel_mime_parser_errno (parser)));
+ camel_object_unref (message);
+ message = NULL;
+ } else {
+ camel_object_unref (p->message);
+ p->message = message;
+ p->modified = TRUE;
+ }
+
+ camel_object_unref (parser);
+
+ wait:
+
+ result = waitpid (pid, &status, 0);
+
+ if (result == -1 && errno == EINTR) {
+ /* child process is hanging... */
+ kill (pid, SIGTERM);
+ sleep (1);
+ result = waitpid (pid, &status, WNOHANG);
+ if (result == 0) {
+ /* ...still hanging, set phasers to KILL */
+ kill (pid, SIGKILL);
+ sleep (1);
+ result = waitpid (pid, &status, WNOHANG);
+ }
+ }
+
+ if (message && result != -1 && WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else
+ return -1;
+}
+
+static ESExpResult *
+pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ int i;
+
+ /* make sure all args are strings */
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type != ESEXP_RES_STRING)
+ return NULL;
+ }
+
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Piping message to %s", argv[0]->value.string);
+ pipe_to_system (f, argc, argv, driver);
+
+ return NULL;
+}
+
+static ESExpResult *
+do_shell (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ GString *command;
+ GPtrArray *args;
+ int i;
+
+ d(fprintf (stderr, "executing shell command\n"));
+
+ command = g_string_new ("");
+
+ args = g_ptr_array_new ();
+
+ /* make sure all args are strings */
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type != ESEXP_RES_STRING)
+ goto done;
+
+ g_ptr_array_add (args, argv[i]->value.string);
+
+ g_string_append (command, argv[i]->value.string);
+ g_string_append_c (command, ' ');
+ }
+
+ g_string_truncate (command, command->len - 1);
+
+ if (p->shellfunc && argc >= 1) {
+ p->shellfunc (driver, argc, (char **) args->pdata, p->shelldata);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Executing shell command: [%s]",
+ command->str);
+ }
+
+ done:
+
+ g_ptr_array_free (args, TRUE);
+ g_string_free (command, TRUE);
+
+ return NULL;
+}
+
+static ESExpResult *
+do_beep (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ d(fprintf (stderr, "beep\n"));
+
+ if (p->beep) {
+ p->beep (driver, p->beepdata);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Beep");
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+play_sound (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ d(fprintf (stderr, "play sound\n"));
+
+ if (p->playfunc && argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
+ p->playfunc (driver, argv[0]->value.string, p->playdata);
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Play sound");
+ }
+
+ return NULL;
+}
+
+static ESExpResult *
+do_only_once (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ d(fprintf (stderr, "only once\n"));
+
+ if (argc == 2 && !g_hash_table_lookup (p->only_once, argv[0]->value.string))
+ g_hash_table_insert (p->only_once, g_strdup (argv[0]->value.string),
+ g_strdup (argv[1]->value.string));
+
+ return NULL;
+}
+
+static CamelFolder *
+open_folder (CamelFilterDriver *driver, const char *folder_url)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ CamelFolder *camelfolder;
+
+ /* we have a lookup table of currently open folders */
+ camelfolder = g_hash_table_lookup (p->folders, folder_url);
+ if (camelfolder)
+ return camelfolder == FOLDER_INVALID?NULL:camelfolder;
+
+ /* if we have a default folder, ignore exceptions. This is so
+ a bad filter rule on pop or local delivery doesn't result
+ in duplicate mails, just mail going to inbox. Otherwise,
+ we want to know about exceptions and abort processing */
+ if (p->defaultfolder) {
+ CamelException ex;
+
+ camel_exception_init (&ex);
+ camelfolder = p->get_folder (driver, folder_url, p->data, &ex);
+ camel_exception_clear (&ex);
+ } else {
+ camelfolder = p->get_folder (driver, folder_url, p->data, p->ex);
+ }
+
+ if (camelfolder) {
+ g_hash_table_insert (p->folders, g_strdup (folder_url), camelfolder);
+ camel_folder_freeze (camelfolder);
+ } else {
+ g_hash_table_insert (p->folders, g_strdup (folder_url), FOLDER_INVALID);
+ }
+
+ return camelfolder;
+}
+
+static void
+close_folder (void *key, void *value, void *data)
+{
+ CamelFolder *folder = value;
+ CamelFilterDriver *driver = data;
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ p->closed++;
+ g_free (key);
+
+ if (folder != FOLDER_INVALID) {
+ camel_folder_sync (folder, FALSE, camel_exception_is_set(p->ex)?NULL : p->ex);
+ camel_folder_thaw (folder);
+ camel_object_unref (folder);
+ }
+
+ report_status(driver, CAMEL_FILTER_STATUS_PROGRESS, g_hash_table_size(p->folders)* 100 / p->closed, _("Syncing folders"));
+}
+
+/* flush/close all folders */
+static int
+close_folders (CamelFilterDriver *driver)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ report_status(driver, CAMEL_FILTER_STATUS_PROGRESS, 0, _("Syncing folders"));
+
+ p->closed = 0;
+ g_hash_table_foreach (p->folders, close_folder, driver);
+ g_hash_table_destroy (p->folders);
+ p->folders = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* FIXME: status from driver */
+ return 0;
+}
+
+#if 0
+static void
+free_key (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+}
+#endif
+
+
+static void
+camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const char *desc, ...)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+
+ if (p->logfile) {
+ char *str = NULL;
+
+ if (desc) {
+ va_list ap;
+
+ va_start (ap, desc);
+ str = g_strdup_vprintf (desc, ap);
+ }
+
+ switch (status) {
+ case FILTER_LOG_START: {
+ /* write log header */
+ const char *subject = NULL;
+ const char *from = NULL;
+ char date[50];
+ time_t t;
+
+ /* FIXME: does this need locking? Probably */
+
+ from = camel_message_info_from (p->info);
+ subject = camel_message_info_subject (p->info);
+
+ time (&t);
+ strftime (date, 49, "%a, %d %b %Y %H:%M:%S", localtime (&t));
+ fprintf (p->logfile, "Applied filter \"%s\" to message from %s - \"%s\" at %s\n",
+ str, from ? from : "unknown", subject ? subject : "", date);
+
+ break;
+ }
+ case FILTER_LOG_ACTION:
+ fprintf (p->logfile, "Action: %s\n", str);
+ break;
+ case FILTER_LOG_END:
+ fprintf (p->logfile, "\n");
+ break;
+ default:
+ /* nothing else is loggable */
+ break;
+ }
+
+ g_free (str);
+ }
+}
+
+
+struct _run_only_once {
+ CamelFilterDriver *driver;
+ CamelException *ex;
+};
+
+static gboolean
+run_only_once (gpointer key, char *action, struct _run_only_once *data)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (data->driver);
+ CamelException *ex = data->ex;
+ ESExpResult *r;
+
+ d(printf ("evaluating: %s\n\n", action));
+
+ e_sexp_input_text (p->eval, action, strlen (action));
+ if (e_sexp_parse (p->eval) == -1) {
+ if (!camel_exception_is_set (ex))
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error parsing filter: %s: %s"),
+ e_sexp_error (p->eval), action);
+ goto done;
+ }
+
+ r = e_sexp_eval (p->eval);
+ if (r == NULL) {
+ if (!camel_exception_is_set (ex))
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error executing filter: %s: %s"),
+ e_sexp_error (p->eval), action);
+ goto done;
+ }
+
+ e_sexp_result_free (p->eval, r);
+
+ done:
+
+ g_free (key);
+ g_free (action);
+
+ return TRUE;
+}
+
+
+/**
+ * camel_filter_driver_flush:
+ * @driver:
+ * @ex:
+ *
+ * Flush all of the only-once filter actions.
+ **/
+void
+camel_filter_driver_flush (CamelFilterDriver *driver, CamelException *ex)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ struct _run_only_once data;
+
+ if (!p->only_once)
+ return;
+
+ data.driver = driver;
+ data.ex = ex;
+
+ g_hash_table_foreach_remove (p->only_once, (GHRFunc) run_only_once, &data);
+}
+
+/**
+ * camel_filter_driver_filter_mbox:
+ * @driver: CamelFilterDriver
+ * @mbox: mbox filename to be filtered
+ * @ex: exception
+ *
+ * Filters an mbox file based on rules defined in the FilterDriver
+ * object. Is more efficient as it doesn't need to open the folder
+ * through Camel directly.
+ *
+ * Returns -1 if errors were encountered during filtering,
+ * otherwise returns 0.
+ *
+ **/
+int
+camel_filter_driver_filter_mbox (CamelFilterDriver *driver, const char *mbox, const char *original_source_url, CamelException *ex)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ CamelMimeParser *mp = NULL;
+ char *source_url = NULL;
+ int fd = -1;
+ int i = 0;
+ struct stat st;
+ int status;
+ off_t last = 0;
+ int ret = -1;
+
+ fd = open (mbox, O_RDONLY);
+ if (fd == -1) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Unable to open spool folder"));
+ goto fail;
+ }
+ /* to get the filesize */
+ fstat (fd, &st);
+
+ mp = camel_mime_parser_new ();
+ camel_mime_parser_scan_from (mp, TRUE);
+ if (camel_mime_parser_init_with_fd (mp, fd) == -1) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Unable to process spool folder"));
+ goto fail;
+ }
+ fd = -1;
+
+ source_url = g_strdup_printf ("file://%s", mbox);
+
+ while (camel_mime_parser_step (mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) {
+ CamelMessageInfo *info;
+ CamelMimeMessage *msg;
+ int pc = 0;
+
+ if (st.st_size > 0)
+ pc = (int)(100.0 * ((double)camel_mime_parser_tell (mp) / (double)st.st_size));
+
+ report_status (driver, CAMEL_FILTER_STATUS_START, pc, _("Getting message %d (%d%%)"), i, pc);
+
+ msg = camel_mime_message_new ();
+ if (camel_mime_part_construct_from_parser (CAMEL_MIME_PART (msg), mp) == -1) {
+ camel_exception_set (ex, (errno==EINTR)?CAMEL_EXCEPTION_USER_CANCEL:CAMEL_EXCEPTION_SYSTEM, _("Cannot open message"));
+ report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Failed on message %d"), i);
+ camel_object_unref (msg);
+ goto fail;
+ }
+
+ info = camel_message_info_new_from_header(((CamelMimePart *)msg)->headers);
+ info->size = camel_mime_parser_tell(mp) - last;
+ last = camel_mime_parser_tell(mp);
+ status = camel_filter_driver_filter_message (driver, msg, info, NULL, NULL, source_url,
+ original_source_url ? original_source_url : source_url, ex);
+ camel_object_unref (msg);
+ if (camel_exception_is_set (ex) || status == -1) {
+ report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Failed on message %d"), i);
+ camel_message_info_free (info);
+ goto fail;
+ }
+
+ i++;
+
+ /* skip over the FROM_END state */
+ camel_mime_parser_step (mp, 0, 0);
+
+ camel_message_info_free (info);
+ }
+
+ if (p->defaultfolder) {
+ report_status(driver, CAMEL_FILTER_STATUS_PROGRESS, 100, _("Syncing folder"));
+ camel_folder_sync(p->defaultfolder, FALSE, camel_exception_is_set (ex) ? NULL : ex);
+ }
+
+ report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete"));
+
+ ret = 0;
+fail:
+ g_free (source_url);
+ if (fd != -1)
+ close (fd);
+ if (mp)
+ camel_object_unref (mp);
+
+ return -1;
+}
+
+
+/**
+ * camel_filter_driver_filter_folder:
+ * @driver: CamelFilterDriver
+ * @folder: CamelFolder to be filtered
+ * @cache: UID cache (needed for POP folders)
+ * @uids: message uids to be filtered or NULL (as a shortcut to filter all messages)
+ * @remove: TRUE to mark filtered messages as deleted
+ * @ex: exception
+ *
+ * Filters a folder based on rules defined in the FilterDriver
+ * object.
+ *
+ * Returns -1 if errors were encountered during filtering,
+ * otherwise returns 0.
+ *
+ **/
+int
+camel_filter_driver_filter_folder (CamelFilterDriver *driver, CamelFolder *folder, CamelUIDCache *cache,
+ GPtrArray *uids, gboolean remove, CamelException *ex)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ gboolean freeuids = FALSE;
+ CamelMessageInfo *info;
+ char *source_url, *service_url;
+ int status = 0;
+ CamelURL *url;
+ int i;
+
+ service_url = camel_service_get_url (CAMEL_SERVICE (camel_folder_get_parent_store (folder)));
+ url = camel_url_new (service_url, NULL);
+ g_free (service_url);
+
+ source_url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+
+ if (uids == NULL) {
+ uids = camel_folder_get_uids (folder);
+ freeuids = TRUE;
+ }
+
+ for (i = 0; i < uids->len; i++) {
+ int pc = (100 * i)/uids->len;
+
+ report_status (driver, CAMEL_FILTER_STATUS_START, pc, _("Getting message %d of %d"), i+1,
+ uids->len);
+
+ if (camel_folder_has_summary_capability (folder))
+ info = camel_folder_get_message_info (folder, uids->pdata[i]);
+ else
+ info = NULL;
+
+ status = camel_filter_driver_filter_message (driver, NULL, info, uids->pdata[i],
+ folder, source_url, source_url, ex);
+
+ if (camel_folder_has_summary_capability (folder))
+ camel_folder_free_message_info (folder, info);
+
+ if (camel_exception_is_set (ex) || status == -1) {
+ report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Failed at message %d of %d"),
+ i+1, uids->len);
+ status = -1;
+ break;
+ }
+
+ if (remove)
+ camel_folder_set_message_flags (folder, uids->pdata[i],
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, ~0);
+
+ if (cache)
+ camel_uid_cache_save_uid (cache, uids->pdata[i]);
+ }
+
+ if (p->defaultfolder) {
+ report_status (driver, CAMEL_FILTER_STATUS_PROGRESS, 100, _("Syncing folder"));
+ camel_folder_sync (p->defaultfolder, FALSE, camel_exception_is_set (ex) ? NULL : ex);
+ }
+
+ if (i == uids->len)
+ report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete"));
+
+ if (freeuids)
+ camel_folder_free_uids (folder, uids);
+
+ g_free (source_url);
+
+ return status;
+}
+
+
+struct _get_message {
+ struct _CamelFilterDriverPrivate *p;
+ const char *source_url;
+};
+
+
+static CamelMimeMessage *
+get_message_cb (void *data, CamelException *ex)
+{
+ struct _get_message *msgdata = data;
+ struct _CamelFilterDriverPrivate *p = msgdata->p;
+ const char *source_url = msgdata->source_url;
+ CamelMimeMessage *message;
+
+ if (p->message) {
+ message = p->message;
+ camel_object_ref (message);
+ } else {
+ const char *uid;
+
+ if (p->uid)
+ uid = p->uid;
+ else
+ uid = camel_message_info_uid (p->info);
+
+ message = camel_folder_get_message (p->source, uid, ex);
+ }
+
+ if (source_url && message && camel_mime_message_get_source (message) == NULL)
+ camel_mime_message_set_source (message, source_url);
+
+ return message;
+}
+
+/**
+ * camel_filter_driver_filter_message:
+ * @driver: CamelFilterDriver
+ * @message: message to filter or NULL
+ * @info: message info or NULL
+ * @uid: message uid or NULL
+ * @source: source folder or NULL
+ * @source_url: url of source folder or NULL
+ * @original_source_url: url of original source folder (pre-movemail) or NULL
+ * @ex: exception
+ *
+ * Filters a message based on rules defined in the FilterDriver
+ * object. If the source folder (@source) and the uid (@uid) are
+ * provided, the filter will operate on the CamelFolder (which in
+ * certain cases is more efficient than using the default
+ * camel_folder_append_message() function).
+ *
+ * Returns -1 if errors were encountered during filtering,
+ * otherwise returns 0.
+ *
+ **/
+int
+camel_filter_driver_filter_message (CamelFilterDriver *driver, CamelMimeMessage *message,
+ CamelMessageInfo *info, const char *uid,
+ CamelFolder *source, const char *source_url,
+ const char *original_source_url,
+ CamelException *ex)
+{
+ struct _CamelFilterDriverPrivate *p = _PRIVATE (driver);
+ struct _filter_rule *node;
+ gboolean freeinfo = FALSE;
+ gboolean filtered = FALSE;
+ ESExpResult *r;
+ int result;
+
+ /* FIXME: make me into a g_return_if_fail/g_assert or whatever... */
+ if (message == NULL && (source == NULL || uid == NULL)) {
+ g_warning ("there is no way to fetch the message using the information provided...");
+ return -1;
+ }
+
+ if (info == NULL) {
+ struct _camel_header_raw *h;
+
+ if (message) {
+ camel_object_ref (message);
+ } else {
+ message = camel_folder_get_message (source, uid, ex);
+ if (!message)
+ return -1;
+ }
+
+ h = CAMEL_MIME_PART (message)->headers;
+ info = camel_message_info_new_from_header (h);
+ freeinfo = TRUE;
+ } else {
+ if (info->flags & CAMEL_MESSAGE_DELETED)
+ return 0;
+
+ uid = camel_message_info_uid (info);
+
+ if (message)
+ camel_object_ref (message);
+ }
+
+ p->ex = ex;
+ p->terminated = FALSE;
+ p->deleted = FALSE;
+ p->copied = FALSE;
+ p->message = message;
+ p->info = info;
+ p->uid = uid;
+ p->source = source;
+
+ if (message && original_source_url && camel_mime_message_get_source (message) == NULL)
+ camel_mime_message_set_source (message, original_source_url);
+
+ node = (struct _filter_rule *) p->rules.head;
+ result = CAMEL_SEARCH_NOMATCH;
+ while (node->next && !p->terminated) {
+ struct _get_message data;
+
+ d(printf("applying rule %s\naction %s\n", node->match, node->action));
+
+ data.p = p;
+ data.source_url = original_source_url;
+
+ result = camel_filter_search_match (p->session, get_message_cb, &data, p->info,
+ original_source_url ? original_source_url : source_url,
+ node->match, p->ex);
+
+ switch (result) {
+ case CAMEL_SEARCH_ERROR:
+ goto error;
+ case CAMEL_SEARCH_MATCHED:
+ filtered = TRUE;
+ camel_filter_driver_log (driver, FILTER_LOG_START, node->name);
+
+ if (camel_debug(":filter"))
+ printf("filtering '%s' applying rule %s\n",
+ camel_message_info_subject(info)?camel_message_info_subject(info):"?no subject?", node->name);
+
+ /* perform necessary filtering actions */
+ e_sexp_input_text (p->eval, node->action, strlen (node->action));
+ if (e_sexp_parse (p->eval) == -1) {
+ camel_exception_setv (ex, 1, _("Error parsing filter: %s: %s"),
+ e_sexp_error (p->eval), node->action);
+ goto error;
+ }
+ r = e_sexp_eval (p->eval);
+ if (camel_exception_is_set(p->ex))
+ goto error;
+
+ if (r == NULL) {
+ camel_exception_setv (ex, 1, _("Error executing filter: %s: %s"),
+ e_sexp_error (p->eval), node->action);
+ goto error;
+ }
+ e_sexp_result_free (p->eval, r);
+ default:
+ break;
+ }
+
+ node = node->next;
+ }
+
+ /* *Now* we can set the DELETED flag... */
+ if (p->deleted) {
+ if (p->source && p->uid && camel_folder_has_summary_capability (p->source))
+ camel_folder_set_message_flags(p->source, p->uid, CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN, ~0);
+ else
+ info->flags |= CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_FOLDER_FLAGGED;
+ }
+
+ /* Logic: if !Moved and there exists a default folder... */
+ if (!(p->copied && p->deleted) && p->defaultfolder) {
+ /* copy it to the default inbox */
+ filtered = TRUE;
+ camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Copy to default folder");
+
+ if (camel_debug(":filter"))
+ printf("filtering '%s' copy %s to default folder\n",
+ camel_message_info_subject(info)?camel_message_info_subject(info):"?no subject?",
+ p->modified?"modified message":"");
+
+ if (!p->modified && p->uid && p->source && camel_folder_has_summary_capability (p->source)) {
+ GPtrArray *uids;
+
+ uids = g_ptr_array_new ();
+ g_ptr_array_add (uids, (char *) p->uid);
+ camel_folder_transfer_messages_to (p->source, uids, p->defaultfolder, NULL, FALSE, p->ex);
+ g_ptr_array_free (uids, TRUE);
+ } else {
+ if (p->message == NULL) {
+ p->message = camel_folder_get_message (source, uid, ex);
+ if (!p->message)
+ goto error;
+ }
+
+ camel_folder_append_message (p->defaultfolder, p->message, p->info, NULL, p->ex);
+ }
+ }
+
+ if (p->message)
+ camel_object_unref (p->message);
+
+ if (freeinfo)
+ camel_message_info_free (info);
+
+ return 0;
+
+ error:
+ if (filtered)
+ camel_filter_driver_log (driver, FILTER_LOG_END, NULL);
+
+ if (p->message)
+ camel_object_unref (p->message);
+
+ if (freeinfo)
+ camel_message_info_free (info);
+
+ return -1;
+}
diff --git a/camel/camel-filter-search.c b/camel/camel-filter-search.c
new file mode 100644
index 0000000000..9999b0ad2c
--- /dev/null
+++ b/camel/camel-filter-search.c
@@ -0,0 +1,704 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <NotZed@Ximian.com>
+ *
+ * Copyright 2000 Ximian, Inc. (www.ximian.com)
+ * Copyright 2001 Ximian Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* (from glibc headers:
+ POSIX says that <sys/types.h> must be included (by the caller) before <regex.h>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#include <e-util/e-sexp.h>
+
+#include <gal/util/e-iconv.h>
+
+#include "camel-mime-message.h"
+#include "camel-provider.h"
+#include "camel-session.h"
+#include "camel-filter-search.h"
+#include "camel-exception.h"
+#include "camel-multipart.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-fs.h"
+#include "camel-search-private.h"
+
+#include "camel-url.h"
+
+#define d(x)
+
+typedef struct {
+ CamelSession *session;
+ CamelFilterSearchGetMessageFunc get_message;
+ void *get_message_data;
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+ const char *source;
+ CamelException *ex;
+} FilterMessageSearch;
+
+/* ESExp callbacks */
+static ESExpResult *header_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_matches (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_soundex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_full_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *match_all (struct _ESExp *f, int argc, struct _ESExpTerm **argv, FilterMessageSearch *fms);
+static ESExpResult *body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *body_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *user_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *user_tag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *get_sent_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *get_received_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *get_current_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *header_source (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+static ESExpResult *junk_test (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
+
+/* builtin functions */
+static struct {
+ char *name;
+ ESExpFunc *func;
+ int type; /* set to 1 if a function can perform shortcut evaluation, or
+ doesn't execute everything, 0 otherwise */
+} symbols[] = {
+ { "match-all", (ESExpFunc *) match_all, 1 },
+ { "body-contains", (ESExpFunc *) body_contains, 0 },
+ { "body-regex", (ESExpFunc *) body_regex, 0 },
+ { "header-contains", (ESExpFunc *) header_contains, 0 },
+ { "header-matches", (ESExpFunc *) header_matches, 0 },
+ { "header-starts-with", (ESExpFunc *) header_starts_with, 0 },
+ { "header-ends-with", (ESExpFunc *) header_ends_with, 0 },
+ { "header-exists", (ESExpFunc *) header_exists, 0 },
+ { "header-soundex", (ESExpFunc *) header_soundex, 0 },
+ { "header-regex", (ESExpFunc *) header_regex, 0 },
+ { "header-full-regex", (ESExpFunc *) header_full_regex, 0 },
+ { "user-tag", (ESExpFunc *) user_tag, 0 },
+ { "user-flag", (ESExpFunc *) user_flag, 0 },
+ { "system-flag", (ESExpFunc *) system_flag, 0 },
+ { "get-sent-date", (ESExpFunc *) get_sent_date, 0 },
+ { "get-received-date", (ESExpFunc *) get_received_date, 0 },
+ { "get-current-date", (ESExpFunc *) get_current_date, 0 },
+ { "header-source", (ESExpFunc *) header_source, 0 },
+ { "get-size", (ESExpFunc *) get_size, 0 },
+ { "pipe-message", (ESExpFunc *) pipe_message, 0 },
+ { "junk-test", (ESExpFunc *) junk_test, 0 },
+};
+
+
+static CamelMimeMessage *
+camel_filter_search_get_message (FilterMessageSearch *fms, struct _ESExp *sexp)
+{
+ if (fms->message)
+ return fms->message;
+
+ fms->message = fms->get_message (fms->get_message_data, fms->ex);
+
+ if (fms->message == NULL)
+ e_sexp_fatal_error (sexp, _("Failed to retrieve message"));
+
+ return fms->message;
+}
+
+static ESExpResult *
+check_header (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms, camel_search_match_t how)
+{
+ gboolean matched = FALSE;
+ ESExpResult *r;
+ int i;
+
+ if (argc > 1 && argv[0]->type == ESEXP_RES_STRING) {
+ char *name = argv[0]->value.string;
+ const char *header;
+ camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
+ CamelContentType *ct;
+ const char *charset = NULL;
+
+ if (g_ascii_strcasecmp(name, "x-camel-mlist") == 0) {
+ header = camel_message_info_mlist(fms->info);
+ type = CAMEL_SEARCH_TYPE_MLIST;
+ } else {
+ CamelMimeMessage *message = camel_filter_search_get_message (fms, f);
+
+ header = camel_medium_get_header (CAMEL_MEDIUM (message), argv[0]->value.string);
+ /* FIXME: what about Resent-To, Resent-Cc and Resent-From? */
+ if (g_ascii_strcasecmp("to", name) == 0 || g_ascii_strcasecmp("cc", name) == 0 || g_ascii_strcasecmp("from", name) == 0)
+ type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
+ else {
+ ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
+ if (ct) {
+ charset = camel_content_type_param (ct, "charset");
+ charset = e_iconv_charset_name (charset);
+ }
+ }
+ }
+
+ if (header) {
+ for (i=1; i<argc && !matched; i++) {
+ if (argv[i]->type == ESEXP_RES_STRING)
+ matched = camel_search_header_match(header, argv[i]->value.string, how, type, charset);
+ }
+ }
+ }
+
+ r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ r->value.bool = matched;
+
+ return r;
+}
+
+static ESExpResult *
+header_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_CONTAINS);
+}
+
+
+static ESExpResult *
+header_matches (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_EXACT);
+}
+
+static ESExpResult *
+header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_STARTS);
+}
+
+static ESExpResult *
+header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_ENDS);
+}
+
+static ESExpResult *
+header_soundex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_SOUNDEX);
+}
+
+static ESExpResult *
+header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ gboolean matched = FALSE;
+ ESExpResult *r;
+ int i;
+
+ message = camel_filter_search_get_message (fms, f);
+
+ for (i = 0; i < argc && !matched; i++) {
+ if (argv[i]->type == ESEXP_RES_STRING)
+ matched = camel_medium_get_header (CAMEL_MEDIUM (message), argv[i]->value.string) != NULL;
+ }
+
+ r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ r->value.bool = matched;
+
+ return r;
+}
+
+static ESExpResult *
+header_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+ const char *contents;
+
+ message = camel_filter_search_get_message (fms, f);
+
+ if (argc > 1 && argv[0]->type == ESEXP_RES_STRING
+ && (contents = camel_medium_get_header (CAMEL_MEDIUM (message), argv[0]->value.string))
+ && camel_search_build_match_regex(&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE, argc-1, argv+1, fms->ex) == 0) {
+ r->value.bool = regexec (&pattern, contents, 0, NULL, 0) == 0;
+ regfree (&pattern);
+ } else
+ r->value.bool = FALSE;
+
+ return r;
+}
+
+static gchar *
+get_full_header (CamelMimeMessage *message)
+{
+ CamelMimePart *mp = CAMEL_MIME_PART (message);
+ GString *str = g_string_new ("");
+ char *ret;
+ struct _camel_header_raw *h;
+
+ for (h = mp->headers; h; h = h->next) {
+ if (h->value != NULL) {
+ g_string_append (str, h->name);
+ if (isspace (h->value[0]))
+ g_string_append (str, ":");
+ else
+ g_string_append (str, ": ");
+ g_string_append (str, h->value);
+ g_string_append_c(str, '\n');
+ }
+ }
+
+ ret = str->str;
+ g_string_free (str, FALSE);
+
+ return ret;
+}
+
+static ESExpResult *
+header_full_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+ char *contents;
+
+ if (camel_search_build_match_regex(&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_NEWLINE,
+ argc, argv, fms->ex) == 0) {
+ message = camel_filter_search_get_message (fms, f);
+ contents = get_full_header (message);
+ r->value.bool = regexec (&pattern, contents, 0, NULL, 0) == 0;
+ g_free (contents);
+ regfree (&pattern);
+ } else
+ r->value.bool = FALSE;
+
+ return r;
+}
+
+static ESExpResult *
+match_all (struct _ESExp *f, int argc, struct _ESExpTerm **argv, FilterMessageSearch *fms)
+{
+ /* match-all: when dealing with single messages is a no-op */
+ ESExpResult *r;
+
+ if (argc > 0)
+ return e_sexp_term_eval (f, argv[0]);
+
+ r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ r->value.bool = TRUE;
+
+ return r;
+}
+
+static ESExpResult *
+body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+
+ if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE, argc, argv, fms->ex) == 0) {
+ message = camel_filter_search_get_message (fms, f);
+ r->value.bool = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
+ regfree (&pattern);
+ } else
+ r->value.bool = FALSE;
+
+ return r;
+}
+
+static ESExpResult *
+body_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ CamelMimeMessage *message;
+ regex_t pattern;
+
+ if (camel_search_build_match_regex(&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE,
+ argc, argv, fms->ex) == 0) {
+ message = camel_filter_search_get_message (fms, f);
+ r->value.bool = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
+ regfree (&pattern);
+ } else
+ r->value.bool = FALSE;
+
+ return r;
+}
+
+static ESExpResult *
+user_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+ gboolean truth = FALSE;
+ int i;
+
+ /* performs an OR of all words */
+ for (i = 0; i < argc && !truth; i++) {
+ if (argv[i]->type == ESEXP_RES_STRING
+ && camel_flag_get (&fms->info->user_flags, argv[i]->value.string)) {
+ truth = TRUE;
+ break;
+ }
+ }
+
+ r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+
+ return r;
+}
+
+static ESExpResult *
+system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+
+ if (argc != 1 || argv[0]->type != ESEXP_RES_STRING)
+ e_sexp_fatal_error(f, _("Invalid arguments to (system-flag)"));
+
+ r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ r->value.bool = camel_system_flag_get (fms->info->flags, argv[0]->value.string);
+
+ return r;
+}
+
+static ESExpResult *
+user_tag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+ const char *tag;
+
+ if (argc != 1 || argv[0]->type != ESEXP_RES_STRING)
+ e_sexp_fatal_error(f, _("Invalid arguments to (user-tag)"));
+
+ tag = camel_tag_get (&fms->info->user_tags, argv[0]->value.string);
+
+ r = e_sexp_result_new (f, ESEXP_RES_STRING);
+ r->value.string = g_strdup (tag ? tag : "");
+
+ return r;
+}
+
+static ESExpResult *
+get_sent_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ ESExpResult *r;
+
+ message = camel_filter_search_get_message (fms, f);
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = camel_mime_message_get_date (message, NULL);
+
+ return r;
+}
+
+static ESExpResult *
+get_received_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ ESExpResult *r;
+
+ message = camel_filter_search_get_message (fms, f);
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = camel_mime_message_get_date_received (message, NULL);
+
+ return r;
+}
+
+static ESExpResult *
+get_current_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = time (NULL);
+
+ return r;
+}
+
+static ESExpResult *
+header_source (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ ESExpResult *r;
+ const char *src;
+ int truth = FALSE, i;
+ CamelProvider *provider;
+ CamelURL *uria, *urib;
+
+ if (fms->source) {
+ src = fms->source;
+ } else {
+ message = camel_filter_search_get_message(fms, f);
+ src = camel_mime_message_get_source(message);
+ }
+
+ if (src
+ && (provider = camel_provider_get(src, NULL))
+ && provider->url_equal) {
+ uria = camel_url_new(src, NULL);
+ if (uria) {
+ for (i=0;i<argc && !truth;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING
+ && (urib = camel_url_new(argv[i]->value.string, NULL))) {
+ truth = provider->url_equal(uria, urib);
+ camel_url_free(urib);
+ }
+ }
+ camel_url_free(uria);
+ }
+ }
+
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+
+ return r;
+}
+
+/* remember, the size comparisons are done at Kbytes */
+static ESExpResult *
+get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+
+ r = e_sexp_result_new(f, ESEXP_RES_INT);
+ r->value.number = fms->info->size / 1024;
+
+ return r;
+}
+
+static int
+run_command (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ CamelMimeMessage *message;
+ CamelStream *stream;
+ int result, status;
+ int in_fds[2];
+ pid_t pid;
+
+ if (argc < 1 || argv[0]->value.string[0] == '\0')
+ return 0;
+
+ if (pipe (in_fds) == -1) {
+ camel_exception_setv (fms->ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to create pipe to '%s': %s"),
+ argv[0]->value.string, g_strerror (errno));
+ return -1;
+ }
+
+ if (!(pid = fork ())) {
+ /* child process */
+ GPtrArray *args;
+ int maxfd, fd, i;
+
+ fd = open ("/dev/null", O_WRONLY);
+
+ if (dup2 (in_fds[0], STDIN_FILENO) < 0 ||
+ dup2 (fd, STDOUT_FILENO) < 0 ||
+ dup2 (fd, STDERR_FILENO) < 0)
+ _exit (255);
+
+ setsid ();
+
+ maxfd = sysconf (_SC_OPEN_MAX);
+ for (fd = 3; fd < maxfd; fd++)
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+
+ args = g_ptr_array_new ();
+ for (i = 0; i < argc; i++)
+ g_ptr_array_add (args, argv[i]->value.string);
+ g_ptr_array_add (args, NULL);
+
+ execvp (argv[0]->value.string, (char **) args->pdata);
+
+ g_ptr_array_free (args, TRUE);
+
+ d(printf ("Could not execute %s: %s\n", argv[0]->value.string, g_strerror (errno)));
+ _exit (255);
+ } else if (pid < 0) {
+ camel_exception_setv (fms->ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to create create child process '%s': %s"),
+ argv[0]->value.string, g_strerror (errno));
+ return -1;
+ }
+
+ /* parent process */
+ close (in_fds[0]);
+
+ message = camel_filter_search_get_message (fms, f);
+
+ stream = camel_stream_fs_new_with_fd (in_fds[1]);
+ camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
+ camel_stream_flush (stream);
+ camel_object_unref (stream);
+
+ result = waitpid (pid, &status, 0);
+
+ if (result == -1 && errno == EINTR) {
+ /* child process is hanging... */
+ kill (pid, SIGTERM);
+ sleep (1);
+ result = waitpid (pid, &status, WNOHANG);
+ if (result == 0) {
+ /* ...still hanging, set phasers to KILL */
+ kill (pid, SIGKILL);
+ sleep (1);
+ result = waitpid (pid, &status, WNOHANG);
+ }
+ }
+
+ if (result != -1 && WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else
+ return -1;
+}
+
+static ESExpResult *
+pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+ int retval, i;
+
+ /* make sure all args are strings */
+ for (i = 0; i < argc; i++) {
+ if (argv[i]->type != ESEXP_RES_STRING) {
+ retval = -1;
+ goto done;
+ }
+ }
+
+ retval = run_command (f, argc, argv, fms);
+
+ done:
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = retval;
+
+ return r;
+}
+
+static ESExpResult *
+junk_test (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
+{
+ ESExpResult *r;
+ gboolean retval = FALSE;
+
+ if (fms->session->junk_plugin != NULL) {
+ retval = camel_junk_plugin_check_junk (fms->session->junk_plugin, camel_filter_search_get_message (fms, f));
+
+ printf("junk filter => %s\n", retval ? "*JUNK*" : "clean");
+ }
+
+ r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+ r->value.number = retval;
+
+ return r;
+}
+
+/**
+ * camel_filter_search_match:
+ * @session:
+ * @get_message: function to retrieve the message if necessary
+ * @data: data for above
+ * @info:
+ * @source:
+ * @expression:
+ * @ex:
+ *
+ * Returns one of CAMEL_SEARCH_MATCHED, CAMEL_SEARCH_NOMATCH, or CAMEL_SEARCH_ERROR.
+ **/
+int
+camel_filter_search_match (CamelSession *session,
+ CamelFilterSearchGetMessageFunc get_message, void *data,
+ CamelMessageInfo *info, const char *source,
+ const char *expression, CamelException *ex)
+{
+ FilterMessageSearch fms;
+ ESExp *sexp;
+ ESExpResult *result;
+ gboolean retval;
+ int i;
+
+ fms.session = session;
+ fms.get_message = get_message;
+ fms.get_message_data = data;
+ fms.message = NULL;
+ fms.info = info;
+ fms.source = source;
+ fms.ex = ex;
+
+ sexp = e_sexp_new ();
+
+ for (i = 0; i < sizeof (symbols) / sizeof (symbols[0]); i++) {
+ if (symbols[i].type == 1)
+ e_sexp_add_ifunction (sexp, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, &fms);
+ else
+ e_sexp_add_function (sexp, 0, symbols[i].name, symbols[i].func, &fms);
+ }
+
+ e_sexp_input_text (sexp, expression, strlen (expression));
+ if (e_sexp_parse (sexp) == -1) {
+ if (!camel_exception_is_set (ex))
+ /* A filter search is a search through your filters, ie. your filters is the corpus being searched thru. */
+ camel_exception_setv (ex, 1, _("Error executing filter search: %s: %s"),
+ e_sexp_error (sexp), expression);
+ goto error;
+ }
+
+ result = e_sexp_eval (sexp);
+ if (result == NULL) {
+ if (!camel_exception_is_set (ex))
+ camel_exception_setv (ex, 1, _("Error executing filter search: %s: %s"),
+ e_sexp_error (sexp), expression);
+ goto error;
+ }
+
+ if (result->type == ESEXP_RES_BOOL)
+ retval = result->value.bool ? CAMEL_SEARCH_MATCHED : CAMEL_SEARCH_NOMATCH;
+ else
+ retval = CAMEL_SEARCH_NOMATCH;
+
+ e_sexp_result_free (sexp, result);
+ e_sexp_unref (sexp);
+
+ if (fms.message)
+ camel_object_unref (fms.message);
+
+ return retval;
+
+ error:
+ if (fms.message)
+ camel_object_unref (fms.message);
+
+ e_sexp_unref (sexp);
+
+ return CAMEL_SEARCH_ERROR;
+}
diff --git a/camel/camel-folder-search.c b/camel/camel-folder-search.c
new file mode 100644
index 0000000000..a0bc3345fe
--- /dev/null
+++ b/camel/camel-folder-search.c
@@ -0,0 +1,1409 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* This is a helper class for folders to implement the search function.
+ It implements enough to do basic searches on folders that can provide
+ an in-memory summary and a body index. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
+
+#include <glib.h>
+
+#include "camel-folder-search.h"
+#include "camel-folder-thread.h"
+
+#include "camel-exception.h"
+#include "camel-medium.h"
+#include "camel-multipart.h"
+#include "camel-mime-message.h"
+#include "camel-stream-mem.h"
+#include "e-util/e-memory.h"
+#include "camel-search-private.h"
+
+#define d(x)
+#define r(x)
+
+struct _CamelFolderSearchPrivate {
+ GHashTable *mempool_hash;
+ CamelException *ex;
+
+ CamelFolderThread *threads;
+ GHashTable *threads_hash;
+};
+
+#define _PRIVATE(o) (((CamelFolderSearch *)(o))->priv)
+
+static ESExpResult *search_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+
+static ESExpResult *search_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+static ESExpResult *search_header_matches(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+static ESExpResult *search_header_starts_with(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+static ESExpResult *search_header_ends_with(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+static ESExpResult *search_header_exists(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+static ESExpResult *search_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search);
+static ESExpResult *search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *s);
+static ESExpResult *search_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+static ESExpResult *search_user_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_user_tag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_system_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_get_sent_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_get_received_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_get_current_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_get_size(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+static ESExpResult *search_uid(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
+
+static ESExpResult *search_dummy(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+
+static void camel_folder_search_class_init (CamelFolderSearchClass *klass);
+static void camel_folder_search_init (CamelFolderSearch *obj);
+static void camel_folder_search_finalize (CamelObject *obj);
+
+static CamelObjectClass *camel_folder_search_parent;
+
+static void
+camel_folder_search_class_init (CamelFolderSearchClass *klass)
+{
+ camel_folder_search_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
+
+ klass->not = search_not;
+
+ klass->match_all = search_match_all;
+ klass->match_threads = search_match_threads;
+ klass->body_contains = search_body_contains;
+ klass->header_contains = search_header_contains;
+ klass->header_matches = search_header_matches;
+ klass->header_starts_with = search_header_starts_with;
+ klass->header_ends_with = search_header_ends_with;
+ klass->header_exists = search_header_exists;
+ klass->user_tag = search_user_tag;
+ klass->user_flag = search_user_flag;
+ klass->system_flag = search_system_flag;
+ klass->get_sent_date = search_get_sent_date;
+ klass->get_received_date = search_get_received_date;
+ klass->get_current_date = search_get_current_date;
+ klass->get_size = search_get_size;
+ klass->uid = search_uid;
+}
+
+static void
+camel_folder_search_init (CamelFolderSearch *obj)
+{
+ struct _CamelFolderSearchPrivate *p;
+
+ p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
+
+ obj->sexp = e_sexp_new();
+
+ /* use a hash of mempools to associate the returned uid lists with
+ the backing mempool. yes pretty weird, but i didn't want to change
+ the api just yet */
+
+ p->mempool_hash = g_hash_table_new(0, 0);
+}
+
+static void
+free_mempool(void *key, void *value, void *data)
+{
+ GPtrArray *uids = key;
+ EMemPool *pool = value;
+
+ g_warning("Search closed with outstanding result unfreed: %p", uids);
+
+ g_ptr_array_free(uids, TRUE);
+ e_mempool_destroy(pool);
+}
+
+static void
+camel_folder_search_finalize (CamelObject *obj)
+{
+ CamelFolderSearch *search = (CamelFolderSearch *)obj;
+ struct _CamelFolderSearchPrivate *p = _PRIVATE(obj);
+
+ if (search->sexp)
+ e_sexp_unref(search->sexp);
+ if (search->summary_hash)
+ g_hash_table_destroy(search->summary_hash);
+
+ g_free(search->last_search);
+ g_hash_table_foreach(p->mempool_hash, free_mempool, obj);
+ g_hash_table_destroy(p->mempool_hash);
+ g_free(p);
+}
+
+CamelType
+camel_folder_search_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_object_get_type (), "CamelFolderSearch",
+ sizeof (CamelFolderSearch),
+ sizeof (CamelFolderSearchClass),
+ (CamelObjectClassInitFunc) camel_folder_search_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_folder_search_init,
+ (CamelObjectFinalizeFunc) camel_folder_search_finalize);
+ }
+
+ return type;
+}
+
+#ifdef offsetof
+#define CAMEL_STRUCT_OFFSET(type, field) ((gint) offsetof (type, field))
+#else
+#define CAMEL_STRUCT_OFFSET(type, field) ((gint) ((gchar*) &((type *) 0)->field))
+#endif
+
+struct {
+ char *name;
+ int offset;
+ int flags; /* 0x02 = immediate, 0x01 = always enter */
+} builtins[] = {
+ /* these have default implementations in e-sexp */
+ { "and", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, and), 2 },
+ { "or", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, or), 2 },
+ /* we need to override this one though to implement an 'array not' */
+ { "not", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, not), 0 },
+ { "<", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, lt), 2 },
+ { ">", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, gt), 2 },
+ { "=", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, eq), 2 },
+
+ /* these we have to use our own default if there is none */
+ /* they should all be defined in the language? so it parses, or should they not?? */
+ { "match-all", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_all), 3 },
+ { "match-threads", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_threads), 3 },
+ { "body-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, body_contains), 1 },
+ { "header-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_contains), 1 },
+ { "header-matches", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_matches), 1 },
+ { "header-starts-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_starts_with), 1 },
+ { "header-ends-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_ends_with), 1 },
+ { "header-exists", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_exists), 1 },
+ { "user-tag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_tag), 1 },
+ { "user-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_flag), 1 },
+ { "system-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, system_flag), 1 },
+ { "get-sent-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_sent_date), 1 },
+ { "get-received-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_received_date), 1 },
+ { "get-current-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_current_date), 1 },
+ { "get-size", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_size), 1 },
+ { "uid", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 },
+};
+
+void
+camel_folder_search_construct (CamelFolderSearch *search)
+{
+ int i;
+ CamelFolderSearchClass *klass = (CamelFolderSearchClass *)CAMEL_OBJECT_GET_CLASS(search);
+
+ for (i=0;i<sizeof(builtins)/sizeof(builtins[0]);i++) {
+ void *func;
+ /* c is sure messy sometimes */
+ func = *((void **)(((char *)klass)+builtins[i].offset));
+ if (func == NULL && builtins[i].flags&1) {
+ g_warning("Search class doesn't implement '%s' method: %s", builtins[i].name, camel_type_to_name(CAMEL_OBJECT_GET_CLASS(search)));
+ func = (void *)search_dummy;
+ }
+ if (func != NULL) {
+ if (builtins[i].flags&2) {
+ e_sexp_add_ifunction(search->sexp, 0, builtins[i].name, (ESExpIFunc *)func, search);
+ } else {
+ e_sexp_add_function(search->sexp, 0, builtins[i].name, (ESExpFunc *)func, search);
+ }
+ }
+ }
+}
+
+/**
+ * camel_folder_search_new:
+ *
+ * Create a new CamelFolderSearch object.
+ *
+ * A CamelFolderSearch is a subclassable, extensible s-exp
+ * evaluator which enforces a particular set of s-expressions.
+ * Particular methods may be overriden by an implementation to
+ * implement a search for any sort of backend.
+ *
+ * Return value: A new CamelFolderSearch widget.
+ **/
+CamelFolderSearch *
+camel_folder_search_new (void)
+{
+ CamelFolderSearch *new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_folder_search_get_type ()));
+
+ camel_folder_search_construct(new);
+ return new;
+}
+
+/**
+ * camel_folder_search_set_folder:
+ * @search:
+ * @folder: A folder.
+ *
+ * Set the folder attribute of the search. This is currently unused, but
+ * could be used to perform a slow-search when indexes and so forth are not
+ * available. Or for use by subclasses.
+ **/
+void
+camel_folder_search_set_folder(CamelFolderSearch *search, CamelFolder *folder)
+{
+ search->folder = folder;
+}
+
+/**
+ * camel_folder_search_set_summary:
+ * @search:
+ * @summary: An array of CamelMessageInfo pointers.
+ *
+ * Set the array of summary objects representing the span of the search.
+ *
+ * If this is not set, then a subclass must provide the functions
+ * for searching headers and for the match-all operator.
+ **/
+void
+camel_folder_search_set_summary(CamelFolderSearch *search, GPtrArray *summary)
+{
+ int i;
+
+ search->summary = summary;
+ if (search->summary_hash)
+ g_hash_table_destroy(search->summary_hash);
+ search->summary_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<summary->len;i++)
+ g_hash_table_insert(search->summary_hash, (char *)camel_message_info_uid(summary->pdata[i]), summary->pdata[i]);
+}
+
+/**
+ * camel_folder_search_set_body_index:
+ * @search:
+ * @index:
+ *
+ * Set the index representing the contents of all messages
+ * in this folder. If this is not set, then the folder implementation
+ * should sub-class the CamelFolderSearch and provide its own
+ * body-contains function.
+ **/
+void
+camel_folder_search_set_body_index(CamelFolderSearch *search, CamelIndex *index)
+{
+ if (search->body_index)
+ camel_object_unref((CamelObject *)search->body_index);
+ search->body_index = index;
+ if (index)
+ camel_object_ref((CamelObject *)index);
+}
+
+/**
+ * camel_folder_search_execute_expression:
+ * @search:
+ * @expr:
+ * @ex:
+ *
+ * Execute the search expression @expr, returning an array of
+ * all matches as a GPtrArray of uid's of matching messages.
+ *
+ * Note that any settings such as set_body_index(), set_folder(),
+ * and so on are reset to #NULL once the search has completed.
+ *
+ * TODO: The interface should probably return summary items instead
+ * (since they are much more useful to any client).
+ *
+ * Return value: A GPtrArray of strings of all matching messages.
+ * This must only be freed by camel_folder_search_free_result.
+ **/
+GPtrArray *
+camel_folder_search_execute_expression(CamelFolderSearch *search, const char *expr, CamelException *ex)
+{
+ ESExpResult *r;
+ GPtrArray *matches;
+ int i;
+ GHashTable *results;
+ EMemPool *pool;
+ struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
+
+ p->ex = ex;
+
+ /* only re-parse if the search has changed */
+ if (search->last_search == NULL
+ || strcmp(search->last_search, expr)) {
+ e_sexp_input_text(search->sexp, expr, strlen(expr));
+ if (e_sexp_parse(search->sexp) == -1) {
+ camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
+ return NULL;
+ }
+
+ g_free(search->last_search);
+ search->last_search = g_strdup(expr);
+ }
+ r = e_sexp_eval(search->sexp);
+ if (r == NULL) {
+ if (!camel_exception_is_set(ex))
+ camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
+ return NULL;
+ }
+
+ matches = g_ptr_array_new();
+
+ /* now create a folder summary to return?? */
+ if (r->type == ESEXP_RES_ARRAY_PTR) {
+ d(printf("got result ...\n"));
+ /* we use a mempool to store the strings, packed in tight as possible, and freed together */
+ /* because the strings are often short (like <8 bytes long), we would be wasting appx 50%
+ of memory just storing the size tag that malloc assigns us and alignment padding, so this
+ gets around that (and is faster to allocate and free as a bonus) */
+ pool = e_mempool_new(512, 256, E_MEMPOOL_ALIGN_BYTE);
+ if (search->summary) {
+ /* reorder result in summary order */
+ results = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<r->value.ptrarray->len;i++) {
+ d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
+ g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
+ }
+ for (i=0;i<search->summary->len;i++) {
+ CamelMessageInfo *info = g_ptr_array_index(search->summary, i);
+ char *uid = (char *)camel_message_info_uid(info);
+ if (g_hash_table_lookup(results, uid)) {
+ g_ptr_array_add(matches, e_mempool_strdup(pool, uid));
+ }
+ }
+ g_hash_table_destroy(results);
+ } else {
+ for (i=0;i<r->value.ptrarray->len;i++) {
+ d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
+ g_ptr_array_add(matches, e_mempool_strdup(pool, g_ptr_array_index(r->value.ptrarray, i)));
+ }
+ }
+ /* instead of putting the mempool_hash in the structure, we keep the api clean by
+ putting a reference to it in a hashtable. Lets us do some debugging and catch
+ unfree'd results as well. */
+ g_hash_table_insert(p->mempool_hash, matches, pool);
+ } else {
+ g_warning("Search returned an invalid result type");
+ }
+
+ e_sexp_result_free(search->sexp, r);
+
+ if (p->threads)
+ camel_folder_thread_messages_unref(p->threads);
+ if (p->threads_hash)
+ g_hash_table_destroy(p->threads_hash);
+
+ p->threads = NULL;
+ p->threads_hash = NULL;
+ search->folder = NULL;
+ search->summary = NULL;
+ search->current = NULL;
+ search->body_index = NULL;
+
+ return matches;
+}
+
+/**
+ * camel_folder_search_search:
+ * @search:
+ * @expr:
+ * @uids: to search against, NULL for all uid's.
+ * @ex:
+ *
+ * Run a search. Search must have had Folder already set on it, and
+ * it must implement summaries.
+ *
+ * Return value:
+ **/
+GPtrArray *
+camel_folder_search_search(CamelFolderSearch *search, const char *expr, GPtrArray *uids, CamelException *ex)
+{
+ ESExpResult *r;
+ GPtrArray *matches = NULL, *summary_set;
+ int i;
+ GHashTable *results;
+ EMemPool *pool;
+ struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
+
+ g_assert(search->folder);
+
+ p->ex = ex;
+
+ /* setup our search list, summary_hash only contains those we're interested in */
+ search->summary = camel_folder_get_summary(search->folder);
+ search->summary_hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ if (uids) {
+ GHashTable *uids_hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ summary_set = search->summary_set = g_ptr_array_new();
+ for (i=0;i<uids->len;i++)
+ g_hash_table_insert(uids_hash, uids->pdata[i], uids->pdata[i]);
+ for (i=0;i<search->summary->len;i++)
+ if (g_hash_table_lookup(uids_hash, camel_message_info_uid(search->summary->pdata[i])))
+ g_ptr_array_add(search->summary_set, search->summary->pdata[i]);
+ } else {
+ summary_set = search->summary;
+ }
+
+ for (i=0;i<summary_set->len;i++)
+ g_hash_table_insert(search->summary_hash, (char *)camel_message_info_uid(summary_set->pdata[i]), summary_set->pdata[i]);
+
+ /* only re-parse if the search has changed */
+ if (search->last_search == NULL
+ || strcmp(search->last_search, expr)) {
+ e_sexp_input_text(search->sexp, expr, strlen(expr));
+ if (e_sexp_parse(search->sexp) == -1) {
+ camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
+ goto fail;
+ }
+
+ g_free(search->last_search);
+ search->last_search = g_strdup(expr);
+ }
+ r = e_sexp_eval(search->sexp);
+ if (r == NULL) {
+ if (!camel_exception_is_set(ex))
+ camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
+ goto fail;
+ }
+
+ matches = g_ptr_array_new();
+
+ /* now create a folder summary to return?? */
+ if (r->type == ESEXP_RES_ARRAY_PTR) {
+ d(printf("got result ...\n"));
+
+ /* we use a mempool to store the strings, packed in tight as possible, and freed together */
+ /* because the strings are often short (like <8 bytes long), we would be wasting appx 50%
+ of memory just storing the size tag that malloc assigns us and alignment padding, so this
+ gets around that (and is faster to allocate and free as a bonus) */
+ pool = e_mempool_new(512, 256, E_MEMPOOL_ALIGN_BYTE);
+ /* reorder result in summary order */
+ results = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<r->value.ptrarray->len;i++) {
+ d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
+ g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
+ }
+
+ for (i=0;i<summary_set->len;i++) {
+ CamelMessageInfo *info = g_ptr_array_index(summary_set, i);
+ char *uid = (char *)camel_message_info_uid(info);
+ if (g_hash_table_lookup(results, uid))
+ g_ptr_array_add(matches, e_mempool_strdup(pool, uid));
+ }
+ g_hash_table_destroy(results);
+
+ /* instead of putting the mempool_hash in the structure, we keep the api clean by
+ putting a reference to it in a hashtable. Lets us do some debugging and catch
+ unfree'd results as well. */
+ g_hash_table_insert(p->mempool_hash, matches, pool);
+ } else {
+ g_warning("Search returned an invalid result type");
+ }
+
+ e_sexp_result_free(search->sexp, r);
+fail:
+ /* these might be allocated by match-threads */
+ if (p->threads)
+ camel_folder_thread_messages_unref(p->threads);
+ if (p->threads_hash)
+ g_hash_table_destroy(p->threads_hash);
+ if (search->summary_set)
+ g_ptr_array_free(search->summary_set, TRUE);
+ g_hash_table_destroy(search->summary_hash);
+ camel_folder_free_summary(search->folder, search->summary);
+
+ p->threads = NULL;
+ p->threads_hash = NULL;
+ search->folder = NULL;
+ search->summary = NULL;
+ search->summary_hash = NULL;
+ search->summary_set = NULL;
+ search->current = NULL;
+ search->body_index = NULL;
+
+ return matches;
+}
+
+void camel_folder_search_free_result(CamelFolderSearch *search, GPtrArray *result)
+{
+ int i;
+ struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
+ EMemPool *pool;
+
+ pool = g_hash_table_lookup(p->mempool_hash, result);
+ if (pool) {
+ e_mempool_destroy(pool);
+ g_hash_table_remove(p->mempool_hash, result);
+ } else {
+ for (i=0;i<result->len;i++)
+ g_free(g_ptr_array_index(result, i));
+ }
+ g_ptr_array_free(result, TRUE);
+}
+
+/* dummy function, returns false always, or an empty match array */
+static ESExpResult *
+search_dummy(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+
+ if (search->current == NULL) {
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = FALSE;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+ }
+
+ return r;
+}
+
+/* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */
+static ESExpResult *
+search_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+ int i;
+
+ if (argc>0) {
+ if (argv[0]->type == ESEXP_RES_ARRAY_PTR) {
+ GPtrArray *v = argv[0]->value.ptrarray;
+ const char *uid;
+
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+
+ /* not against a single message?*/
+ if (search->current) {
+ int found = FALSE;
+
+ uid = camel_message_info_uid(search->current);
+ for (i=0;!found && i<v->len;i++) {
+ if (strcmp(uid, v->pdata[i]) == 0)
+ found = TRUE;
+ }
+
+ if (!found)
+ g_ptr_array_add(r->value.ptrarray, (char *)uid);
+ } else if (search->summary == NULL) {
+ g_warning("No summary set, 'not' against an array requires a summary");
+ } else {
+ /* 'not' against the whole summary */
+ GHashTable *have = g_hash_table_new(g_str_hash, g_str_equal);
+ char **s;
+ CamelMessageInfo **m;
+
+ s = (char **)v->pdata;
+ for (i=0;i<v->len;i++)
+ g_hash_table_insert(have, s[i], s[i]);
+
+ v = search->summary_set?search->summary_set:search->summary;
+ m = (CamelMessageInfo **)v->pdata;
+ for (i=0;i<v->len;i++) {
+ char *uid = (char *)camel_message_info_uid(m[i]);
+
+ if (g_hash_table_lookup(have, uid) == NULL)
+ g_ptr_array_add(r->value.ptrarray, uid);
+ }
+ g_hash_table_destroy(have);
+ }
+ } else {
+ int res = TRUE;
+
+ if (argv[0]->type == ESEXP_RES_BOOL)
+ res = ! argv[0]->value.bool;
+
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = res;
+ }
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = TRUE;
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
+{
+ int i;
+ ESExpResult *r, *r1;
+ GPtrArray *v;
+
+ if (argc>1) {
+ g_warning("match-all only takes a single argument, other arguments ignored");
+ }
+
+ /* we are only matching a single message? or already inside a match-all? */
+ if (search->current) {
+ d(printf("matching against 1 message: %s\n", camel_message_info_subject(search->current)));
+
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = FALSE;
+
+ if (argc>0) {
+ r1 = e_sexp_term_eval(f, argv[0]);
+ if (r1->type == ESEXP_RES_BOOL) {
+ r->value.bool = r1->value.bool;
+ } else {
+ g_warning("invalid syntax, matches require a single bool result");
+ e_sexp_fatal_error(f, _("(match-all) requires a single bool result"));
+ }
+ e_sexp_result_free(f, r1);
+ } else {
+ r->value.bool = TRUE;
+ }
+ return r;
+ }
+
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+
+ if (search->summary == NULL) {
+ /* TODO: make it work - e.g. use the folder and so forth for a slower search */
+ g_warning("No summary supplied, match-all doesn't work with no summary");
+ g_assert(0);
+ return r;
+ }
+
+ v = search->summary_set?search->summary_set:search->summary;
+ for (i=0;i<v->len;i++) {
+ const char *uid;
+
+ search->current = g_ptr_array_index(v, i);
+ uid = camel_message_info_uid(search->current);
+
+ if (argc>0) {
+ r1 = e_sexp_term_eval(f, argv[0]);
+ if (r1->type == ESEXP_RES_BOOL) {
+ if (r1->value.bool)
+ g_ptr_array_add(r->value.ptrarray, (char *)uid);
+ } else {
+ g_warning("invalid syntax, matches require a single bool result");
+ e_sexp_fatal_error(f, _("(match-all) requires a single bool result"));
+ }
+ e_sexp_result_free(f, r1);
+ } else {
+ g_ptr_array_add(r->value.ptrarray, (char *)uid);
+ }
+ }
+ search->current = NULL;
+
+ return r;
+}
+
+static void
+fill_thread_table(struct _CamelFolderThreadNode *root, GHashTable *id_hash)
+{
+ while (root) {
+ g_hash_table_insert(id_hash, (char *)camel_message_info_uid(root->message), root);
+ if (root->child)
+ fill_thread_table(root->child, id_hash);
+ root = root->next;
+ }
+}
+
+static void
+add_thread_results(struct _CamelFolderThreadNode *root, GHashTable *result_hash)
+{
+ while (root) {
+ g_hash_table_insert(result_hash, (char *)camel_message_info_uid(root->message), GINT_TO_POINTER (1));
+ if (root->child)
+ add_thread_results(root->child, result_hash);
+ root = root->next;
+ }
+}
+
+static void
+add_results(char *uid, void *dummy, GPtrArray *result)
+{
+ g_ptr_array_add(result, uid);
+}
+
+static ESExpResult *
+search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+ struct _CamelFolderSearchPrivate *p = search->priv;
+ int i, type;
+ GHashTable *results;
+
+ /* not supported in match-all */
+ if (search->current)
+ e_sexp_fatal_error(f, _("(match-threads) not allowed inside match-all"));
+
+ if (argc == 0)
+ e_sexp_fatal_error(f, _("(match-threads) requires a match type string"));
+
+ r = e_sexp_term_eval(f, argv[0]);
+ if (r->type != ESEXP_RES_STRING)
+ e_sexp_fatal_error(f, _("(match-threads) requires a match type string"));
+
+ type = 0;
+ if (!strcmp(r->value.string, "none"))
+ type = 0;
+ else if (!strcmp(r->value.string, "all"))
+ type = 1;
+ else if (!strcmp(r->value.string, "replies"))
+ type = 2;
+ else if (!strcmp(r->value.string, "replies_parents"))
+ type = 3;
+ e_sexp_result_free(f, r);
+
+ /* behave as (begin does */
+ r = NULL;
+ for (i=1;i<argc;i++) {
+ if (r)
+ e_sexp_result_free(f, r);
+ r = e_sexp_term_eval(f, argv[i]);
+ }
+
+ if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR)
+ e_sexp_fatal_error(f, _("(match-threads) expects an array result"));
+
+ if (type == 0)
+ return r;
+
+ if (search->folder == NULL)
+ e_sexp_fatal_error(f, _("(match-threads) requires the folder set"));
+
+ /* cache this, so we only have to re-calculate once per search at most */
+ if (p->threads == NULL) {
+ p->threads = camel_folder_thread_messages_new(search->folder, NULL, TRUE);
+ p->threads_hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ fill_thread_table(p->threads->tree, p->threads_hash);
+ }
+
+ results = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<r->value.ptrarray->len;i++) {
+ d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
+ g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
+ }
+
+ for (i=0;i<r->value.ptrarray->len;i++) {
+ struct _CamelFolderThreadNode *node, *scan;
+
+ node = g_hash_table_lookup(p->threads_hash, (char *)g_ptr_array_index(r->value.ptrarray, i));
+ if (node == NULL) /* this shouldn't happen but why cry over spilt milk */
+ continue;
+
+ /* select messages in thread according to search criteria */
+ if (type == 3) {
+ scan = node;
+ while (scan && scan->parent) {
+ scan = scan->parent;
+ g_hash_table_insert(results, (char *)camel_message_info_uid(scan->message), GINT_TO_POINTER(1));
+ }
+ } else if (type == 1) {
+ while (node && node->parent)
+ node = node->parent;
+ }
+ g_hash_table_insert(results, (char *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
+ if (node->child)
+ add_thread_results(node->child, results);
+ }
+ e_sexp_result_free(f, r);
+
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+
+ g_hash_table_foreach(results, (GHFunc)add_results, r->value.ptrarray);
+ g_hash_table_destroy(results);
+
+ return r;
+}
+
+static ESExpResult *
+check_header(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search, camel_search_match_t how)
+{
+ ESExpResult *r;
+ int truth = FALSE;
+
+ r(printf("executing check-header %d\n", how));
+
+ /* are we inside a match-all? */
+ if (search->current && argc>1
+ && argv[0]->type == ESEXP_RES_STRING) {
+ char *headername;
+ const char *header = NULL;
+ char strbuf[32];
+ int i, j;
+ camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
+ struct _camel_search_words *words;
+
+ /* only a subset of headers are supported .. */
+ headername = argv[0]->value.string;
+ if (!strcasecmp(headername, "subject")) {
+ header = camel_message_info_subject(search->current);
+ } else if (!strcasecmp(headername, "date")) {
+ /* FIXME: not a very useful form of the date */
+ sprintf(strbuf, "%d", (int)search->current->date_sent);
+ header = strbuf;
+ } else if (!strcasecmp(headername, "from")) {
+ header = camel_message_info_from(search->current);
+ type = CAMEL_SEARCH_TYPE_ADDRESS;
+ } else if (!strcasecmp(headername, "to")) {
+ header = camel_message_info_to(search->current);
+ type = CAMEL_SEARCH_TYPE_ADDRESS;
+ } else if (!strcasecmp(headername, "cc")) {
+ header = camel_message_info_cc(search->current);
+ type = CAMEL_SEARCH_TYPE_ADDRESS;
+ } else if (!g_ascii_strcasecmp(headername, "x-camel-mlist")) {
+ header = camel_message_info_mlist(search->current);
+ type = CAMEL_SEARCH_TYPE_MLIST;
+ } else {
+ e_sexp_resultv_free(f, argc, argv);
+ e_sexp_fatal_error(f, _("Performing query on unknown header: %s"), headername);
+ }
+
+ if (header == NULL)
+ header = "";
+
+ /* performs an OR of all words */
+ for (i=1;i<argc && !truth;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING) {
+ if (argv[i]->value.string[0] == 0) {
+ truth = TRUE;
+ } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) {
+ /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */
+ words = camel_search_words_split(argv[i]->value.string);
+ truth = TRUE;
+ for (j=0;j<words->len && truth;j++) {
+ truth = camel_search_header_match(header, words->words[j]->word, how, type, NULL);
+ }
+ camel_search_words_free(words);
+ } else {
+ truth = camel_search_header_match(header, argv[i]->value.string, how, type, NULL);
+ }
+ }
+ }
+ }
+ /* TODO: else, find all matches */
+
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+
+ return r;
+}
+
+static ESExpResult *
+search_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
+}
+
+static ESExpResult *
+search_header_matches(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
+}
+
+static ESExpResult *
+search_header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
+}
+
+static ESExpResult *
+search_header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
+}
+
+static ESExpResult *
+search_header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+
+ r(printf ("executing header-exists\n"));
+
+ if (search->current) {
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ if (argc == 1 && argv[0]->type == ESEXP_RES_STRING)
+ r->value.bool = camel_medium_get_header(CAMEL_MEDIUM(search->current), argv[0]->value.string) != NULL;
+
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+ }
+
+ return r;
+}
+
+/* this is just to OR results together */
+struct _glib_sux_donkeys {
+ int count;
+ GPtrArray *uids;
+};
+
+/* or, store all unique values */
+static void
+g_lib_sux_htor(char *key, int value, struct _glib_sux_donkeys *fuckup)
+{
+ g_ptr_array_add(fuckup->uids, key);
+}
+
+/* and, only store duplicates */
+static void
+g_lib_sux_htand(char *key, int value, struct _glib_sux_donkeys *fuckup)
+{
+ if (value == fuckup->count)
+ g_ptr_array_add(fuckup->uids, key);
+}
+
+static int
+match_message_index(CamelIndex *idx, const char *uid, const char *match, CamelException *ex)
+{
+ CamelIndexCursor *wc, *nc;
+ const char *word, *name;
+ int truth = FALSE;
+
+ wc = camel_index_words(idx);
+ if (wc) {
+ while (!truth && (word = camel_index_cursor_next(wc))) {
+ if (camel_ustrstrcase(word,match) != NULL) {
+ /* perf: could have the wc cursor return the name cursor */
+ nc = camel_index_find(idx, word);
+ if (nc) {
+ while (!truth && (name = camel_index_cursor_next(nc)))
+ truth = strcmp(name, uid) == 0;
+ camel_object_unref((CamelObject *)nc);
+ }
+ }
+ }
+ camel_object_unref((CamelObject *)wc);
+ }
+
+ return truth;
+}
+
+/*
+ "one two" "three" "four five"
+
+ one and two
+or
+ three
+or
+ four and five
+*/
+
+/* returns messages which contain all words listed in words */
+static GPtrArray *
+match_words_index(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
+{
+ GPtrArray *result = g_ptr_array_new();
+ GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
+ struct _glib_sux_donkeys lambdafoo;
+ CamelIndexCursor *wc, *nc;
+ const char *word, *name;
+ CamelMessageInfo *mi;
+ int i;
+
+ /* we can have a maximum of 32 words, as we use it as the AND mask */
+
+ wc = camel_index_words(search->body_index);
+ if (wc) {
+ while ((word = camel_index_cursor_next(wc))) {
+ for (i=0;i<words->len;i++) {
+ if (camel_ustrstrcase(word, words->words[i]->word) != NULL) {
+ /* perf: could have the wc cursor return the name cursor */
+ nc = camel_index_find(search->body_index, word);
+ if (nc) {
+ while ((name = camel_index_cursor_next(nc))) {
+ mi = g_hash_table_lookup(search->summary_hash, name);
+ if (mi) {
+ int mask;
+ const char *uid = camel_message_info_uid(mi);
+
+ mask = (GPOINTER_TO_INT(g_hash_table_lookup(ht, uid))) | (1<<i);
+ g_hash_table_insert(ht, (char *)uid, GINT_TO_POINTER(mask));
+ }
+ }
+ camel_object_unref((CamelObject *)nc);
+ }
+ }
+ }
+ }
+ camel_object_unref((CamelObject *)wc);
+
+ lambdafoo.uids = result;
+ lambdafoo.count = (1<<words->len) - 1;
+ g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htand, &lambdafoo);
+ g_hash_table_destroy(ht);
+ }
+
+ return result;
+}
+
+static gboolean
+match_words_1message (CamelDataWrapper *object, struct _camel_search_words *words, guint32 *mask)
+{
+ CamelDataWrapper *containee;
+ int truth = FALSE;
+ int parts, i;
+
+ containee = camel_medium_get_content_object (CAMEL_MEDIUM (object));
+
+ if (containee == NULL)
+ return FALSE;
+
+ /* using the object types is more accurate than using the mime/types */
+ if (CAMEL_IS_MULTIPART (containee)) {
+ parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
+ for (i = 0; i < parts && truth == FALSE; i++) {
+ CamelDataWrapper *part = (CamelDataWrapper *)camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
+ if (part)
+ truth = match_words_1message(part, words, mask);
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
+ /* for messages we only look at its contents */
+ truth = match_words_1message((CamelDataWrapper *)containee, words, mask);
+ } else if (camel_content_type_is(CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) {
+ /* for all other text parts, we look inside, otherwise we dont care */
+ CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new ();
+
+ /* FIXME: The match should be part of a stream op */
+ camel_data_wrapper_decode_to_stream (containee, CAMEL_STREAM (mem));
+ camel_stream_write (CAMEL_STREAM (mem), "", 1);
+ for (i=0;i<words->len;i++) {
+ /* FIXME: This is horridly slow, and should use a real search algorithm */
+ if (camel_ustrstrcase(mem->buffer->data, words->words[i]->word) != NULL) {
+ *mask |= (1<<i);
+ /* shortcut a match */
+ if (*mask == (1<<(words->len))-1)
+ return TRUE;
+ }
+ }
+
+ camel_object_unref (mem);
+ }
+
+ return truth;
+}
+
+static gboolean
+match_words_message(CamelFolder *folder, const char *uid, struct _camel_search_words *words, CamelException *ex)
+{
+ guint32 mask;
+ CamelMimeMessage *msg;
+ CamelException x = CAMEL_EXCEPTION_INITIALISER;
+ int truth;
+
+ msg = camel_folder_get_message(folder, uid, &x);
+ if (msg) {
+ mask = 0;
+ truth = match_words_1message((CamelDataWrapper *)msg, words, &mask);
+ camel_object_unref((CamelObject *)msg);
+ } else {
+ camel_exception_clear(&x);
+ truth = FALSE;
+ }
+
+ return truth;
+}
+
+static GPtrArray *
+match_words_messages(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
+{
+ int i;
+ GPtrArray *matches = g_ptr_array_new();
+
+ if (search->body_index) {
+ GPtrArray *indexed;
+ struct _camel_search_words *simple;
+
+ simple = camel_search_words_simple(words);
+ indexed = match_words_index(search, simple, ex);
+ camel_search_words_free(simple);
+
+ for (i=0;i<indexed->len;i++) {
+ const char *uid = g_ptr_array_index(indexed, i);
+
+ if (match_words_message(search->folder, uid, words, ex))
+ g_ptr_array_add(matches, (char *)uid);
+ }
+
+ g_ptr_array_free(indexed, TRUE);
+ } else {
+ GPtrArray *v = search->summary_set?search->summary_set:search->summary;
+
+ for (i=0;i<v->len;i++) {
+ CamelMessageInfo *info = g_ptr_array_index(v, i);
+ const char *uid = camel_message_info_uid(info);
+
+ if (match_words_message(search->folder, uid, words, ex))
+ g_ptr_array_add(matches, (char *)uid);
+ }
+ }
+
+ return matches;
+}
+
+static ESExpResult *
+search_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ int i, j;
+ CamelException *ex = search->priv->ex;
+ struct _camel_search_words *words;
+ ESExpResult *r;
+ struct _glib_sux_donkeys lambdafoo;
+
+ if (search->current) {
+ int truth = FALSE;
+
+ if (argc == 1 && argv[0]->value.string[0] == 0) {
+ truth = TRUE;
+ } else {
+ for (i=0;i<argc && !truth;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING) {
+ words = camel_search_words_split(argv[i]->value.string);
+ truth = TRUE;
+ if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
+ for (j=0;j<words->len && truth;j++)
+ truth = match_message_index(search->body_index, camel_message_info_uid(search->current), words->words[j]->word, ex);
+ } else {
+ /* TODO: cache current message incase of multiple body search terms */
+ truth = match_words_message(search->folder, camel_message_info_uid(search->current), words, ex);
+ }
+ camel_search_words_free(words);
+ }
+ }
+ }
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+
+ if (argc == 1 && argv[0]->value.string[0] == 0) {
+ GPtrArray *v = search->summary_set?search->summary_set:search->summary;
+
+ for (i=0;i<v->len;i++) {
+ CamelMessageInfo *info = g_ptr_array_index(v, i);
+
+ g_ptr_array_add(r->value.ptrarray, (char *)camel_message_info_uid(info));
+ }
+ } else {
+ GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
+ GPtrArray *matches;
+
+ for (i=0;i<argc;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING) {
+ words = camel_search_words_split(argv[i]->value.string);
+ if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
+ matches = match_words_index(search, words, ex);
+ } else {
+ matches = match_words_messages(search, words, ex);
+ }
+ for (j=0;j<matches->len;j++)
+ g_hash_table_insert(ht, matches->pdata[j], matches->pdata[j]);
+ g_ptr_array_free(matches, TRUE);
+ camel_search_words_free(words);
+ }
+ }
+ lambdafoo.uids = r->value.ptrarray;
+ g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htor, &lambdafoo);
+ g_hash_table_destroy(ht);
+ }
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_user_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+ int i;
+
+ r(printf("executing user-flag\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ int truth = FALSE;
+ /* performs an OR of all words */
+ for (i=0;i<argc && !truth;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING
+ && camel_flag_get(&search->current->user_flags, argv[i]->value.string)) {
+ truth = TRUE;
+ break;
+ }
+ }
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+
+ r(printf ("executing system-flag\n"));
+
+ if (search->current) {
+ gboolean truth = FALSE;
+
+ if (argc == 1)
+ truth = camel_system_flag_get (search->current->flags, argv[0]->value.string);
+
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_user_tag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ const char *value = NULL;
+ ESExpResult *r;
+
+ r(printf("executing user-tag\n"));
+
+ if (argc == 1)
+ value = camel_tag_get (&search->current->user_tags, argv[0]->value.string);
+
+ r = e_sexp_result_new(f, ESEXP_RES_STRING);
+ r->value.string = g_strdup (value ? value : "");
+
+ return r;
+}
+
+static ESExpResult *
+search_get_sent_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
+{
+ ESExpResult *r;
+
+ r(printf("executing get-sent-date\n"));
+
+ /* are we inside a match-all? */
+ if (s->current) {
+ r = e_sexp_result_new(f, ESEXP_RES_INT);
+
+ r->value.number = s->current->date_sent;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_get_received_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
+{
+ ESExpResult *r;
+
+ r(printf("executing get-received-date\n"));
+
+ /* are we inside a match-all? */
+ if (s->current) {
+ r = e_sexp_result_new(f, ESEXP_RES_INT);
+
+ r->value.number = s->current->date_received;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_get_current_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
+{
+ ESExpResult *r;
+
+ r(printf("executing get-current-date\n"));
+
+ r = e_sexp_result_new(f, ESEXP_RES_INT);
+ r->value.number = time (NULL);
+ return r;
+}
+
+static ESExpResult *
+search_get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
+{
+ ESExpResult *r;
+
+ r(printf("executing get-size\n"));
+
+ /* are we inside a match-all? */
+ if (s->current) {
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = s->current->size / 1024;
+ } else {
+ r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new ();
+ }
+
+ return r;
+}
+
+static ESExpResult *
+search_uid(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+ ESExpResult *r;
+ int i;
+
+ r(printf("executing uid\n"));
+
+ /* are we inside a match-all? */
+ if (search->current) {
+ int truth = FALSE;
+ const char *uid = camel_message_info_uid(search->current);
+
+ /* performs an OR of all words */
+ for (i=0;i<argc && !truth;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING
+ && !strcmp(uid, argv[i]->value.string)) {
+ truth = TRUE;
+ break;
+ }
+ }
+ r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+ r->value.bool = truth;
+ } else {
+ r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
+ r->value.ptrarray = g_ptr_array_new();
+ for (i=0;i<argc;i++) {
+ if (argv[i]->type == ESEXP_RES_STRING)
+ g_ptr_array_add(r->value.ptrarray, argv[i]->value.string);
+ }
+ }
+
+ return r;
+}
diff --git a/camel/camel-folder-summary.c b/camel/camel-folder-summary.c
new file mode 100644
index 0000000000..bf58f4a47e
--- /dev/null
+++ b/camel/camel-folder-summary.c
@@ -0,0 +1,2888 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <gal/util/e-iconv.h>
+
+#include "camel-folder-summary.h"
+
+#include <camel/camel-file-utils.h>
+#include <camel/camel-mime-filter.h>
+#include <camel/camel-mime-filter-index.h>
+#include <camel/camel-mime-filter-charset.h>
+#include <camel/camel-mime-filter-basic.h>
+#include <camel/camel-mime-filter-html.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-multipart.h>
+#include <camel/camel-stream-mem.h>
+
+#include <camel/camel-stream-null.h>
+#include <camel/camel-stream-filter.h>
+
+#include <camel/camel-string-utils.h>
+
+#include "e-util/md5-utils.h"
+#include "e-util/e-memory.h"
+
+#include "camel-private.h"
+
+
+static pthread_mutex_t info_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* this lock is ONLY for the standalone messageinfo stuff */
+#define GLOBAL_INFO_LOCK(i) pthread_mutex_lock(&info_lock)
+#define GLOBAL_INFO_UNLOCK(i) pthread_mutex_unlock(&info_lock)
+
+
+/* this should probably be conditional on it existing */
+#define USE_BSEARCH
+
+#define d(x)
+#define io(x) /* io debug */
+
+#if 0
+extern int strdup_count, malloc_count, free_count;
+#endif
+
+#define CAMEL_FOLDER_SUMMARY_VERSION (13)
+
+#define _PRIVATE(o) (((CamelFolderSummary *)(o))->priv)
+
+/* trivial lists, just because ... */
+struct _node {
+ struct _node *next;
+};
+
+static struct _node *my_list_append(struct _node **list, struct _node *n);
+static int my_list_size(struct _node **list);
+
+static int summary_header_load(CamelFolderSummary *, FILE *);
+static int summary_header_save(CamelFolderSummary *, FILE *);
+
+static CamelMessageInfo * message_info_new(CamelFolderSummary *, struct _camel_header_raw *);
+static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
+static CamelMessageInfo * message_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg);
+static CamelMessageInfo * message_info_load(CamelFolderSummary *, FILE *);
+static int message_info_save(CamelFolderSummary *, FILE *, CamelMessageInfo *);
+static void message_info_free(CamelFolderSummary *, CamelMessageInfo *);
+
+static CamelMessageContentInfo * content_info_new(CamelFolderSummary *, struct _camel_header_raw *);
+static CamelMessageContentInfo * content_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
+static CamelMessageContentInfo * content_info_new_from_message(CamelFolderSummary *s, CamelMimePart *mp);
+static CamelMessageContentInfo * content_info_load(CamelFolderSummary *, FILE *);
+static int content_info_save(CamelFolderSummary *, FILE *, CamelMessageContentInfo *);
+static void content_info_free(CamelFolderSummary *, CamelMessageContentInfo *);
+
+static char *next_uid_string(CamelFolderSummary *s);
+
+static CamelMessageContentInfo * summary_build_content_info(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimeParser *mp);
+static CamelMessageContentInfo * summary_build_content_info_message(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimePart *object);
+
+static void camel_folder_summary_class_init (CamelFolderSummaryClass *klass);
+static void camel_folder_summary_init (CamelFolderSummary *obj);
+static void camel_folder_summary_finalize (CamelObject *obj);
+
+static CamelObjectClass *camel_folder_summary_parent;
+
+static void
+camel_folder_summary_class_init (CamelFolderSummaryClass *klass)
+{
+ camel_folder_summary_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
+
+ klass->summary_header_load = summary_header_load;
+ klass->summary_header_save = summary_header_save;
+
+ klass->message_info_new = message_info_new;
+ klass->message_info_new_from_parser = message_info_new_from_parser;
+ klass->message_info_new_from_message = message_info_new_from_message;
+ klass->message_info_load = message_info_load;
+ klass->message_info_save = message_info_save;
+ klass->message_info_free = message_info_free;
+
+ klass->content_info_new = content_info_new;
+ klass->content_info_new_from_parser = content_info_new_from_parser;
+ klass->content_info_new_from_message = content_info_new_from_message;
+ klass->content_info_load = content_info_load;
+ klass->content_info_save = content_info_save;
+ klass->content_info_free = content_info_free;
+
+ klass->next_uid_string = next_uid_string;
+}
+
+static void
+camel_folder_summary_init (CamelFolderSummary *s)
+{
+ struct _CamelFolderSummaryPrivate *p;
+
+ p = _PRIVATE(s) = g_malloc0(sizeof(*p));
+
+ p->filter_charset = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
+
+ s->message_info_size = sizeof(CamelMessageInfo);
+ s->content_info_size = sizeof(CamelMessageContentInfo);
+
+ s->message_info_chunks = NULL;
+ s->content_info_chunks = NULL;
+
+#if defined (DOESTRV) || defined (DOEPOOLV)
+ s->message_info_strings = CAMEL_MESSAGE_INFO_LAST;
+#endif
+
+ s->version = CAMEL_FOLDER_SUMMARY_VERSION;
+ s->flags = 0;
+ s->time = 0;
+ s->nextuid = 1;
+
+ s->messages = g_ptr_array_new();
+ s->messages_uid = g_hash_table_new(g_str_hash, g_str_equal);
+
+ p->summary_lock = g_mutex_new();
+ p->io_lock = g_mutex_new();
+ p->filter_lock = g_mutex_new();
+ p->alloc_lock = g_mutex_new();
+ p->ref_lock = g_mutex_new();
+}
+
+static void free_o_name(void *key, void *value, void *data)
+{
+ camel_object_unref((CamelObject *)value);
+ g_free(key);
+}
+
+static void
+camel_folder_summary_finalize (CamelObject *obj)
+{
+ struct _CamelFolderSummaryPrivate *p;
+ CamelFolderSummary *s = (CamelFolderSummary *)obj;
+
+ p = _PRIVATE(obj);
+
+ camel_folder_summary_clear(s);
+ g_ptr_array_free(s->messages, TRUE);
+ g_hash_table_destroy(s->messages_uid);
+
+ g_hash_table_foreach(p->filter_charset, free_o_name, 0);
+ g_hash_table_destroy(p->filter_charset);
+
+ g_free(s->summary_path);
+
+ if (s->message_info_chunks)
+ e_memchunk_destroy(s->message_info_chunks);
+ if (s->content_info_chunks)
+ e_memchunk_destroy(s->content_info_chunks);
+
+ if (p->filter_index)
+ camel_object_unref((CamelObject *)p->filter_index);
+ if (p->filter_64)
+ camel_object_unref((CamelObject *)p->filter_64);
+ if (p->filter_qp)
+ camel_object_unref((CamelObject *)p->filter_qp);
+ if (p->filter_uu)
+ camel_object_unref((CamelObject *)p->filter_uu);
+ if (p->filter_save)
+ camel_object_unref((CamelObject *)p->filter_save);
+ if (p->filter_html)
+ camel_object_unref((CamelObject *)p->filter_html);
+
+ if (p->filter_stream)
+ camel_object_unref((CamelObject *)p->filter_stream);
+ if (p->index)
+ camel_object_unref((CamelObject *)p->index);
+
+ g_mutex_free(p->summary_lock);
+ g_mutex_free(p->io_lock);
+ g_mutex_free(p->filter_lock);
+ g_mutex_free(p->alloc_lock);
+ g_mutex_free(p->ref_lock);
+
+ g_free(p);
+}
+
+CamelType
+camel_folder_summary_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_object_get_type (), "CamelFolderSummary",
+ sizeof (CamelFolderSummary),
+ sizeof (CamelFolderSummaryClass),
+ (CamelObjectClassInitFunc) camel_folder_summary_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_folder_summary_init,
+ (CamelObjectFinalizeFunc) camel_folder_summary_finalize);
+ }
+
+ return type;
+}
+
+/**
+ * camel_folder_summary_new:
+ *
+ * Create a new CamelFolderSummary object.
+ *
+ * Return value: A new CamelFolderSummary widget.
+ **/
+CamelFolderSummary *
+camel_folder_summary_new (void)
+{
+ CamelFolderSummary *new = CAMEL_FOLDER_SUMMARY ( camel_object_new (camel_folder_summary_get_type ())); return new;
+}
+
+
+/**
+ * camel_folder_summary_set_filename:
+ * @s:
+ * @name:
+ *
+ * Set the filename where the summary will be loaded to/saved from.
+ **/
+void camel_folder_summary_set_filename(CamelFolderSummary *s, const char *name)
+{
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+
+ g_free(s->summary_path);
+ s->summary_path = g_strdup(name);
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+}
+
+/**
+ * camel_folder_summary_set_index:
+ * @s:
+ * @index:
+ *
+ * Set the index used to index body content. If the index is NULL, or
+ * not set (the default), no indexing of body content will take place.
+ *
+ * Unlike earlier behaviour, build_content need not be set to perform indexing.
+ **/
+void camel_folder_summary_set_index(CamelFolderSummary *s, CamelIndex *index)
+{
+ struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
+
+ if (p->index)
+ camel_object_unref((CamelObject *)p->index);
+
+ p->index = index;
+ if (index)
+ camel_object_ref((CamelObject *)index);
+}
+
+/**
+ * camel_folder_summary_set_build_content:
+ * @s:
+ * @state:
+ *
+ * Set a flag to tell the summary to build the content info summary
+ * (CamelMessageInfo.content). The default is not to build content info
+ * summaries.
+ **/
+void camel_folder_summary_set_build_content(CamelFolderSummary *s, gboolean state)
+{
+ s->build_content = state;
+}
+
+/**
+ * camel_folder_summary_count:
+ * @s:
+ *
+ * Get the number of summary items stored in this summary.
+ *
+ * Return value: The number of items int he summary.
+ **/
+int
+camel_folder_summary_count(CamelFolderSummary *s)
+{
+ return s->messages->len;
+}
+
+/**
+ * camel_folder_summary_index:
+ * @s:
+ * @i:
+ *
+ * Retrieve a summary item by index number.
+ *
+ * A referenced to the summary item is returned, which may be
+ * ref'd or free'd as appropriate.
+ *
+ * Return value: The summary item, or NULL if the index @i is out
+ * of range.
+ * It must be freed using camel_folder_summary_info_free().
+ **/
+CamelMessageInfo *
+camel_folder_summary_index(CamelFolderSummary *s, int i)
+{
+ CamelMessageInfo *info = NULL;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ CAMEL_SUMMARY_LOCK(s, ref_lock);
+
+ if (i<s->messages->len)
+ info = g_ptr_array_index(s->messages, i);
+
+ if (info)
+ info->refcount++;
+
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_index:
+ * @s:
+ * @i:
+ *
+ * Obtain a copy of the summary array. This is done atomically,
+ * so cannot contain empty entries.
+ *
+ * It must be freed using camel_folder_summary_array_free().
+ **/
+GPtrArray *
+camel_folder_summary_array(CamelFolderSummary *s)
+{
+ CamelMessageInfo *info;
+ GPtrArray *res = g_ptr_array_new();
+ int i;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ CAMEL_SUMMARY_LOCK(s, ref_lock);
+
+ g_ptr_array_set_size(res, s->messages->len);
+ for (i=0;i<s->messages->len;i++) {
+ info = res->pdata[i] = g_ptr_array_index(s->messages, i);
+ info->refcount++;
+ }
+
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+
+ return res;
+}
+
+/**
+ * camel_folder_summary_array_free:
+ * @s:
+ * @array:
+ *
+ * Free the folder summary array.
+ **/
+void
+camel_folder_summary_array_free(CamelFolderSummary *s, GPtrArray *array)
+{
+ int i;
+
+ for (i=0;i<array->len;i++)
+ camel_folder_summary_info_free(s, array->pdata[i]);
+
+ g_ptr_array_free(array, TRUE);
+}
+
+/**
+ * camel_folder_summary_uid:
+ * @s:
+ * @uid:
+ *
+ * Retrieve a summary item by uid.
+ *
+ * A referenced to the summary item is returned, which may be
+ * ref'd or free'd as appropriate.
+ *
+ * Return value: The summary item, or NULL if the uid @uid
+ * is not available.
+ * It must be freed using camel_folder_summary_info_free().
+ **/
+CamelMessageInfo *
+camel_folder_summary_uid(CamelFolderSummary *s, const char *uid)
+{
+ CamelMessageInfo *info;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ CAMEL_SUMMARY_LOCK(s, ref_lock);
+
+ info = g_hash_table_lookup(s->messages_uid, uid);
+
+ if (info)
+ info->refcount++;
+
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_next_uid:
+ * @s:
+ *
+ * Generate a new unique uid value as an integer. This
+ * may be used to create a unique sequence of numbers.
+ *
+ * Return value: The next unique uid value.
+ **/
+guint32 camel_folder_summary_next_uid(CamelFolderSummary *s)
+{
+ guint32 uid;
+
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+
+ uid = s->nextuid++;
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+
+ /* FIXME: sync this to disk */
+/* summary_header_save(s);*/
+ return uid;
+}
+
+/**
+ * camel_folder_summary_set_uid:
+ * @s:
+ * @uid: The next minimum uid to assign. To avoid clashing
+ * uid's, set this to the uid of a given messages + 1.
+ *
+ * Set the next minimum uid available. This can be used to
+ * ensure new uid's do not clash with existing uid's.
+ **/
+void camel_folder_summary_set_uid(CamelFolderSummary *s, guint32 uid)
+{
+ /* TODO: sync to disk? */
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+
+ s->nextuid = MAX(s->nextuid, uid);
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+}
+
+/**
+ * camel_folder_summary_next_uid_string:
+ * @s:
+ *
+ * Retrieve the next uid, but as a formatted string.
+ *
+ * Return value: The next uid as an unsigned integer string.
+ * This string must be freed by the caller.
+ **/
+char *
+camel_folder_summary_next_uid_string(CamelFolderSummary *s)
+{
+ return ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->next_uid_string(s);
+}
+
+/* loads the content descriptions, recursively */
+static CamelMessageContentInfo *
+perform_content_info_load(CamelFolderSummary *s, FILE *in)
+{
+ int i;
+ guint32 count;
+ CamelMessageContentInfo *ci, *part;
+
+ ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_load(s, in);
+ if (ci == NULL)
+ return NULL;
+
+ if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500) {
+ camel_folder_summary_content_info_free(s, ci);
+ return NULL;
+ }
+
+ for (i=0;i<count;i++) {
+ part = perform_content_info_load(s, in);
+ if (part) {
+ my_list_append((struct _node **)&ci->childs, (struct _node *)part);
+ part->parent = ci;
+ } else {
+ d(fprintf (stderr, "Summary file format messed up?"));
+ camel_folder_summary_content_info_free(s, ci);
+ return NULL;
+ }
+ }
+ return ci;
+}
+
+int
+camel_folder_summary_load(CamelFolderSummary *s)
+{
+ FILE *in;
+ int i;
+ CamelMessageInfo *mi;
+
+ if (s->summary_path == NULL)
+ return 0;
+
+ in = fopen(s->summary_path, "r");
+ if (in == NULL)
+ return -1;
+
+ CAMEL_SUMMARY_LOCK(s, io_lock);
+ if ( ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in) == -1)
+ goto error;
+
+ /* now read in each message ... */
+ for (i=0;i<s->saved_count;i++) {
+ mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_load(s, in);
+
+ if (mi == NULL)
+ goto error;
+
+ if (s->build_content) {
+ mi->content = perform_content_info_load(s, in);
+ if (mi->content == NULL) {
+ camel_folder_summary_info_free(s, mi);
+ goto error;
+ }
+ }
+
+ camel_folder_summary_add(s, mi);
+ }
+
+ CAMEL_SUMMARY_UNLOCK(s, io_lock);
+
+ if (fclose (in) != 0)
+ return -1;
+
+ s->flags &= ~CAMEL_SUMMARY_DIRTY;
+
+ return 0;
+
+error:
+ if (errno != EINVAL)
+ g_warning ("Cannot load summary file: `%s': %s", s->summary_path, g_strerror (errno));
+
+ CAMEL_SUMMARY_UNLOCK(s, io_lock);
+ fclose (in);
+ s->flags |= ~CAMEL_SUMMARY_DIRTY;
+
+ return -1;
+}
+
+/* saves the content descriptions, recursively */
+static int
+perform_content_info_save(CamelFolderSummary *s, FILE *out, CamelMessageContentInfo *ci)
+{
+ CamelMessageContentInfo *part;
+
+ if (((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (s)))->content_info_save (s, out, ci) == -1)
+ return -1;
+
+ if (camel_file_util_encode_uint32 (out, my_list_size ((struct _node **)&ci->childs)) == -1)
+ return -1;
+
+ part = ci->childs;
+ while (part) {
+ if (perform_content_info_save (s, out, part) == -1)
+ return -1;
+ part = part->next;
+ }
+
+ return 0;
+}
+
+/**
+ * camel_folder_summary_save:
+ * @s:
+ *
+ * Writes the summary to disk. The summary is only written if changes
+ * have occured.
+ *
+ * Return value: Returns -1 on error.
+ **/
+int
+camel_folder_summary_save(CamelFolderSummary *s)
+{
+ FILE *out;
+ int fd, i;
+ guint32 count;
+ CamelMessageInfo *mi;
+ char *path;
+
+ if (s->summary_path == NULL
+ || (s->flags & CAMEL_SUMMARY_DIRTY) == 0)
+ return 0;
+
+ path = alloca(strlen(s->summary_path)+4);
+ sprintf(path, "%s~", s->summary_path);
+ fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0600);
+ if (fd == -1)
+ return -1;
+ out = fdopen(fd, "w");
+ if (out == NULL) {
+ i = errno;
+ unlink(path);
+ close(fd);
+ errno = i;
+ return -1;
+ }
+
+ io(printf("saving header\n"));
+
+ CAMEL_SUMMARY_LOCK(s, io_lock);
+
+ if (((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_save(s, out) == -1)
+ goto exception;
+
+ /* now write out each message ... */
+ /* we check ferorr when done for i/o errors */
+ count = s->messages->len;
+ for (i = 0; i < count; i++) {
+ mi = s->messages->pdata[i];
+ if (((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS (s)))->message_info_save (s, out, mi) == -1)
+ goto exception;
+
+ if (s->build_content) {
+ if (perform_content_info_save (s, out, mi->content) == -1)
+ goto exception;
+ }
+ }
+
+ if (fflush (out) != 0 || fsync (fileno (out)) == -1)
+ goto exception;
+
+ fclose (out);
+
+ CAMEL_SUMMARY_UNLOCK(s, io_lock);
+
+ if (rename(path, s->summary_path) == -1) {
+ i = errno;
+ unlink(path);
+ errno = i;
+ return -1;
+ }
+
+ s->flags &= ~CAMEL_SUMMARY_DIRTY;
+ return 0;
+
+ exception:
+
+ i = errno;
+
+ fclose (out);
+
+ CAMEL_SUMMARY_UNLOCK(s, io_lock);
+
+ unlink (path);
+ errno = i;
+
+ return -1;
+}
+
+/**
+ * camel_folder_summary_header_load:
+ * @s: Summary object.
+ *
+ * Only load the header information from the summary,
+ * keep the rest on disk. This should only be done on
+ * a fresh summary object.
+ *
+ * Return value: -1 on error.
+ **/
+int camel_folder_summary_header_load(CamelFolderSummary *s)
+{
+ FILE *in;
+ int ret;
+
+ if (s->summary_path == NULL)
+ return 0;
+
+ in = fopen(s->summary_path, "r");
+ if (in == NULL)
+ return -1;
+
+ CAMEL_SUMMARY_LOCK(s, io_lock);
+ ret = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->summary_header_load(s, in);
+ CAMEL_SUMMARY_UNLOCK(s, io_lock);
+
+ fclose(in);
+ s->flags &= ~CAMEL_SUMMARY_DIRTY;
+ return ret;
+}
+
+static int
+summary_assign_uid(CamelFolderSummary *s, CamelMessageInfo *info)
+{
+ const char *uid;
+ CamelMessageInfo *mi;
+
+ uid = camel_message_info_uid(info);
+ if (uid == NULL || uid[0] == 0) {
+ camel_message_info_set_uid(info, camel_folder_summary_next_uid_string(s));
+ uid = camel_message_info_uid(info);
+ }
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+
+ while ((mi = g_hash_table_lookup(s->messages_uid, uid))) {
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ if (mi == info)
+ return 0;
+ d(printf ("Trying to insert message with clashing uid (%s). new uid re-assigned", camel_message_info_uid(info)));
+ camel_message_info_set_uid(info, camel_folder_summary_next_uid_string(s));
+ uid = camel_message_info_uid(info);
+ info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ }
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ return 1;
+}
+
+/**
+ * camel_folder_summary_add:
+ * @s:
+ * @info:
+ *
+ * Adds a new @info record to the summary. If @info->uid is NULL, then a new
+ * uid is automatically re-assigned by calling :next_uid_string().
+ *
+ * The @info record should have been generated by calling one of the
+ * info_new_*() functions, as it will be free'd based on the summary
+ * class. And MUST NOT be allocated directly using malloc.
+ **/
+void camel_folder_summary_add(CamelFolderSummary *s, CamelMessageInfo *info)
+{
+ if (info == NULL)
+ return;
+
+ if (summary_assign_uid(s, info) == 0)
+ return;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+
+/* unnecessary for pooled vectors */
+#ifdef DOESTRV
+ /* this is vitally important, and also if this is ever modified, then
+ the hash table needs to be resynced */
+ info->strings = e_strv_pack(info->strings);
+#endif
+
+ g_ptr_array_add(s->messages, info);
+ g_hash_table_insert(s->messages_uid, (char *)camel_message_info_uid(info), info);
+ s->flags |= CAMEL_SUMMARY_DIRTY;
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+}
+
+/**
+ * camel_folder_summary_add_from_header:
+ * @s:
+ * @h:
+ *
+ * Build a new info record based on a set of headers, and add it to the
+ * summary.
+ *
+ * Note that this function should not be used if build_content_info has
+ * been specified for this summary.
+ *
+ * Return value: The newly added record.
+ **/
+CamelMessageInfo *camel_folder_summary_add_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
+{
+ CamelMessageInfo *info = camel_folder_summary_info_new_from_header(s, h);
+
+ camel_folder_summary_add(s, info);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_add_from_parser:
+ * @s:
+ * @mp:
+ *
+ * Build a new info record based on the current position of a CamelMimeParser.
+ *
+ * The parser should be positioned before the start of the message to summarise.
+ * This function may be used if build_contnet_info or an index has been
+ * specified for the summary.
+ *
+ * Return value: The newly added record.
+ **/
+CamelMessageInfo *camel_folder_summary_add_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
+{
+ CamelMessageInfo *info = camel_folder_summary_info_new_from_parser(s, mp);
+
+ camel_folder_summary_add(s, info);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_add_from_message:
+ * @s:
+ * @msg:
+ *
+ * Add a summary item from an existing message.
+ *
+ * Return value:
+ **/
+CamelMessageInfo *camel_folder_summary_add_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
+{
+ CamelMessageInfo *info = camel_folder_summary_info_new_from_message(s, msg);
+
+ camel_folder_summary_add(s, info);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_info_new_from_header:
+ * @s:
+ * @h:
+ *
+ * Create a new info record from a header.
+ *
+ * Return value: Guess? This info record MUST be freed using
+ * camel_folder_summary_info_free(), camel_message_info_free() will not work.
+ **/
+CamelMessageInfo *camel_folder_summary_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
+{
+ return ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s))) -> message_info_new(s, h);
+}
+
+/**
+ * camel_folder_summary_info_new_from_parser:
+ * @s:
+ * @mp:
+ *
+ * Create a new info record from a parser. If the parser cannot
+ * determine a uid, then none will be assigned.
+
+ * If indexing is enabled, and the parser cannot determine a new uid, then
+ * one is automatically assigned.
+ *
+ * If indexing is enabled, then the content will be indexed based
+ * on this new uid. In this case, the message info MUST be
+ * added using :add().
+ *
+ * Once complete, the parser will be positioned at the end of
+ * the message.
+ *
+ * Return value: Guess? This info record MUST be freed using
+ * camel_folder_summary_info_free(), camel_message_info_free() will not work.
+ **/
+CamelMessageInfo *camel_folder_summary_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
+{
+ CamelMessageInfo *info = NULL;
+ char *buffer;
+ size_t len;
+ struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
+ off_t start;
+ CamelIndexName *name = NULL;
+
+ /* should this check the parser is in the right state, or assume it is?? */
+
+ start = camel_mime_parser_tell(mp);
+ if (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_EOF) {
+ info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_parser(s, mp);
+
+ camel_mime_parser_unstep(mp);
+
+ /* assign a unique uid, this is slightly 'wrong' as we do not really
+ * know if we are going to store this in the summary, but no matter */
+ if (p->index)
+ summary_assign_uid(s, info);
+
+ CAMEL_SUMMARY_LOCK(s, filter_lock);
+
+ if (p->index) {
+ if (p->filter_index == NULL)
+ p->filter_index = camel_mime_filter_index_new_index(p->index);
+ camel_index_delete_name(p->index, camel_message_info_uid(info));
+ name = camel_index_add_name(p->index, camel_message_info_uid(info));
+ camel_mime_filter_index_set_name(p->filter_index, name);
+ }
+
+ /* always scan the content info, even if we dont save it */
+ info->content = summary_build_content_info(s, info, mp);
+
+ if (name) {
+ camel_index_write_name(p->index, name);
+ camel_object_unref((CamelObject *)name);
+ camel_mime_filter_index_set_name(p->filter_index, NULL);
+ }
+
+ CAMEL_SUMMARY_UNLOCK(s, filter_lock);
+
+ info->size = camel_mime_parser_tell(mp) - start;
+ }
+ return info;
+}
+
+/**
+ * camel_folder_summary_info_new_from_message:
+ * @:
+ * @:
+ *
+ * Create a summary item from a message.
+ *
+ * Return value:
+ **/
+CamelMessageInfo *camel_folder_summary_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
+{
+ CamelMessageInfo *info;
+ struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
+ CamelIndexName *name = NULL;
+
+ info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new_from_message(s, msg);
+
+ /* assign a unique uid, this is slightly 'wrong' as we do not really
+ * know if we are going to store this in the summary, but we need it set for indexing */
+ if (p->index)
+ summary_assign_uid(s, info);
+
+ CAMEL_SUMMARY_LOCK(s, filter_lock);
+
+ if (p->index) {
+ if (p->filter_index == NULL)
+ p->filter_index = camel_mime_filter_index_new_index(p->index);
+ camel_index_delete_name(p->index, camel_message_info_uid(info));
+ name = camel_index_add_name(p->index, camel_message_info_uid(info));
+ camel_mime_filter_index_set_name(p->filter_index, name);
+
+ if (p->filter_stream == NULL) {
+ CamelStream *null = camel_stream_null_new();
+
+ p->filter_stream = camel_stream_filter_new_with_stream(null);
+ camel_object_unref((CamelObject *)null);
+ }
+ }
+
+ info->content = summary_build_content_info_message(s, info, (CamelMimePart *)msg);
+
+ if (name) {
+ camel_index_write_name(p->index, name);
+ camel_object_unref((CamelObject *)name);
+ camel_mime_filter_index_set_name(p->filter_index, NULL);
+ }
+
+ CAMEL_SUMMARY_UNLOCK(s, filter_lock);
+
+ return info;
+}
+
+/**
+ * camel_folder_summary_content_info_free:
+ * @s:
+ * @ci:
+ *
+ * Free the content info @ci, and all associated memory.
+ **/
+void
+camel_folder_summary_content_info_free(CamelFolderSummary *s, CamelMessageContentInfo *ci)
+{
+ CamelMessageContentInfo *pw, *pn;
+
+ pw = ci->childs;
+ ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_free(s, ci);
+ while (pw) {
+ pn = pw->next;
+ camel_folder_summary_content_info_free(s, pw);
+ pw = pn;
+ }
+}
+
+/**
+ * camel_folder_summary_info_free:
+ * @s:
+ * @mi:
+ *
+ * Unref and potentially free the message info @mi, and all associated memory.
+ **/
+void camel_folder_summary_info_free(CamelFolderSummary *s, CamelMessageInfo *mi)
+{
+ CamelMessageContentInfo *ci;
+
+ g_assert(mi);
+ g_assert(s);
+
+ CAMEL_SUMMARY_LOCK(s, ref_lock);
+
+ g_assert(mi->refcount >= 1);
+
+ mi->refcount--;
+ if (mi->refcount > 0) {
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+ return;
+ }
+
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+
+ ci = mi->content;
+
+ ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_free(s, mi);
+ if (s->build_content && ci) {
+ camel_folder_summary_content_info_free(s, ci);
+ }
+}
+
+/**
+ * camel_folder_summary_info_ref:
+ * @s:
+ * @mi:
+ *
+ * Add an extra reference to @mi.
+ **/
+void camel_folder_summary_info_ref(CamelFolderSummary *s, CamelMessageInfo *mi)
+{
+ g_assert(mi);
+ g_assert(s);
+
+ CAMEL_SUMMARY_LOCK(s, ref_lock);
+ g_assert(mi->refcount >= 1);
+ mi->refcount++;
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+}
+
+/**
+ * camel_folder_summary_touch:
+ * @s:
+ *
+ * Mark the summary as changed, so that a save will save it.
+ **/
+void
+camel_folder_summary_touch(CamelFolderSummary *s)
+{
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ s->flags |= CAMEL_SUMMARY_DIRTY;
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+}
+
+/**
+ * camel_folder_summary_clear:
+ * @s:
+ *
+ * Empty the summary contents.
+ **/
+void
+camel_folder_summary_clear(CamelFolderSummary *s)
+{
+ int i;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ if (camel_folder_summary_count(s) == 0) {
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ return;
+ }
+
+ for (i=0;i<s->messages->len;i++)
+ camel_folder_summary_info_free(s, s->messages->pdata[i]);
+
+ g_ptr_array_set_size(s->messages, 0);
+ g_hash_table_destroy(s->messages_uid);
+ s->messages_uid = g_hash_table_new(g_str_hash, g_str_equal);
+ s->flags |= CAMEL_SUMMARY_DIRTY;
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+}
+
+/**
+ * camel_folder_summary_remove:
+ * @s:
+ * @info:
+ *
+ * Remove a specific @info record from the summary.
+ **/
+void camel_folder_summary_remove(CamelFolderSummary *s, CamelMessageInfo *info)
+{
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
+ g_ptr_array_remove(s->messages, info);
+ s->flags |= CAMEL_SUMMARY_DIRTY;
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+
+ camel_folder_summary_info_free(s, info);
+}
+
+/**
+ * camel_folder_summary_remove_uid:
+ * @s:
+ * @uid:
+ *
+ * Remove a specific info record from the summary, by @uid.
+ **/
+void camel_folder_summary_remove_uid(CamelFolderSummary *s, const char *uid)
+{
+ CamelMessageInfo *oldinfo;
+ char *olduid;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ CAMEL_SUMMARY_LOCK(s, ref_lock);
+ if (g_hash_table_lookup_extended(s->messages_uid, uid, (void *)&olduid, (void *)&oldinfo)) {
+ /* make sure it doesn't vanish while we're removing it */
+ oldinfo->refcount++;
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ camel_folder_summary_remove(s, oldinfo);
+ camel_folder_summary_info_free(s, oldinfo);
+ } else {
+ CAMEL_SUMMARY_UNLOCK(s, ref_lock);
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ }
+}
+
+/**
+ * camel_folder_summary_remove_index:
+ * @s:
+ * @index:
+ *
+ * Remove a specific info record from the summary, by index.
+ **/
+void camel_folder_summary_remove_index(CamelFolderSummary *s, int index)
+{
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ if (index < s->messages->len) {
+ CamelMessageInfo *info = s->messages->pdata[index];
+
+ g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
+ g_ptr_array_remove_index(s->messages, index);
+ s->flags |= CAMEL_SUMMARY_DIRTY;
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ camel_folder_summary_info_free(s, info);
+ } else {
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ }
+}
+
+/**
+ * camel_folder_summary_remove_range:
+ * @s:
+ * @start: initial index
+ * @end: last index to remove
+ *
+ * Removes an indexed range of info records.
+ **/
+void camel_folder_summary_remove_range(CamelFolderSummary *s, int start, int end)
+{
+ if (end < start)
+ return;
+
+ CAMEL_SUMMARY_LOCK(s, summary_lock);
+ if (start < s->messages->len) {
+ CamelMessageInfo **infos;
+ int i;
+
+ end = MIN(end+1, s->messages->len);
+ infos = g_malloc((end-start)*sizeof(infos[0]));
+
+ for (i=start;i<end;i++) {
+ CamelMessageInfo *info = s->messages->pdata[i];
+
+ infos[i-start] = info;
+ g_hash_table_remove(s->messages_uid, camel_message_info_uid(info));
+ }
+
+ memmove(s->messages->pdata+start, s->messages->pdata+end, (s->messages->len-end)*sizeof(s->messages->pdata[0]));
+ g_ptr_array_set_size(s->messages, s->messages->len - (end - start));
+ s->flags |= CAMEL_SUMMARY_DIRTY;
+
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+
+ for (i=start;i<end;i++)
+ camel_folder_summary_info_free(s, infos[i-start]);
+ g_free(infos);
+ } else {
+ CAMEL_SUMMARY_UNLOCK(s, summary_lock);
+ }
+}
+
+/* should be sorted, for binary search */
+/* This is a tokenisation mechanism for strings written to the
+ summary - to save space.
+ This list can have at most 31 words. */
+static char * tokens[] = {
+ "7bit",
+ "8bit",
+ "alternative",
+ "application",
+ "base64",
+ "boundary",
+ "charset",
+ "filename",
+ "html",
+ "image",
+ "iso-8859-1",
+ "iso-8859-8",
+ "message",
+ "mixed",
+ "multipart",
+ "name",
+ "octet-stream",
+ "parallel",
+ "plain",
+ "postscript",
+ "quoted-printable",
+ "related",
+ "rfc822",
+ "text",
+ "us-ascii", /* 25 words */
+};
+
+#define tokens_len (sizeof(tokens)/sizeof(tokens[0]))
+
+/* baiscally ...
+ 0 = null
+ 1-tokens_len == tokens[id-1]
+ >=32 string, length = n-32
+*/
+
+#ifdef USE_BSEARCH
+static int
+token_search_cmp(char *key, char **index)
+{
+ d(printf("comparing '%s' to '%s'\n", key, *index));
+ return strcmp(key, *index);
+}
+#endif
+
+/**
+ * camel_folder_summary_encode_token:
+ * @out:
+ * @str:
+ *
+ * Encode a string value, but use tokenisation and compression
+ * to reduce the size taken for common mailer words. This
+ * can still be used to encode normal strings as well.
+ *
+ * Return value: -1 on error.
+ **/
+int
+camel_folder_summary_encode_token(FILE *out, const char *str)
+{
+ io(printf("Encoding token: '%s'\n", str));
+
+ if (str == NULL) {
+ return camel_file_util_encode_uint32(out, 0);
+ } else {
+ int len = strlen(str);
+ int i, token=-1;
+
+ if (len <= 16) {
+ char lower[32];
+ char **match;
+
+ for (i=0;i<len;i++)
+ lower[i] = tolower(str[i]);
+ lower[i] = 0;
+#ifdef USE_BSEARCH
+ match = bsearch(lower, tokens, tokens_len, sizeof(char *), (int (*)(const void *, const void *))token_search_cmp);
+ if (match)
+ token = match-tokens;
+#else
+ for (i=0;i<tokens_len;i++) {
+ if (!strcmp(tokens[i], lower)) {
+ token = i;
+ break;
+ }
+ }
+#endif
+ }
+ if (token != -1) {
+ return camel_file_util_encode_uint32(out, token+1);
+ } else {
+ if (camel_file_util_encode_uint32(out, len+32) == -1)
+ return -1;
+ if (fwrite(str, len, 1, out) != 1)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * camel_folder_summary_decode_token:
+ * @in:
+ * @str:
+ *
+ * Decode a token value.
+ *
+ * Return value: -1 on error.
+ **/
+int
+camel_folder_summary_decode_token(FILE *in, char **str)
+{
+ char *ret;
+ guint32 len;
+
+ io(printf("Decode token ...\n"));
+
+ if (camel_file_util_decode_uint32(in, &len) == -1) {
+ io(printf ("Could not decode token from file"));
+ *str = NULL;
+ return -1;
+ }
+
+ if (len<32) {
+ if (len <= 0) {
+ ret = NULL;
+ } else if (len<= tokens_len) {
+ ret = g_strdup(tokens[len-1]);
+ } else {
+ io(printf ("Invalid token encountered: %d", len));
+ *str = NULL;
+ return -1;
+ }
+ } else if (len > 10240) {
+ io(printf ("Got broken string header length: %d bytes", len));
+ *str = NULL;
+ return -1;
+ } else {
+ len -= 32;
+ ret = g_malloc(len+1);
+ if (len > 0 && fread(ret, len, 1, in) != 1) {
+ g_free(ret);
+ *str = NULL;
+ return -1;
+ }
+ ret[len]=0;
+ }
+
+ io(printf("Token = '%s'\n", ret));
+
+ *str = ret;
+ return 0;
+}
+
+static struct _node *
+my_list_append(struct _node **list, struct _node *n)
+{
+ struct _node *ln = (struct _node *)list;
+ while (ln->next)
+ ln = ln->next;
+ n->next = 0;
+ ln->next = n;
+ return n;
+}
+
+static int
+my_list_size(struct _node **list)
+{
+ int len = 0;
+ struct _node *ln = (struct _node *)list;
+ while (ln->next) {
+ ln = ln->next;
+ len++;
+ }
+ return len;
+}
+
+static int
+summary_header_load(CamelFolderSummary *s, FILE *in)
+{
+ fseek(in, 0, SEEK_SET);
+
+ io(printf("Loading header\n"));
+
+ if (camel_file_util_decode_fixed_int32(in, &s->version) == -1)
+ return -1;
+
+ /* Legacy version check, before version 12 we have no upgrade knowledge */
+ if ((s->version > 0xff) && (s->version & 0xff) < 12) {
+ io(printf ("Summary header version mismatch"));
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!(s->version < 0x100 && s->version >= 13))
+ io(printf("Loading legacy summary\n"));
+ else
+ io(printf("loading new-format summary\n"));
+
+ /* legacy version */
+ if (camel_file_util_decode_fixed_int32(in, &s->flags) == -1
+ || camel_file_util_decode_fixed_int32(in, &s->nextuid) == -1
+ || camel_file_util_decode_time_t(in, &s->time) == -1
+ || camel_file_util_decode_fixed_int32(in, &s->saved_count) == -1) {
+ return -1;
+ }
+
+ /* version 13 */
+ if (s->version < 0x100 && s->version >= 13
+ && (camel_file_util_decode_fixed_int32(in, &s->unread_count) == -1
+ || camel_file_util_decode_fixed_int32(in, &s->deleted_count) == -1
+ || camel_file_util_decode_fixed_int32(in, &s->junk_count) == -1)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+summary_header_save(CamelFolderSummary *s, FILE *out)
+{
+ int unread = 0, deleted = 0, junk = 0, count, i;
+
+ fseek(out, 0, SEEK_SET);
+
+ io(printf("Savining header\n"));
+
+ /* we always write out the current version */
+ camel_file_util_encode_fixed_int32(out, CAMEL_FOLDER_SUMMARY_VERSION);
+ camel_file_util_encode_fixed_int32(out, s->flags);
+ camel_file_util_encode_fixed_int32(out, s->nextuid);
+ camel_file_util_encode_time_t(out, s->time);
+
+ count = camel_folder_summary_count(s);
+ for (i=0; i<count; i++) {
+ CamelMessageInfo *info = camel_folder_summary_index(s, i);
+
+ if (info == NULL)
+ continue;
+
+ if ((info->flags & CAMEL_MESSAGE_SEEN) == 0)
+ unread++;
+ if ((info->flags & CAMEL_MESSAGE_DELETED) != 0)
+ deleted++;
+ if ((info->flags & CAMEL_MESSAGE_JUNK) != 0)
+ junk++;
+
+ camel_folder_summary_info_free(s, info);
+ }
+
+ camel_file_util_encode_fixed_int32(out, count);
+ camel_file_util_encode_fixed_int32(out, unread);
+ camel_file_util_encode_fixed_int32(out, deleted);
+
+ return camel_file_util_encode_fixed_int32(out, junk);
+}
+
+/* are these even useful for anything??? */
+static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
+{
+ CamelMessageInfo *mi = NULL;
+ int state;
+
+ state = camel_mime_parser_state(mp);
+ switch (state) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new(s, camel_mime_parser_headers_raw(mp));
+ break;
+ default:
+ g_error("Invalid parser state");
+ }
+
+ return mi;
+}
+
+static CamelMessageContentInfo * content_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
+{
+ CamelMessageContentInfo *ci = NULL;
+
+ switch (camel_mime_parser_state(mp)) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new(s, camel_mime_parser_headers_raw(mp));
+ if (ci) {
+ ci->type = camel_mime_parser_content_type(mp);
+ camel_content_type_ref(ci->type);
+ }
+ break;
+ default:
+ g_error("Invalid parser state");
+ }
+
+ return ci;
+}
+
+static CamelMessageInfo * message_info_new_from_message(CamelFolderSummary *s, CamelMimeMessage *msg)
+{
+ CamelMessageInfo *mi;
+
+ mi = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->message_info_new(s, ((CamelMimePart *)msg)->headers);
+
+ return mi;
+}
+
+static CamelMessageContentInfo * content_info_new_from_message(CamelFolderSummary *s, CamelMimePart *mp)
+{
+ CamelMessageContentInfo *ci;
+
+ ci = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new(s, mp->headers);
+
+ return ci;
+}
+
+static char *
+summary_format_address(struct _camel_header_raw *h, const char *name, const char *charset)
+{
+ struct _camel_header_address *addr;
+ const char *text;
+ char *ret;
+
+ text = camel_header_raw_find (&h, name, NULL);
+ addr = camel_header_address_decode (text, charset);
+ if (addr) {
+ ret = camel_header_address_list_format (addr);
+ camel_header_address_list_clear (&addr);
+ } else {
+ ret = g_strdup (text);
+ }
+
+ return ret;
+}
+
+static char *
+summary_format_string (struct _camel_header_raw *h, const char *name, const char *charset)
+{
+ const char *text;
+
+ text = camel_header_raw_find (&h, name, NULL);
+ if (text) {
+ while (isspace ((unsigned) *text))
+ text++;
+ return camel_header_decode_string (text, charset);
+ } else {
+ return NULL;
+ }
+}
+
+/**
+ * camel_folder_summary_info_new:
+ * @s:
+ *
+ * Allocate a new camel message info, suitable for adding
+ * to this summary.
+ *
+ * Return value:
+ **/
+CamelMessageInfo *
+camel_folder_summary_info_new(CamelFolderSummary *s)
+{
+ CamelMessageInfo *mi;
+
+ CAMEL_SUMMARY_LOCK(s, alloc_lock);
+ if (s->message_info_chunks == NULL)
+ s->message_info_chunks = e_memchunk_new(32, s->message_info_size);
+ mi = e_memchunk_alloc(s->message_info_chunks);
+ CAMEL_SUMMARY_UNLOCK(s, alloc_lock);
+
+ memset(mi, 0, s->message_info_size);
+#ifdef DOEPOOLV
+ mi->strings = e_poolv_new (s->message_info_strings);
+#endif
+#ifdef DOESTRV
+ mi->strings = e_strv_new(s->message_info_strings);
+#endif
+ mi->refcount = 1;
+ return mi;
+}
+
+/**
+ * camel_folder_summary_content_info_new:
+ * @s:
+ *
+ * Allocate a new camel message content info, suitable for adding
+ * to this summary.
+ *
+ * Return value:
+ **/
+CamelMessageContentInfo *
+camel_folder_summary_content_info_new(CamelFolderSummary *s)
+{
+ CamelMessageContentInfo *ci;
+
+ CAMEL_SUMMARY_LOCK(s, alloc_lock);
+ if (s->content_info_chunks == NULL)
+ s->content_info_chunks = e_memchunk_new(32, s->content_info_size);
+ ci = e_memchunk_alloc(s->content_info_chunks);
+ CAMEL_SUMMARY_UNLOCK(s, alloc_lock);
+
+ memset(ci, 0, s->content_info_size);
+ return ci;
+}
+
+static CamelMessageInfo *
+message_info_new(CamelFolderSummary *s, struct _camel_header_raw *h)
+{
+ CamelMessageInfo *mi;
+ const char *received;
+ guchar digest[16];
+ struct _camel_header_references *refs, *irt, *scan;
+ char *msgid;
+ int count;
+ char *subject, *from, *to, *cc, *mlist;
+ CamelContentType *ct = NULL;
+ const char *content, *charset = NULL;
+
+ mi = camel_folder_summary_info_new(s);
+
+ if ((content = camel_header_raw_find(&h, "Content-Type", NULL))
+ && (ct = camel_content_type_decode(content))
+ && (charset = camel_content_type_param(ct, "charset"))
+ && (g_ascii_strcasecmp(charset, "us-ascii") == 0))
+ charset = NULL;
+
+ charset = charset ? e_iconv_charset_name (charset) : NULL;
+
+ subject = summary_format_string(h, "subject", charset);
+ from = summary_format_address(h, "from", charset);
+ to = summary_format_address(h, "to", charset);
+ cc = summary_format_address(h, "cc", charset);
+ mlist = camel_header_raw_check_mailing_list(&h);
+
+ if (ct)
+ camel_content_type_unref(ct);
+
+#ifdef DOEPOOLV
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_SUBJECT, subject, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_FROM, from, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_TO, to, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_CC, cc, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_MLIST, mlist, TRUE);
+#elif defined (DOESTRV)
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_SUBJECT, subject);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_FROM, from);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_TO, to);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_CC, cc);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_MLIST, mlist);
+#else
+ mi->subject = subject;
+ mi->from = from;
+ mi->to = to;
+ mi->cc = cc;
+ mi->mlist = mlist;
+#endif
+
+ mi->user_flags = NULL;
+ mi->user_tags = NULL;
+ mi->date_sent = camel_header_decode_date(camel_header_raw_find(&h, "date", NULL), NULL);
+ received = camel_header_raw_find(&h, "received", NULL);
+ if (received)
+ received = strrchr(received, ';');
+ if (received)
+ mi->date_received = camel_header_decode_date(received + 1, NULL);
+ else
+ mi->date_received = 0;
+
+ msgid = camel_header_msgid_decode(camel_header_raw_find(&h, "message-id", NULL));
+ if (msgid) {
+ md5_get_digest(msgid, strlen(msgid), digest);
+ memcpy(mi->message_id.id.hash, digest, sizeof(mi->message_id.id.hash));
+ g_free(msgid);
+ }
+
+ /* decode our references and in-reply-to headers */
+ refs = camel_header_references_decode (camel_header_raw_find (&h, "references", NULL));
+ irt = camel_header_references_inreplyto_decode (camel_header_raw_find (&h, "in-reply-to", NULL));
+ if (refs || irt) {
+ if (irt) {
+ /* The References field is populated from the ``References'' and/or ``In-Reply-To''
+ headers. If both headers exist, take the first thing in the In-Reply-To header
+ that looks like a Message-ID, and append it to the References header. */
+
+ if (refs)
+ irt->next = refs;
+
+ refs = irt;
+ }
+
+ count = camel_header_references_list_size(&refs);
+ mi->references = g_malloc(sizeof(*mi->references) + ((count-1) * sizeof(mi->references->references[0])));
+ count = 0;
+ scan = refs;
+ while (scan) {
+ md5_get_digest(scan->id, strlen(scan->id), digest);
+ memcpy(mi->references->references[count].id.hash, digest, sizeof(mi->message_id.id.hash));
+ count++;
+ scan = scan->next;
+ }
+ mi->references->size = count;
+ camel_header_references_list_clear(&refs);
+ }
+
+ return mi;
+}
+
+
+static CamelMessageInfo *
+message_info_load(CamelFolderSummary *s, FILE *in)
+{
+ CamelMessageInfo *mi;
+ guint count;
+ int i;
+ char *subject, *from, *to, *cc, *mlist, *uid;;
+
+ mi = camel_folder_summary_info_new(s);
+
+ io(printf("Loading message info\n"));
+
+ camel_file_util_decode_string(in, &uid);
+ camel_file_util_decode_uint32(in, &mi->flags);
+ camel_file_util_decode_uint32(in, &mi->size);
+ camel_file_util_decode_time_t(in, &mi->date_sent);
+ camel_file_util_decode_time_t(in, &mi->date_received);
+ camel_file_util_decode_string(in, &subject);
+ camel_file_util_decode_string(in, &from);
+ camel_file_util_decode_string(in, &to);
+ camel_file_util_decode_string(in, &cc);
+ camel_file_util_decode_string(in, &mlist);
+
+#ifdef DOEPOOLV
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_UID, uid, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_SUBJECT, subject, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_FROM, from, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_TO, to, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_CC, cc, TRUE);
+ e_poolv_set(mi->strings, CAMEL_MESSAGE_INFO_MLIST, mlist, TRUE);
+#elif defined (DOESTRV)
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_UID, uid);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_SUBJECT, subject);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_FROM, from);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_TO, to);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_CC, cc);
+ e_strv_set_ref_free(mi->strings, CAMEL_MESSAGE_INFO_MLIST, mlist);
+#else
+ mi->uid = uid;
+ mi->subject = subject;
+ mi->from = from;
+ mi->to = to;
+ mi->cc = cc;
+ mi->mlist = mlist;
+#endif
+
+ mi->content = NULL;
+
+ camel_file_util_decode_fixed_int32(in, &mi->message_id.id.part.hi);
+ camel_file_util_decode_fixed_int32(in, &mi->message_id.id.part.lo);
+
+ if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
+ goto error;
+
+ if (count > 0) {
+ mi->references = g_malloc(sizeof(*mi->references) + ((count-1) * sizeof(mi->references->references[0])));
+ mi->references->size = count;
+ for (i=0;i<count;i++) {
+ camel_file_util_decode_fixed_int32(in, &mi->references->references[i].id.part.hi);
+ camel_file_util_decode_fixed_int32(in, &mi->references->references[i].id.part.lo);
+ }
+ }
+
+ if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
+ goto error;
+
+ for (i=0;i<count;i++) {
+ char *name;
+ if (camel_file_util_decode_string(in, &name) == -1 || name == NULL)
+ goto error;
+ camel_flag_set(&mi->user_flags, name, TRUE);
+ g_free(name);
+ }
+
+ if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
+ goto error;
+
+ for (i=0;i<count;i++) {
+ char *name, *value;
+ if (camel_file_util_decode_string(in, &name) == -1 || name == NULL
+ || camel_file_util_decode_string(in, &value) == -1)
+ goto error;
+ camel_tag_set(&mi->user_tags, name, value);
+ g_free(name);
+ g_free(value);
+ }
+
+ if (!ferror(in))
+ return mi;
+
+error:
+ camel_folder_summary_info_free(s, mi);
+
+ return NULL;
+}
+
+static int
+message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *mi)
+{
+ guint32 count;
+ CamelFlag *flag;
+ CamelTag *tag;
+ int i;
+
+ io(printf("Saving message info\n"));
+
+ camel_file_util_encode_string(out, camel_message_info_uid(mi));
+ camel_file_util_encode_uint32(out, mi->flags);
+ camel_file_util_encode_uint32(out, mi->size);
+ camel_file_util_encode_time_t(out, mi->date_sent);
+ camel_file_util_encode_time_t(out, mi->date_received);
+ camel_file_util_encode_string(out, camel_message_info_subject(mi));
+ camel_file_util_encode_string(out, camel_message_info_from(mi));
+ camel_file_util_encode_string(out, camel_message_info_to(mi));
+ camel_file_util_encode_string(out, camel_message_info_cc(mi));
+ camel_file_util_encode_string(out, camel_message_info_mlist(mi));
+
+ camel_file_util_encode_fixed_int32(out, mi->message_id.id.part.hi);
+ camel_file_util_encode_fixed_int32(out, mi->message_id.id.part.lo);
+
+ if (mi->references) {
+ camel_file_util_encode_uint32(out, mi->references->size);
+ for (i=0;i<mi->references->size;i++) {
+ camel_file_util_encode_fixed_int32(out, mi->references->references[i].id.part.hi);
+ camel_file_util_encode_fixed_int32(out, mi->references->references[i].id.part.lo);
+ }
+ } else {
+ camel_file_util_encode_uint32(out, 0);
+ }
+
+ count = camel_flag_list_size(&mi->user_flags);
+ camel_file_util_encode_uint32(out, count);
+ flag = mi->user_flags;
+ while (flag) {
+ camel_file_util_encode_string(out, flag->name);
+ flag = flag->next;
+ }
+
+ count = camel_tag_list_size(&mi->user_tags);
+ camel_file_util_encode_uint32(out, count);
+ tag = mi->user_tags;
+ while (tag) {
+ camel_file_util_encode_string(out, tag->name);
+ camel_file_util_encode_string(out, tag->value);
+ tag = tag->next;
+ }
+
+ return ferror(out);
+}
+
+static void
+message_info_free(CamelFolderSummary *s, CamelMessageInfo *mi)
+{
+#ifdef DOEPOOLV
+ e_poolv_destroy(mi->strings);
+#elif defined (DOESTRV)
+ e_strv_destroy(mi->strings);
+#else
+ g_free(mi->uid);
+ g_free(mi->subject);
+ g_free(mi->from);
+ g_free(mi->to);
+ g_free(mi->cc);
+ g_free(mi->mlist);
+#endif
+ g_free(mi->references);
+ camel_flag_list_free(&mi->user_flags);
+ camel_tag_list_free(&mi->user_tags);
+ e_memchunk_free(s->message_info_chunks, mi);
+}
+
+static CamelMessageContentInfo *
+content_info_new (CamelFolderSummary *s, struct _camel_header_raw *h)
+{
+ CamelMessageContentInfo *ci;
+ const char *charset;
+
+ ci = camel_folder_summary_content_info_new (s);
+
+ charset = e_iconv_locale_charset ();
+ ci->id = camel_header_msgid_decode (camel_header_raw_find (&h, "content-id", NULL));
+ ci->description = camel_header_decode_string (camel_header_raw_find (&h, "content-description", NULL), NULL);
+ ci->encoding = camel_content_transfer_encoding_decode (camel_header_raw_find (&h, "content-transfer-encoding", NULL));
+ ci->type = camel_content_type_decode(camel_header_raw_find(&h, "content-type", NULL));
+
+ return ci;
+}
+
+static CamelMessageContentInfo *
+content_info_load(CamelFolderSummary *s, FILE *in)
+{
+ CamelMessageContentInfo *ci;
+ char *type, *subtype;
+ guint32 count, i;
+ CamelContentType *ct;
+
+ io(printf("Loading content info\n"));
+
+ ci = camel_folder_summary_content_info_new(s);
+
+ camel_folder_summary_decode_token(in, &type);
+ camel_folder_summary_decode_token(in, &subtype);
+ ct = camel_content_type_new(type, subtype);
+ g_free(type); /* can this be removed? */
+ g_free(subtype);
+ if (camel_file_util_decode_uint32(in, &count) == -1 || count > 500)
+ goto error;
+
+ for (i=0;i<count;i++) {
+ char *name, *value;
+ camel_folder_summary_decode_token(in, &name);
+ camel_folder_summary_decode_token(in, &value);
+ if (!(name && value))
+ goto error;
+
+ camel_content_type_set_param(ct, name, value);
+ /* TODO: do this so we dont have to double alloc/free */
+ g_free(name);
+ g_free(value);
+ }
+ ci->type = ct;
+
+ camel_folder_summary_decode_token(in, &ci->id);
+ camel_folder_summary_decode_token(in, &ci->description);
+ camel_folder_summary_decode_token(in, &ci->encoding);
+
+ camel_file_util_decode_uint32(in, &ci->size);
+
+ ci->childs = NULL;
+
+ if (!ferror(in))
+ return ci;
+
+ error:
+ camel_folder_summary_content_info_free(s, ci);
+ return NULL;
+}
+
+static int
+content_info_save(CamelFolderSummary *s, FILE *out, CamelMessageContentInfo *ci)
+{
+ CamelContentType *ct;
+ struct _camel_header_param *hp;
+
+ io(printf("Saving content info\n"));
+
+ ct = ci->type;
+ if (ct) {
+ camel_folder_summary_encode_token(out, ct->type);
+ camel_folder_summary_encode_token(out, ct->subtype);
+ camel_file_util_encode_uint32(out, my_list_size((struct _node **)&ct->params));
+ hp = ct->params;
+ while (hp) {
+ camel_folder_summary_encode_token(out, hp->name);
+ camel_folder_summary_encode_token(out, hp->value);
+ hp = hp->next;
+ }
+ } else {
+ camel_folder_summary_encode_token(out, NULL);
+ camel_folder_summary_encode_token(out, NULL);
+ camel_file_util_encode_uint32(out, 0);
+ }
+ camel_folder_summary_encode_token(out, ci->id);
+ camel_folder_summary_encode_token(out, ci->description);
+ camel_folder_summary_encode_token(out, ci->encoding);
+ return camel_file_util_encode_uint32(out, ci->size);
+}
+
+static void
+content_info_free(CamelFolderSummary *s, CamelMessageContentInfo *ci)
+{
+ camel_content_type_unref(ci->type);
+ g_free(ci->id);
+ g_free(ci->description);
+ g_free(ci->encoding);
+ e_memchunk_free(s->content_info_chunks, ci);
+}
+
+static char *
+next_uid_string(CamelFolderSummary *s)
+{
+ return g_strdup_printf("%u", camel_folder_summary_next_uid(s));
+}
+
+/*
+ OK
+ Now this is where all the "smarts" happen, where the content info is built,
+ and any indexing and what not is performed
+*/
+
+/* must have filter_lock before calling this function */
+static CamelMessageContentInfo *
+summary_build_content_info(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimeParser *mp)
+{
+ int state;
+ size_t len;
+ char *buffer;
+ CamelMessageContentInfo *info = NULL;
+ CamelContentType *ct;
+ int body;
+ int enc_id = -1, chr_id = -1, html_id = -1, idx_id = -1;
+ struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
+ CamelMimeFilterCharset *mfc;
+ CamelMessageContentInfo *part;
+
+ d(printf("building content info\n"));
+
+ /* start of this part */
+ state = camel_mime_parser_step(mp, &buffer, &len);
+ body = camel_mime_parser_tell(mp);
+
+ if (s->build_content)
+ info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_parser(s, mp);
+
+ switch(state) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ /* check content type for indexing, then read body */
+ ct = camel_mime_parser_content_type(mp);
+ /* update attachments flag as we go */
+ if (camel_content_type_is(ct, "application", "pgp-signature")
+#ifdef ENABLE_SMIME
+ || camel_content_type_is(ct, "application", "x-pkcs7-signature")
+ || camel_content_type_is(ct, "application", "pkcs7-signature")
+#endif
+ )
+ msginfo->flags |= CAMEL_MESSAGE_SECURE;
+
+ if (p->index && camel_content_type_is(ct, "text", "*")) {
+ char *encoding;
+ const char *charset;
+
+ d(printf("generating index:\n"));
+
+ encoding = camel_content_transfer_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL));
+ if (encoding) {
+ if (!strcasecmp(encoding, "base64")) {
+ d(printf(" decoding base64\n"));
+ if (p->filter_64 == NULL)
+ p->filter_64 = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_DEC);
+ else
+ camel_mime_filter_reset((CamelMimeFilter *)p->filter_64);
+ enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_64);
+ } else if (!g_ascii_strcasecmp(encoding, "quoted-printable")) {
+ d(printf(" decoding quoted-printable\n"));
+ if (p->filter_qp == NULL)
+ p->filter_qp = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_DEC);
+ else
+ camel_mime_filter_reset((CamelMimeFilter *)p->filter_qp);
+ enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_qp);
+ } else if (!strcasecmp (encoding, "x-uuencode")) {
+ d(printf(" decoding x-uuencode\n"));
+ if (p->filter_uu == NULL)
+ p->filter_uu = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_UU_DEC);
+ else
+ camel_mime_filter_reset((CamelMimeFilter *)p->filter_uu);
+ enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_uu);
+ } else {
+ d(printf(" ignoring encoding %s\n", encoding));
+ }
+ g_free(encoding);
+ }
+
+ charset = camel_content_type_param(ct, "charset");
+ if (charset!=NULL
+ && !(g_ascii_strcasecmp(charset, "us-ascii")==0
+ || strcasecmp(charset, "utf-8")==0)) {
+ d(printf(" Adding conversion filter from %s to UTF-8\n", charset));
+ mfc = g_hash_table_lookup(p->filter_charset, charset);
+ if (mfc == NULL) {
+ mfc = camel_mime_filter_charset_new_convert(charset, "UTF-8");
+ if (mfc)
+ g_hash_table_insert(p->filter_charset, g_strdup(charset), mfc);
+ } else {
+ camel_mime_filter_reset((CamelMimeFilter *)mfc);
+ }
+ if (mfc) {
+ chr_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfc);
+ } else {
+ g_warning("Cannot convert '%s' to 'UTF-8', message index may be corrupt", charset);
+ }
+ }
+
+ /* we do charset conversions before this filter, which isn't strictly correct,
+ but works in most cases */
+ if (camel_content_type_is(ct, "text", "html")) {
+ if (p->filter_html == NULL)
+ p->filter_html = camel_mime_filter_html_new();
+ else
+ camel_mime_filter_reset((CamelMimeFilter *)p->filter_html);
+ html_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_html);
+ }
+
+ /* and this filter actually does the indexing */
+ idx_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)p->filter_index);
+ }
+ /* and scan/index everything */
+ while (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
+ ;
+ /* and remove the filters */
+ camel_mime_parser_filter_remove(mp, enc_id);
+ camel_mime_parser_filter_remove(mp, chr_id);
+ camel_mime_parser_filter_remove(mp, html_id);
+ camel_mime_parser_filter_remove(mp, idx_id);
+ break;
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ d(printf("Summarising multipart\n"));
+ /* update attachments flag as we go */
+ ct = camel_mime_parser_content_type(mp);
+ if (camel_content_type_is(ct, "multipart", "mixed"))
+ msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
+ if (camel_content_type_is(ct, "multipart", "signed")
+ || camel_content_type_is(ct, "multipart", "encrypted"))
+ msginfo->flags |= CAMEL_MESSAGE_SECURE;
+
+ while (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
+ camel_mime_parser_unstep(mp);
+ part = summary_build_content_info(s, msginfo, mp);
+ if (part) {
+ part->parent = info;
+ my_list_append((struct _node **)&info->childs, (struct _node *)part);
+ }
+ }
+ break;
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ d(printf("Summarising message\n"));
+ /* update attachments flag as we go */
+ msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
+
+ part = summary_build_content_info(s, msginfo, mp);
+ if (part) {
+ part->parent = info;
+ my_list_append((struct _node **)&info->childs, (struct _node *)part);
+ }
+ state = camel_mime_parser_step(mp, &buffer, &len);
+ if (state != CAMEL_MIME_PARSER_STATE_MESSAGE_END) {
+ g_error("Bad parser state: Expecing MESSAGE_END or MESSAGE_EOF, got: %d", state);
+ camel_mime_parser_unstep(mp);
+ }
+ break;
+ }
+
+ d(printf("finished building content info\n"));
+
+ return info;
+}
+
+/* build the content-info, from a message */
+/* this needs the filter lock since it uses filters to perform indexing */
+static CamelMessageContentInfo *
+summary_build_content_info_message(CamelFolderSummary *s, CamelMessageInfo *msginfo, CamelMimePart *object)
+{
+ CamelDataWrapper *containee;
+ int parts, i;
+ struct _CamelFolderSummaryPrivate *p = _PRIVATE(s);
+ CamelMessageContentInfo *info = NULL, *child;
+ CamelContentType *ct;
+
+ if (s->build_content)
+ info = ((CamelFolderSummaryClass *)(CAMEL_OBJECT_GET_CLASS(s)))->content_info_new_from_message(s, object);
+
+ containee = camel_medium_get_content_object(CAMEL_MEDIUM(object));
+
+ if (containee == NULL)
+ return info;
+
+ /* TODO: I find it odd that get_part and get_content_object do not
+ add a reference, probably need fixing for multithreading */
+
+ /* check for attachments */
+ ct = ((CamelDataWrapper *)containee)->mime_type;
+ if (camel_content_type_is(ct, "multipart", "*")) {
+ if (camel_content_type_is(ct, "multipart", "mixed"))
+ msginfo->flags |= CAMEL_MESSAGE_ATTACHMENTS;
+ if (camel_content_type_is(ct, "multipart", "signed")
+ || camel_content_type_is(ct, "multipart", "encrypted"))
+ msginfo->flags |= CAMEL_MESSAGE_SECURE;
+ } else if (camel_content_type_is(ct, "application", "pgp-signature")
+#ifdef ENABLE_SMIME
+ || camel_content_type_is(ct, "application", "x-pkcs7-signature")
+ || camel_content_type_is(ct, "application", "pkcs7-signature")
+#endif
+ ) {
+ msginfo->flags |= CAMEL_MESSAGE_SECURE;
+ }
+
+ /* using the object types is more accurate than using the mime/types */
+ if (CAMEL_IS_MULTIPART(containee)) {
+ parts = camel_multipart_get_number(CAMEL_MULTIPART(containee));
+ for (i=0;i<parts;i++) {
+ CamelMimePart *part = camel_multipart_get_part(CAMEL_MULTIPART(containee), i);
+ g_assert(part);
+ child = summary_build_content_info_message(s, msginfo, part);
+ if (child) {
+ child->parent = info;
+ my_list_append((struct _node **)&info->childs, (struct _node *)child);
+ }
+ }
+ } else if (CAMEL_IS_MIME_MESSAGE(containee)) {
+ /* for messages we only look at its contents */
+ child = summary_build_content_info_message(s, msginfo, (CamelMimePart *)containee);
+ if (child) {
+ child->parent = info;
+ my_list_append((struct _node **)&info->childs, (struct _node *)child);
+ }
+ } else if (p->filter_stream
+ && camel_content_type_is(ct, "text", "*")) {
+ int html_id = -1, idx_id = -1;
+
+ /* pre-attach html filter if required, otherwise just index filter */
+ if (camel_content_type_is(ct, "text", "html")) {
+ if (p->filter_html == NULL)
+ p->filter_html = camel_mime_filter_html_new();
+ else
+ camel_mime_filter_reset((CamelMimeFilter *)p->filter_html);
+ html_id = camel_stream_filter_add(p->filter_stream, (CamelMimeFilter *)p->filter_html);
+ }
+ idx_id = camel_stream_filter_add(p->filter_stream, (CamelMimeFilter *)p->filter_index);
+
+ camel_data_wrapper_decode_to_stream(containee, (CamelStream *)p->filter_stream);
+ camel_stream_flush((CamelStream *)p->filter_stream);
+
+ camel_stream_filter_remove(p->filter_stream, idx_id);
+ camel_stream_filter_remove(p->filter_stream, html_id);
+ }
+
+ return info;
+}
+
+/**
+ * camel_flag_get:
+ * @list:
+ * @name:
+ *
+ * Find the state of the flag @name in @list.
+ *
+ * Return value: The state of the flag (TRUE or FALSE).
+ **/
+gboolean
+camel_flag_get(CamelFlag **list, const char *name)
+{
+ CamelFlag *flag;
+ flag = *list;
+ while (flag) {
+ if (!strcmp(flag->name, name))
+ return TRUE;
+ flag = flag->next;
+ }
+ return FALSE;
+}
+
+/**
+ * camel_flag_set:
+ * @list:
+ * @name:
+ * @value:
+ *
+ * Set the state of a flag @name in the list @list to @value.
+ *
+ * Return value: Whether or not it changed.
+ **/
+gboolean
+camel_flag_set(CamelFlag **list, const char *name, gboolean value)
+{
+ CamelFlag *flag, *tmp;
+
+ /* this 'trick' works because flag->next is the first element */
+ flag = (CamelFlag *)list;
+ while (flag->next) {
+ tmp = flag->next;
+ if (!strcmp(flag->next->name, name)) {
+ if (!value) {
+ flag->next = tmp->next;
+ g_free(tmp);
+ }
+ return !value;
+ }
+ flag = tmp;
+ }
+
+ if (value) {
+ tmp = g_malloc(sizeof(*tmp) + strlen(name));
+ strcpy(tmp->name, name);
+ tmp->next = 0;
+ flag->next = tmp;
+ }
+ return value;
+}
+
+/**
+ * camel_flag_list_size:
+ * @list:
+ *
+ * Get the length of the flag list.
+ *
+ * Return value: The number of TRUE flags in the list.
+ **/
+int
+camel_flag_list_size(CamelFlag **list)
+{
+ int count=0;
+ CamelFlag *flag;
+
+ flag = *list;
+ while (flag) {
+ count++;
+ flag = flag->next;
+ }
+ return count;
+}
+
+/**
+ * camel_flag_list_free:
+ * @list:
+ *
+ * Free the memory associated with the flag list @list.
+ **/
+void
+camel_flag_list_free(CamelFlag **list)
+{
+ CamelFlag *flag, *tmp;
+ flag = *list;
+ while (flag) {
+ tmp = flag->next;
+ g_free(flag);
+ flag = tmp;
+ }
+ *list = NULL;
+}
+
+/**
+ * camel_flag_list_copy:
+ * @to:
+ * @from:
+ *
+ * Copy a flag list, return true if the destination list @to changed.
+ *
+ * Return value:
+ **/
+gboolean
+camel_flag_list_copy(CamelFlag **to, CamelFlag **from)
+{
+ CamelFlag *flag, *tmp;
+ int changed = FALSE;
+
+ if (*to == NULL && from == NULL)
+ return FALSE;
+
+ /* Remove any now-missing flags */
+ flag = (CamelFlag *)to;
+ while (flag->next) {
+ tmp = flag->next;
+ if (!camel_flag_get(from, tmp->name)) {
+ flag->next = tmp->next;
+ g_free(tmp);
+ changed = TRUE;
+ } else {
+ flag = tmp;
+ }
+ }
+
+ /* Add any new flags */
+ flag = *from;
+ while (flag) {
+ changed |= camel_flag_set(to, flag->name, TRUE);
+ flag = flag->next;
+ }
+
+ return changed;
+}
+
+const char *
+camel_tag_get(CamelTag **list, const char *name)
+{
+ CamelTag *tag;
+
+ tag = *list;
+ while (tag) {
+ if (!strcmp(tag->name, name))
+ return (const char *)tag->value;
+ tag = tag->next;
+ }
+ return NULL;
+}
+
+/**
+ * camel_tag_set:
+ * @list:
+ * @name:
+ * @value:
+ *
+ * Set the tag @name in the tag list @list to @value.
+ *
+ * Return value: whether or not it changed
+ **/
+gboolean
+camel_tag_set(CamelTag **list, const char *name, const char *value)
+{
+ CamelTag *tag, *tmp;
+
+ /* this 'trick' works because tag->next is the first element */
+ tag = (CamelTag *)list;
+ while (tag->next) {
+ tmp = tag->next;
+ if (!strcmp(tmp->name, name)) {
+ if (value == NULL) { /* clear it? */
+ tag->next = tmp->next;
+ g_free(tmp->value);
+ g_free(tmp);
+ return TRUE;
+ } else if (strcmp(tmp->value, value)) { /* has it changed? */
+ g_free(tmp->value);
+ tmp->value = g_strdup(value);
+ return TRUE;
+ }
+ return FALSE;
+ }
+ tag = tmp;
+ }
+
+ if (value) {
+ tmp = g_malloc(sizeof(*tmp)+strlen(name));
+ strcpy(tmp->name, name);
+ tmp->value = g_strdup(value);
+ tmp->next = 0;
+ tag->next = tmp;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * camel_tag_list_size:
+ * @list:
+ *
+ * Get the number of tags present in the tag list @list.
+ *
+ * Return value: The number of tags.
+ **/
+int camel_tag_list_size(CamelTag **list)
+{
+ int count=0;
+ CamelTag *tag;
+
+ tag = *list;
+ while (tag) {
+ count++;
+ tag = tag->next;
+ }
+ return count;
+}
+
+static void
+rem_tag(char *key, char *value, CamelTag **to)
+{
+ camel_tag_set(to, key, NULL);
+}
+
+/**
+ * camel_tag_list_copy:
+ * @to:
+ * @from:
+ *
+ * Copy a list of tags.
+ *
+ * Return value:
+ **/
+gboolean
+camel_tag_list_copy(CamelTag **to, CamelTag **from)
+{
+ int changed = FALSE;
+ CamelTag *tag;
+ GHashTable *left;
+
+ if (*to == NULL && from == NULL)
+ return FALSE;
+
+ left = g_hash_table_new(g_str_hash, g_str_equal);
+ tag = *to;
+ while (tag) {
+ g_hash_table_insert(left, tag->name, tag);
+ tag = tag->next;
+ }
+
+ tag = *from;
+ while (tag) {
+ changed |= camel_tag_set(to, tag->name, tag->value);
+ g_hash_table_remove(left, tag->name);
+ tag = tag->next;
+ }
+
+ if (g_hash_table_size(left)>0) {
+ g_hash_table_foreach(left, (GHFunc)rem_tag, to);
+ changed = TRUE;
+ }
+ g_hash_table_destroy(left);
+
+ return changed;
+}
+
+/**
+ * camel_tag_list_free:
+ * @list:
+ *
+ * Free the tag list @list.
+ **/
+void camel_tag_list_free(CamelTag **list)
+{
+ CamelTag *tag, *tmp;
+ tag = *list;
+ while (tag) {
+ tmp = tag->next;
+ g_free(tag->value);
+ g_free(tag);
+ tag = tmp;
+ }
+ *list = NULL;
+}
+
+struct flag_names_t {
+ char *name;
+ guint32 value;
+} flag_names[] = {
+ { "answered", CAMEL_MESSAGE_ANSWERED },
+ { "deleted", CAMEL_MESSAGE_DELETED },
+ { "draft", CAMEL_MESSAGE_DELETED },
+ { "flagged", CAMEL_MESSAGE_FLAGGED },
+ { "seen", CAMEL_MESSAGE_SEEN },
+ { "attachments", CAMEL_MESSAGE_ATTACHMENTS },
+ { "junk", CAMEL_MESSAGE_JUNK },
+ { "secure", CAMEL_MESSAGE_SECURE },
+ { NULL, 0 }
+};
+
+/**
+ * camel_system_flag:
+ * @name:
+ *
+ * Returns the integer value of the flag string.
+ **/
+guint32
+camel_system_flag (const char *name)
+{
+ struct flag_names_t *flag;
+
+ g_return_val_if_fail (name != NULL, 0);
+
+ for (flag = flag_names; *flag->name; flag++)
+ if (!g_ascii_strcasecmp (name, flag->name))
+ return flag->value;
+
+ return 0;
+}
+
+/**
+ * camel_system_flag_get:
+ * @flags:
+ * @name:
+ *
+ * Find the state of the flag @name in @flags.
+ *
+ * Return value: The state of the flag (TRUE or FALSE).
+ **/
+gboolean
+camel_system_flag_get (guint32 flags, const char *name)
+{
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ return flags & camel_system_flag (name);
+}
+
+
+/**
+ * camel_message_info_new:
+ *
+ * Returns a new CamelMessageInfo structure.
+ **/
+CamelMessageInfo *
+camel_message_info_new (void)
+{
+ CamelMessageInfo *info;
+
+ info = g_malloc0(sizeof(*info));
+#ifdef DOEPOOLV
+ info->strings = e_poolv_new(CAMEL_MESSAGE_INFO_LAST);
+#endif
+#ifdef DOESTRV
+ info->strings = e_strv_new (CAMEL_MESSAGE_INFO_LAST);
+#endif
+ info->refcount = 1;
+
+ return info;
+}
+
+/**
+ * camel_message_info_ref:
+ * @info:
+ *
+ * Reference an info.
+ *
+ * NOTE: This interface is not MT-SAFE, like the others.
+ **/
+void camel_message_info_ref(CamelMessageInfo *info)
+{
+ GLOBAL_INFO_LOCK(info);
+ info->refcount++;
+ GLOBAL_INFO_UNLOCK(info);
+}
+
+/**
+ * camel_message_info_new_from_header:
+ * @header: raw header
+ *
+ * Returns a new CamelMessageInfo structure populated by the header.
+ **/
+CamelMessageInfo *
+camel_message_info_new_from_header (struct _camel_header_raw *header)
+{
+ CamelMessageInfo *info;
+ char *subject, *from, *to, *cc, *mlist;
+ CamelContentType *ct = NULL;
+ const char *content, *date, *charset = NULL;
+
+ if ((content = camel_header_raw_find(&header, "Content-Type", NULL))
+ && (ct = camel_content_type_decode(content))
+ && (charset = camel_content_type_param(ct, "charset"))
+ && (g_ascii_strcasecmp(charset, "us-ascii") == 0))
+ charset = NULL;
+
+ charset = charset ? e_iconv_charset_name (charset) : NULL;
+
+ subject = summary_format_string(header, "subject", charset);
+ from = summary_format_address(header, "from", charset);
+ to = summary_format_address(header, "to", charset);
+ cc = summary_format_address(header, "cc", charset);
+ date = camel_header_raw_find(&header, "date", NULL);
+ mlist = camel_header_raw_check_mailing_list(&header);
+
+ if (ct)
+ camel_content_type_unref(ct);
+
+ info = camel_message_info_new();
+
+ camel_message_info_set_subject(info, subject);
+ camel_message_info_set_from(info, from);
+ camel_message_info_set_to(info, to);
+ camel_message_info_set_cc(info, cc);
+ camel_message_info_set_mlist(info, mlist);
+
+ if (date)
+ info->date_sent = camel_header_decode_date (date, NULL);
+ else
+ info->date_sent = time (NULL);
+
+ date = camel_header_raw_find (&header, "received", NULL);
+ if (date && (date = strrchr (date, ';')))
+ date++;
+
+ if (date)
+ info->date_received = camel_header_decode_date (date, NULL);
+ else
+ info->date_received = time (NULL);
+
+ return info;
+}
+
+/**
+ * camel_message_info_dup_to:
+ * @from: source message info
+ * @to: destination message info
+ *
+ * Duplicates the contents of one CamelMessageInfo structure into another.
+ * (The destination is assumed to be empty: its contents are not freed.)
+ * The slightly odd interface is to allow this to be used to initialize
+ * "subclasses" of CamelMessageInfo.
+ **/
+void
+camel_message_info_dup_to(const CamelMessageInfo *from, CamelMessageInfo *to)
+{
+ CamelFlag *flag;
+ CamelTag *tag;
+
+ /* Copy numbers */
+ to->flags = from->flags;
+ to->size = from->size;
+ to->date_sent = from->date_sent;
+ to->date_received = from->date_received;
+ to->refcount = 1;
+
+ /* Copy strings */
+#ifdef DOEPOOLV
+ to->strings = e_poolv_cpy (to->strings, from->strings);
+#elif defined (DOESTRV)
+ /* to->strings = e_strv_new(CAMEL_MESSAGE_INFO_LAST); */
+ e_strv_set(to->strings, CAMEL_MESSAGE_INFO_SUBJECT, camel_message_info_subject(from));
+ e_strv_set(to->strings, CAMEL_MESSAGE_INFO_FROM, camel_message_info_from(from));
+ e_strv_set(to->strings, CAMEL_MESSAGE_INFO_TO, camel_message_info_to(from));
+ e_strv_set(to->strings, CAMEL_MESSAGE_INFO_CC, camel_message_info_cc(from));
+ e_strv_set(to->strings, CAMEL_MESSAGE_INFO_UID, camel_message_info_uid(from));
+ e_strv_set(to->strings, CAMEL_MESSAGE_INFO_UID, camel_message_info_mlist(from));
+#else
+ to->subject = g_strdup(from->subject);
+ to->from = g_strdup(from->from);
+ to->to = g_strdup(from->to);
+ to->cc = g_strdup(from->cc);
+ to->uid = g_strdup(from->uid);
+ to->mlist = g_strdup(from->mlist);
+#endif
+ memcpy(&to->message_id, &from->message_id, sizeof(from->message_id));
+
+ /* Copy structures */
+ if (from->references) {
+ int len = sizeof(*from->references) + ((from->references->size-1) * sizeof(from->references->references[0]));
+
+ to->references = g_malloc(len);
+ memcpy(to->references, from->references, len);
+ } else {
+ to->references = NULL;
+ }
+
+ flag = from->user_flags;
+ while (flag) {
+ camel_flag_set(&to->user_flags, flag->name, TRUE);
+ flag = flag->next;
+ }
+
+ tag = from->user_tags;
+ while (tag) {
+ camel_tag_set(&to->user_tags, tag->name, tag->value);
+ tag = tag->next;
+ }
+
+ /* No, this is impossible without knowing the class of summary we came from */
+ /* FIXME some day */
+ to->content = NULL;
+}
+
+/**
+ * camel_message_info_free:
+ * @mi: the message info
+ *
+ * Unref's and potentially frees a CamelMessageInfo and its contents.
+ *
+ * Can only be used to free CamelMessageInfo's created with
+ * camel_message_info_dup_to.
+ *
+ * NOTE: This interface is not MT-SAFE, like the others.
+ *
+ **/
+void
+camel_message_info_free(CamelMessageInfo *mi)
+{
+ g_return_if_fail(mi != NULL);
+
+ GLOBAL_INFO_LOCK(info);
+ mi->refcount--;
+ if (mi->refcount > 0) {
+ GLOBAL_INFO_UNLOCK(info);
+ return;
+ }
+ GLOBAL_INFO_UNLOCK(info);
+
+#ifdef DOEPOOLV
+ e_poolv_destroy(mi->strings);
+#elif defined (DOESTRV)
+ e_strv_destroy(mi->strings);
+#else
+ g_free(mi->uid);
+ g_free(mi->subject);
+ g_free(mi->from);
+ g_free(mi->to);
+ g_free(mi->cc);
+ g_free(mi->mlist);
+#endif
+ g_free(mi->references);
+ camel_flag_list_free(&mi->user_flags);
+ camel_tag_list_free(&mi->user_tags);
+ /* FIXME: content info? */
+ g_free(mi);
+}
+
+#if defined (DOEPOOLV) || defined (DOESTRV)
+const char *
+camel_message_info_string (const CamelMessageInfo *mi, int type)
+{
+ g_assert (mi != NULL);
+
+ if (mi->strings == NULL)
+ return "";
+#ifdef DOEPOOLV
+ return e_poolv_get (mi->strings, type);
+#else
+ return e_strv_get (mi->strings, type);
+#endif
+}
+
+void
+camel_message_info_set_string (CamelMessageInfo *mi, int type, char *str)
+{
+ g_assert (mi != NULL);
+ g_assert (mi->strings != NULL);
+#ifdef DOEPOOLV
+ e_poolv_set (mi->strings, type, str, TRUE);
+#else
+ mi->strings = e_strv_set_ref_free (mi->strings, type, str);
+#endif
+}
+#endif
+
+
+void
+camel_content_info_dump (CamelMessageContentInfo *ci, int depth)
+{
+ char *p;
+
+ p = alloca (depth * 4 + 1);
+ memset (p, ' ', depth * 4);
+ p[depth * 4] = 0;
+
+ if (ci == NULL) {
+ printf ("%s<empty>\n", p);
+ return;
+ }
+
+ if (ci->type)
+ printf ("%scontent-type: %s/%s\n", p, ci->type->type ? ci->type->type : "(null)",
+ ci->type->subtype ? ci->type->subtype : "(null)");
+ else
+ printf ("%scontent-type: <unset>\n", p);
+ printf ("%scontent-transfer-encoding: %s\n", p, ci->encoding ? ci->encoding : "(null)");
+ printf ("%scontent-description: %s\n", p, ci->description ? ci->description : "(null)");
+ printf ("%ssize: %lu\n", p, (unsigned long) ci->size);
+ ci = ci->childs;
+ while (ci) {
+ camel_content_info_dump (ci, depth + 1);
+ ci = ci->next;
+ }
+}
+
+void
+camel_message_info_dump (CamelMessageInfo *mi)
+{
+ if (mi == NULL) {
+ printf("No message?\n");
+ return;
+ }
+
+ printf("Subject: %s\n", camel_message_info_subject(mi));
+ printf("To: %s\n", camel_message_info_to(mi));
+ printf("Cc: %s\n", camel_message_info_cc(mi));
+ printf("mailing list: %s\n", camel_message_info_mlist(mi));
+ printf("From: %s\n", camel_message_info_from(mi));
+ printf("UID: %s\n", camel_message_info_uid(mi));
+ printf("Flags: %04x\n", mi->flags & 0xffff);
+ camel_content_info_dump(mi->content, 0);
+}
diff --git a/camel/camel-gpg-context.c b/camel/camel-gpg-context.c
new file mode 100644
index 0000000000..acd00494ae
--- /dev/null
+++ b/camel/camel-gpg-context.c
@@ -0,0 +1,1817 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/* Debug states:
+ gpg:sign dump canonicalised to-be-signed data to a file
+ gpg:verify dump canonicalised verification and signature data to file
+ gpg:status print gpg status-fd output to stdout
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <termios.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "gal/util/e-iconv.h"
+
+#include "camel-gpg-context.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-fs.h"
+#include "camel-operation.h"
+#include "camel-mime-part.h"
+#include "camel-mime-filter-canon.h"
+
+#include "camel-multipart-signed.h"
+#include "camel-multipart-encrypted.h"
+
+#define d(x)
+
+#define GPG_LOG
+
+#ifdef GPG_LOG
+#include "camel-debug.h"
+static int logid;
+#endif
+
+static CamelCipherContextClass *parent_class = NULL;
+
+/**
+ * camel_gpg_context_new:
+ * @session: session
+ *
+ * Creates a new gpg cipher context object.
+ *
+ * Returns a new gpg cipher context object.
+ **/
+CamelCipherContext *
+camel_gpg_context_new (CamelSession *session)
+{
+ CamelCipherContext *cipher;
+ CamelGpgContext *ctx;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ ctx = (CamelGpgContext *) camel_object_new (camel_gpg_context_get_type ());
+
+ cipher = (CamelCipherContext *) ctx;
+ cipher->session = session;
+ camel_object_ref (session);
+
+ return cipher;
+}
+
+
+/**
+ * camel_gpg_context_set_always_trust:
+ * @ctx: gpg context
+ * @always_trust always truct flag
+ *
+ * Sets the @always_trust flag on the gpg context which is used for
+ * encryption.
+ **/
+void
+camel_gpg_context_set_always_trust (CamelGpgContext *ctx, gboolean always_trust)
+{
+ g_return_if_fail (CAMEL_IS_GPG_CONTEXT (ctx));
+
+ ctx->always_trust = always_trust;
+}
+
+
+static const char *
+gpg_hash_to_id (CamelCipherContext *context, CamelCipherHash hash)
+{
+ switch (hash) {
+ case CAMEL_CIPHER_HASH_MD2:
+ return "pgp-md2";
+ case CAMEL_CIPHER_HASH_MD5:
+ return "pgp-md5";
+ case CAMEL_CIPHER_HASH_SHA1:
+ case CAMEL_CIPHER_HASH_DEFAULT:
+ return "pgp-sha1";
+ case CAMEL_CIPHER_HASH_RIPEMD160:
+ return "pgp-ripemd160";
+ case CAMEL_CIPHER_HASH_TIGER192:
+ return "pgp-tiger192";
+ case CAMEL_CIPHER_HASH_HAVAL5160:
+ return "pgp-haval-5-160";
+ }
+
+ return NULL;
+}
+
+static CamelCipherHash
+gpg_id_to_hash (CamelCipherContext *context, const char *id)
+{
+ if (id) {
+ if (!strcmp (id, "pgp-md2"))
+ return CAMEL_CIPHER_HASH_MD2;
+ else if (!strcmp (id, "pgp-md5"))
+ return CAMEL_CIPHER_HASH_MD5;
+ else if (!strcmp (id, "pgp-sha1"))
+ return CAMEL_CIPHER_HASH_SHA1;
+ else if (!strcmp (id, "pgp-ripemd160"))
+ return CAMEL_CIPHER_HASH_RIPEMD160;
+ else if (!strcmp (id, "tiger192"))
+ return CAMEL_CIPHER_HASH_TIGER192;
+ else if (!strcmp (id, "haval-5-160"))
+ return CAMEL_CIPHER_HASH_HAVAL5160;
+ }
+
+ return CAMEL_CIPHER_HASH_DEFAULT;
+}
+
+
+enum _GpgCtxMode {
+ GPG_CTX_MODE_SIGN,
+ GPG_CTX_MODE_VERIFY,
+ GPG_CTX_MODE_ENCRYPT,
+ GPG_CTX_MODE_DECRYPT,
+ GPG_CTX_MODE_IMPORT,
+ GPG_CTX_MODE_EXPORT,
+};
+
+enum _GpgTrustMetric {
+ GPG_TRUST_NONE,
+ GPG_TRUST_NEVER,
+ GPG_TRUST_UNDEFINED,
+ GPG_TRUST_MARGINAL,
+ GPG_TRUST_FULLY,
+ GPG_TRUST_ULTIMATE
+};
+
+struct _GpgCtx {
+ enum _GpgCtxMode mode;
+ CamelSession *session;
+ GHashTable *userid_hint;
+ pid_t pid;
+
+ char *userid;
+ char *sigfile;
+ GPtrArray *recipients;
+ CamelCipherHash hash;
+
+ int stdin_fd;
+ int stdout_fd;
+ int stderr_fd;
+ int status_fd;
+ int passwd_fd; /* only needed for sign/decrypt */
+
+ /* status-fd buffer */
+ unsigned char *statusbuf;
+ unsigned char *statusptr;
+ unsigned int statusleft;
+
+ char *need_id;
+ char *passwd;
+
+ CamelStream *istream;
+ CamelStream *ostream;
+
+ GByteArray *diagbuf;
+ CamelStream *diagnostics;
+
+ int exit_status;
+
+ unsigned int exited:1;
+ unsigned int complete:1;
+ unsigned int seen_eof1:1;
+ unsigned int seen_eof2:1;
+ unsigned int always_trust:1;
+ unsigned int armor:1;
+ unsigned int need_passwd:1;
+ unsigned int send_passwd:1;
+
+ unsigned int bad_passwds:2;
+
+ unsigned int hadsig:1;
+ unsigned int badsig:1;
+ unsigned int errsig:1;
+ unsigned int goodsig:1;
+ unsigned int validsig:1;
+ unsigned int nopubkey:1;
+ unsigned int nodata:1;
+ unsigned int trust:3;
+
+ unsigned int diagflushed:1;
+
+ unsigned int utf8:1;
+
+ unsigned int padding:10;
+};
+
+static struct _GpgCtx *
+gpg_ctx_new (CamelSession *session)
+{
+ struct _GpgCtx *gpg;
+ const char *charset;
+ CamelStream *stream;
+
+ gpg = g_new (struct _GpgCtx, 1);
+ gpg->mode = GPG_CTX_MODE_SIGN;
+ gpg->session = session;
+ camel_object_ref (session);
+ gpg->userid_hint = g_hash_table_new (g_str_hash, g_str_equal);
+ gpg->complete = FALSE;
+ gpg->seen_eof1 = TRUE;
+ gpg->seen_eof2 = FALSE;
+ gpg->pid = (pid_t) -1;
+ gpg->exit_status = 0;
+ gpg->exited = FALSE;
+
+ gpg->userid = NULL;
+ gpg->sigfile = NULL;
+ gpg->recipients = NULL;
+ gpg->hash = CAMEL_CIPHER_HASH_DEFAULT;
+ gpg->always_trust = FALSE;
+ gpg->armor = FALSE;
+
+ gpg->stdin_fd = -1;
+ gpg->stdout_fd = -1;
+ gpg->stderr_fd = -1;
+ gpg->status_fd = -1;
+ gpg->passwd_fd = -1;
+
+ gpg->statusbuf = g_malloc (128);
+ gpg->statusptr = gpg->statusbuf;
+ gpg->statusleft = 128;
+
+ gpg->bad_passwds = 0;
+ gpg->need_passwd = FALSE;
+ gpg->send_passwd = FALSE;
+ gpg->need_id = NULL;
+ gpg->passwd = NULL;
+
+ gpg->nodata = FALSE;
+ gpg->hadsig = FALSE;
+ gpg->badsig = FALSE;
+ gpg->errsig = FALSE;
+ gpg->goodsig = FALSE;
+ gpg->validsig = FALSE;
+ gpg->nopubkey = FALSE;
+ gpg->trust = GPG_TRUST_NONE;
+
+ gpg->istream = NULL;
+ gpg->ostream = NULL;
+
+ stream = camel_stream_mem_new ();
+ gpg->diagbuf = CAMEL_STREAM_MEM (stream)->buffer;
+ gpg->diagflushed = FALSE;
+
+ if ((charset = e_iconv_locale_charset ()) && strcasecmp (charset, "UTF-8") != 0) {
+ CamelMimeFilterCharset *filter;
+ CamelStreamFilter *fstream;
+
+ gpg->utf8 = FALSE;
+
+ if ((filter = camel_mime_filter_charset_new_convert (charset, "UTF-8"))) {
+ fstream = camel_stream_filter_new_with_stream (stream);
+ camel_stream_filter_add (fstream, (CamelMimeFilter *) filter);
+ camel_object_unref (filter);
+ camel_object_unref (stream);
+
+ stream = (CamelStream *) fstream;
+ }
+ } else {
+ gpg->utf8 = TRUE;
+ }
+
+ gpg->diagnostics = stream;
+
+ return gpg;
+}
+
+static void
+gpg_ctx_set_mode (struct _GpgCtx *gpg, enum _GpgCtxMode mode)
+{
+ gpg->mode = mode;
+ gpg->need_passwd = ((gpg->mode == GPG_CTX_MODE_SIGN) || (gpg->mode == GPG_CTX_MODE_DECRYPT));
+}
+
+static void
+gpg_ctx_set_hash (struct _GpgCtx *gpg, CamelCipherHash hash)
+{
+ gpg->hash = hash;
+}
+
+static void
+gpg_ctx_set_always_trust (struct _GpgCtx *gpg, gboolean trust)
+{
+ gpg->always_trust = trust;
+}
+
+static void
+gpg_ctx_set_userid (struct _GpgCtx *gpg, const char *userid)
+{
+ g_free (gpg->userid);
+ gpg->userid = g_strdup (userid);
+}
+
+static void
+gpg_ctx_add_recipient (struct _GpgCtx *gpg, const char *keyid)
+{
+ if (gpg->mode != GPG_CTX_MODE_ENCRYPT && gpg->mode != GPG_CTX_MODE_EXPORT)
+ return;
+
+ if (!gpg->recipients)
+ gpg->recipients = g_ptr_array_new ();
+
+ g_ptr_array_add (gpg->recipients, g_strdup (keyid));
+}
+
+static void
+gpg_ctx_set_sigfile (struct _GpgCtx *gpg, const char *sigfile)
+{
+ g_free (gpg->sigfile);
+ gpg->sigfile = g_strdup (sigfile);
+}
+
+static void
+gpg_ctx_set_armor (struct _GpgCtx *gpg, gboolean armor)
+{
+ gpg->armor = armor;
+}
+
+static void
+gpg_ctx_set_istream (struct _GpgCtx *gpg, CamelStream *istream)
+{
+ camel_object_ref (istream);
+ if (gpg->istream)
+ camel_object_unref (gpg->istream);
+ gpg->istream = istream;
+}
+
+static void
+gpg_ctx_set_ostream (struct _GpgCtx *gpg, CamelStream *ostream)
+{
+ camel_object_ref (ostream);
+ if (gpg->ostream)
+ camel_object_unref (gpg->ostream);
+ gpg->ostream = ostream;
+ gpg->seen_eof1 = FALSE;
+}
+
+static const char *
+gpg_ctx_get_diagnostics (struct _GpgCtx *gpg)
+{
+ if (!gpg->diagflushed) {
+ gpg->diagflushed = TRUE;
+ camel_stream_flush (gpg->diagnostics);
+ if (gpg->diagbuf->len == 0)
+ return NULL;
+
+ g_byte_array_append (gpg->diagbuf, "", 1);
+ }
+
+ return gpg->diagbuf->data;
+}
+
+static void
+userid_hint_free (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+ g_free (value);
+}
+
+static void
+gpg_ctx_free (struct _GpgCtx *gpg)
+{
+ int i;
+
+ if (gpg == NULL)
+ return;
+
+ if (gpg->session)
+ camel_object_unref (gpg->session);
+
+ g_hash_table_foreach (gpg->userid_hint, userid_hint_free, NULL);
+ g_hash_table_destroy (gpg->userid_hint);
+
+ g_free (gpg->userid);
+
+ g_free (gpg->sigfile);
+
+ if (gpg->recipients) {
+ for (i = 0; i < gpg->recipients->len; i++)
+ g_free (gpg->recipients->pdata[i]);
+
+ g_ptr_array_free (gpg->recipients, TRUE);
+ }
+
+ if (gpg->stdin_fd != -1)
+ close (gpg->stdin_fd);
+ if (gpg->stdout_fd != -1)
+ close (gpg->stdout_fd);
+ if (gpg->stderr_fd != -1)
+ close (gpg->stderr_fd);
+ if (gpg->status_fd != -1)
+ close (gpg->status_fd);
+ if (gpg->passwd_fd != -1)
+ close (gpg->passwd_fd);
+
+ g_free (gpg->statusbuf);
+
+ g_free (gpg->need_id);
+
+ if (gpg->passwd) {
+ memset (gpg->passwd, 0, strlen (gpg->passwd));
+ g_free (gpg->passwd);
+ }
+
+ if (gpg->istream)
+ camel_object_unref (gpg->istream);
+
+ if (gpg->ostream)
+ camel_object_unref (gpg->ostream);
+
+ camel_object_unref (gpg->diagnostics);
+
+ g_free (gpg);
+}
+
+static const char *
+gpg_hash_str (CamelCipherHash hash)
+{
+ switch (hash) {
+ case CAMEL_CIPHER_HASH_MD2:
+ return "--digest-algo=MD2";
+ case CAMEL_CIPHER_HASH_MD5:
+ return "--digest-algo=MD5";
+ case CAMEL_CIPHER_HASH_SHA1:
+ return "--digest-algo=SHA1";
+ case CAMEL_CIPHER_HASH_RIPEMD160:
+ return "--digest-algo=RIPEMD160";
+ default:
+ return NULL;
+ }
+}
+
+static GPtrArray *
+gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, char **sfd, int passwd_fd, char **pfd)
+{
+ const char *hash_str;
+ GPtrArray *argv;
+ char *buf;
+ int i;
+
+ argv = g_ptr_array_new ();
+ g_ptr_array_add (argv, "gpg");
+
+ g_ptr_array_add (argv, "--verbose");
+ g_ptr_array_add (argv, "--no-secmem-warning");
+ g_ptr_array_add (argv, "--no-greeting");
+ g_ptr_array_add (argv, "--no-tty");
+ if (passwd_fd == -1) {
+ /* only use batch mode if we don't intend on using the
+ interactive --command-fd option */
+ g_ptr_array_add (argv, "--batch");
+ g_ptr_array_add (argv, "--yes");
+ }
+
+ *sfd = buf = g_strdup_printf ("--status-fd=%d", status_fd);
+ g_ptr_array_add (argv, buf);
+
+ if (passwd_fd != -1) {
+ *pfd = buf = g_strdup_printf ("--command-fd=%d", passwd_fd);
+ g_ptr_array_add (argv, buf);
+ }
+
+ switch (gpg->mode) {
+ case GPG_CTX_MODE_SIGN:
+ g_ptr_array_add (argv, "--sign");
+ g_ptr_array_add (argv, "--detach");
+ if (gpg->armor)
+ g_ptr_array_add (argv, "--armor");
+ hash_str = gpg_hash_str (gpg->hash);
+ if (hash_str)
+ g_ptr_array_add (argv, (char *) hash_str);
+ if (gpg->userid) {
+ g_ptr_array_add (argv, "-u");
+ g_ptr_array_add (argv, (char *) gpg->userid);
+ }
+ g_ptr_array_add (argv, "--output");
+ g_ptr_array_add (argv, "-");
+ break;
+ case GPG_CTX_MODE_VERIFY:
+ if (!camel_session_is_online (gpg->session)) {
+ /* this is a deprecated flag to gpg since 1.0.7 */
+ /*g_ptr_array_add (argv, "--no-auto-key-retrieve");*/
+ g_ptr_array_add (argv, "--keyserver-options");
+ g_ptr_array_add (argv, "no-auto-key-retrieve");
+ }
+ g_ptr_array_add (argv, "--verify");
+ if (gpg->sigfile)
+ g_ptr_array_add (argv, gpg->sigfile);
+ g_ptr_array_add (argv, "-");
+ break;
+ case GPG_CTX_MODE_ENCRYPT:
+ g_ptr_array_add (argv, "--encrypt");
+ if (gpg->armor)
+ g_ptr_array_add (argv, "--armor");
+ if (gpg->always_trust)
+ g_ptr_array_add (argv, "--always-trust");
+ if (gpg->userid) {
+ g_ptr_array_add (argv, "-u");
+ g_ptr_array_add (argv, (char *) gpg->userid);
+ }
+ if (gpg->recipients) {
+ for (i = 0; i < gpg->recipients->len; i++) {
+ g_ptr_array_add (argv, "-r");
+ g_ptr_array_add (argv, gpg->recipients->pdata[i]);
+ }
+ }
+ g_ptr_array_add (argv, "--output");
+ g_ptr_array_add (argv, "-");
+ break;
+ case GPG_CTX_MODE_DECRYPT:
+ g_ptr_array_add (argv, "--decrypt");
+ g_ptr_array_add (argv, "--output");
+ g_ptr_array_add (argv, "-");
+ break;
+ case GPG_CTX_MODE_IMPORT:
+ g_ptr_array_add (argv, "--import");
+ g_ptr_array_add (argv, "-");
+ break;
+ case GPG_CTX_MODE_EXPORT:
+ if (gpg->armor)
+ g_ptr_array_add (argv, "--armor");
+ g_ptr_array_add (argv, "--export");
+ for (i = 0; i < gpg->recipients->len; i++)
+ g_ptr_array_add (argv, gpg->recipients->pdata[i]);
+ break;
+ }
+
+ g_ptr_array_add (argv, NULL);
+
+ return argv;
+}
+
+static int
+gpg_ctx_op_start (struct _GpgCtx *gpg)
+{
+ char *status_fd = NULL, *passwd_fd = NULL;
+ int i, maxfd, errnosave, fds[10];
+ GPtrArray *argv;
+ int flags;
+
+ for (i = 0; i < 10; i++)
+ fds[i] = -1;
+
+ maxfd = gpg->need_passwd ? 10 : 8;
+ for (i = 0; i < maxfd; i += 2) {
+ if (pipe (fds + i) == -1)
+ goto exception;
+ }
+
+ argv = gpg_ctx_get_argv (gpg, fds[7], &status_fd, fds[8], &passwd_fd);
+
+ if (!(gpg->pid = fork ())) {
+ /* child process */
+
+ if ((dup2 (fds[0], STDIN_FILENO) < 0 ) ||
+ (dup2 (fds[3], STDOUT_FILENO) < 0 ) ||
+ (dup2 (fds[5], STDERR_FILENO) < 0 )) {
+ _exit (255);
+ }
+
+ /* Dissociate from camel's controlling terminal so
+ * that gpg won't be able to read from it.
+ */
+ setsid ();
+
+ maxfd = sysconf (_SC_OPEN_MAX);
+ /* Loop over all fds. */
+ for (i = 3; i < maxfd; i++) {
+ /* don't close the status-fd or passwd-fd */
+ if (i != fds[7] && i != fds[8])
+ fcntl (i, F_SETFD, FD_CLOEXEC);
+ }
+
+ /* run gpg */
+ execvp ("gpg", (char **) argv->pdata);
+ _exit (255);
+ } else if (gpg->pid < 0) {
+ g_ptr_array_free (argv, TRUE);
+ g_free (status_fd);
+ g_free (passwd_fd);
+ goto exception;
+ }
+
+ g_ptr_array_free (argv, TRUE);
+ g_free (status_fd);
+ g_free (passwd_fd);
+
+ /* Parent */
+ close (fds[0]);
+ gpg->stdin_fd = fds[1];
+ gpg->stdout_fd = fds[2];
+ close (fds[3]);
+ gpg->stderr_fd = fds[4];
+ close (fds[5]);
+ gpg->status_fd = fds[6];
+ close (fds[7]);
+ if (gpg->need_passwd) {
+ close (fds[8]);
+ gpg->passwd_fd = fds[9];
+ flags = fcntl (gpg->passwd_fd, F_GETFL);
+ fcntl (gpg->passwd_fd, F_SETFL, flags | O_NONBLOCK);
+ }
+
+ flags = fcntl (gpg->stdin_fd, F_GETFL);
+ fcntl (gpg->stdin_fd, F_SETFL, flags | O_NONBLOCK);
+
+ flags = fcntl (gpg->stdout_fd, F_GETFL);
+ fcntl (gpg->stdout_fd, F_SETFL, flags | O_NONBLOCK);
+
+ flags = fcntl (gpg->stderr_fd, F_GETFL);
+ fcntl (gpg->stderr_fd, F_SETFL, flags | O_NONBLOCK);
+
+ flags = fcntl (gpg->status_fd, F_GETFL);
+ fcntl (gpg->status_fd, F_SETFL, flags | O_NONBLOCK);
+
+ return 0;
+
+ exception:
+
+ errnosave = errno;
+
+ for (i = 0; i < 10; i++) {
+ if (fds[i] != -1)
+ close (fds[i]);
+ }
+
+ errno = errnosave;
+
+ return -1;
+}
+
+static const char *
+next_token (const char *in, char **token)
+{
+ const char *start, *inptr = in;
+
+ while (*inptr == ' ')
+ inptr++;
+
+ if (*inptr == '\0' || *inptr == '\n') {
+ if (token)
+ *token = NULL;
+ return inptr;
+ }
+
+ start = inptr;
+ while (*inptr && *inptr != ' ' && *inptr != '\n')
+ inptr++;
+
+ if (token)
+ *token = g_strndup (start, inptr - start);
+
+ return inptr;
+}
+
+static int
+gpg_ctx_parse_status (struct _GpgCtx *gpg, CamelException *ex)
+{
+ register unsigned char *inptr;
+ const unsigned char *status;
+ size_t nread, nwritten;
+ int len;
+
+ parse:
+
+ inptr = gpg->statusbuf;
+ while (inptr < gpg->statusptr && *inptr != '\n')
+ inptr++;
+
+ if (*inptr != '\n') {
+ /* we don't have enough data buffered to parse this status line */
+ return 0;
+ }
+
+ *inptr++ = '\0';
+ status = gpg->statusbuf;
+
+ if (camel_debug("gpg:status"))
+ printf ("status: %s\n", status);
+
+ if (strncmp (status, "[GNUPG:] ", 9) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected GnuPG status message encountered:\n\n%s"),
+ status);
+ return -1;
+ }
+
+ status += 9;
+
+ if (!strncmp (status, "USERID_HINT ", 12)) {
+ char *hint, *user;
+
+ status += 12;
+ status = next_token (status, &hint);
+ if (!hint) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to parse gpg userid hint."));
+ return -1;
+ }
+
+ if (g_hash_table_lookup (gpg->userid_hint, hint)) {
+ /* we already have this userid hint... */
+ g_free (hint);
+ goto recycle;
+ }
+
+ if (gpg->utf8 || !(user = g_locale_to_utf8 (status, -1, &nread, &nwritten, NULL)))
+ user = g_strdup (status);
+
+ g_strstrip (user);
+
+ g_hash_table_insert (gpg->userid_hint, hint, user);
+ } else if (!strncmp (status, "NEED_PASSPHRASE ", 16)) {
+ char *userid;
+
+ status += 16;
+
+ status = next_token (status, &userid);
+ if (!userid) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to parse gpg passphrase request."));
+ return -1;
+ }
+
+ g_free (gpg->need_id);
+ gpg->need_id = userid;
+ } else if (!strncmp (status, "GET_HIDDEN passphrase.enter", 27)) {
+ char *prompt, *passwd;
+ const char *name;
+
+ name = g_hash_table_lookup (gpg->userid_hint, gpg->need_id);
+ if (!name)
+ name = gpg->need_id;
+
+ prompt = g_strdup_printf (_("You need a passphrase to unlock the key for\n"
+ "user: \"%s\""), name);
+
+ if ((passwd = camel_session_get_password (gpg->session, NULL, NULL, prompt, gpg->need_id, CAMEL_SESSION_PASSWORD_SECRET, ex)) && !gpg->utf8) {
+ char *opasswd = passwd;
+
+ if ((passwd = g_locale_to_utf8 (passwd, -1, &nread, &nwritten, NULL))) {
+ memset (opasswd, 0, strlen (opasswd));
+ g_free (opasswd);
+ } else {
+ passwd = opasswd;
+ }
+ }
+ g_free (prompt);
+
+ if (passwd == NULL) {
+ if (!camel_exception_is_set (ex))
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled."));
+ return -1;
+ }
+
+ gpg->passwd = g_strdup_printf ("%s\n", passwd);
+ memset (passwd, 0, strlen (passwd));
+ g_free (passwd);
+
+ gpg->send_passwd = TRUE;
+ } else if (!strncmp (status, "GOOD_PASSPHRASE", 15)) {
+ gpg->bad_passwds = 0;
+ } else if (!strncmp (status, "BAD_PASSPHRASE", 14)) {
+ gpg->bad_passwds++;
+
+ camel_session_forget_password (gpg->session, NULL, NULL, gpg->need_id, ex);
+
+ if (gpg->bad_passwds == 3) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Failed to unlock secret key: 3 bad passphrases given."));
+ return -1;
+ }
+ } else if (!strncmp (status, "UNEXPECTED ", 11)) {
+ /* this is an error */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected response from GnuPG: %s"),
+ status + 11);
+ return -1;
+ } else if (!strncmp (status, "NODATA", 6)) {
+ /* this is an error */
+ /* But we ignore it anyway, we should get other response codes to say why */
+ gpg->nodata = TRUE;
+ } else {
+ /* check to see if we are complete */
+ switch (gpg->mode) {
+ case GPG_CTX_MODE_SIGN:
+ if (!strncmp (status, "SIG_CREATED ", 12)) {
+ /* FIXME: save this state? */
+ }
+ break;
+ case GPG_CTX_MODE_VERIFY:
+ if (!strncmp (status, "TRUST_", 6)) {
+ status += 6;
+ if (!strncmp (status, "NEVER", 5)) {
+ gpg->trust = GPG_TRUST_NEVER;
+ } else if (!strncmp (status, "MARGINAL", 8)) {
+ gpg->trust = GPG_TRUST_MARGINAL;
+ } else if (!strncmp (status, "FULLY", 5)) {
+ gpg->trust = GPG_TRUST_FULLY;
+ } else if (!strncmp (status, "ULTIMATE", 8)) {
+ gpg->trust = GPG_TRUST_ULTIMATE;
+ } else if (!strncmp (status, "UNDEFINED", 9)) {
+ gpg->trust = GPG_TRUST_UNDEFINED;
+ }
+ } else if (!strncmp (status, "GOODSIG ", 8)) {
+ gpg->goodsig = TRUE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp (status, "VALIDSIG ", 9)) {
+ gpg->validsig = TRUE;
+ } else if (!strncmp (status, "BADSIG ", 7)) {
+ gpg->badsig = FALSE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp (status, "ERRSIG ", 7)) {
+ /* Note: NO_PUBKEY often comes after an ERRSIG */
+ gpg->errsig = FALSE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp (status, "NO_PUBKEY ", 10)) {
+ gpg->nopubkey = TRUE;
+ }
+ break;
+ case GPG_CTX_MODE_ENCRYPT:
+ if (!strncmp (status, "BEGIN_ENCRYPTION", 16)) {
+ /* nothing to do... but we know to expect data on stdout soon */
+ } else if (!strncmp (status, "END_ENCRYPTION", 14)) {
+ /* nothing to do, but we know the end is near? */
+ } else if (!strncmp (status, "NO_RECP", 7)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to encrypt: No valid recipients specified."));
+ return -1;
+ }
+ break;
+ case GPG_CTX_MODE_DECRYPT:
+ if (!strncmp (status, "BEGIN_DECRYPTION", 16)) {
+ /* nothing to do... but we know to expect data on stdout soon */
+ } else if (!strncmp (status, "END_DECRYPTION", 14)) {
+ /* nothing to do, but we know the end is near? */
+ }
+ break;
+ case GPG_CTX_MODE_IMPORT:
+ /* noop */
+ break;
+ case GPG_CTX_MODE_EXPORT:
+ /* noop */
+ break;
+ }
+ }
+
+ recycle:
+
+ /* recycle our statusbuf by moving inptr to the beginning of statusbuf */
+ len = gpg->statusptr - inptr;
+ memmove (gpg->statusbuf, inptr, len);
+
+ len = inptr - gpg->statusbuf;
+ gpg->statusleft += len;
+ gpg->statusptr -= len;
+
+ /* if we have more data, try parsing the next line? */
+ if (gpg->statusptr > gpg->statusbuf)
+ goto parse;
+
+ return 0;
+}
+
+#define status_backup(gpg, start, len) G_STMT_START { \
+ if (gpg->statusleft <= len) { \
+ unsigned int slen, soff; \
+ \
+ slen = soff = gpg->statusptr - gpg->statusbuf; \
+ slen = slen ? slen : 1; \
+ \
+ while (slen < soff + len) \
+ slen <<= 1; \
+ \
+ gpg->statusbuf = g_realloc (gpg->statusbuf, slen + 1); \
+ gpg->statusptr = gpg->statusbuf + soff; \
+ gpg->statusleft = slen - soff; \
+ } \
+ \
+ memcpy (gpg->statusptr, start, len); \
+ gpg->statusptr += len; \
+ gpg->statusleft -= len; \
+} G_STMT_END
+
+static void
+gpg_ctx_op_cancel (struct _GpgCtx *gpg)
+{
+ pid_t retval;
+ int status;
+
+ if (gpg->exited)
+ return;
+
+ kill (gpg->pid, SIGTERM);
+ sleep (1);
+ retval = waitpid (gpg->pid, &status, WNOHANG);
+ if (retval == (pid_t) 0) {
+ /* no more mr nice guy... */
+ kill (gpg->pid, SIGKILL);
+ sleep (1);
+ waitpid (gpg->pid, &status, WNOHANG);
+ }
+}
+
+static int
+gpg_ctx_op_step (struct _GpgCtx *gpg, CamelException *ex)
+{
+ struct pollfd polls[6];
+ int status, i, cancel_fd;
+
+ for (i=0;i<6;i++)
+ polls[i].fd = -1;
+
+ if (!gpg->seen_eof1) {
+ polls[0].fd = gpg->stdout_fd;
+ polls[0].events = POLLIN;
+ }
+
+ if (!gpg->seen_eof2) {
+ polls[1].fd = gpg->stderr_fd;
+ polls[1].events = POLLIN;
+ }
+
+ if (!gpg->complete) {
+ polls[2].fd = gpg->status_fd;
+ polls[2].events = POLLIN;
+ }
+
+ polls[3].fd = gpg->stdin_fd;
+ polls[3].events = POLLOUT;
+ polls[4].fd = gpg->passwd_fd;
+ polls[4].events = POLLOUT;
+ cancel_fd = camel_operation_cancel_fd(NULL);
+ polls[5].fd = cancel_fd;
+ polls[5].events = POLLIN;
+
+ do {
+ for (i=0;i<6;i++)
+ polls[i].revents = 0;
+ status = poll(polls, 6, 30*1000);
+ } while (status == -1 && errno == EINTR);
+
+ if (status == 0)
+ return 0; /* timed out */
+ else if (status == -1)
+ goto exception;
+
+ if ((polls[5].revents & POLLIN) && camel_operation_cancel_check(NULL)) {
+ camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled."));
+ gpg_ctx_op_cancel(gpg);
+ return -1;
+ }
+
+ /* Test each and every file descriptor to see if it's 'ready',
+ and if so - do what we can with it and then drop through to
+ the next file descriptor and so on until we've done what we
+ can to all of them. If one fails along the way, return
+ -1. */
+
+ if (polls[2].revents & (POLLIN|POLLHUP)) {
+ /* read the status message and decide what to do... */
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("reading from gpg's status-fd...\n"));
+
+ do {
+ nread = read (gpg->status_fd, buffer, sizeof (buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ status_backup (gpg, buffer, nread);
+
+ if (gpg_ctx_parse_status (gpg, ex) == -1)
+ return -1;
+ } else {
+ gpg->complete = TRUE;
+ }
+ }
+
+ if ((polls[0].revents & (POLLIN|POLLHUP)) && gpg->ostream) {
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("reading gpg's stdout...\n"));
+
+ do {
+ nread = read (gpg->stdout_fd, buffer, sizeof (buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ if (camel_stream_write (gpg->ostream, buffer, (size_t) nread) == -1)
+ goto exception;
+ } else {
+ gpg->seen_eof1 = TRUE;
+ }
+ }
+
+ if (polls[1].revents & (POLLIN|POLLHUP)) {
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("reading gpg's stderr...\n"));
+
+ do {
+ nread = read (gpg->stderr_fd, buffer, sizeof (buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ camel_stream_write (gpg->diagnostics, buffer, nread);
+ } else {
+ gpg->seen_eof2 = TRUE;
+ }
+ }
+
+ if ((polls[4].revents & (POLLOUT|POLLHUP)) && gpg->need_passwd && gpg->send_passwd) {
+ ssize_t w, nwritten = 0;
+ size_t n;
+
+ d(printf ("sending gpg our passphrase...\n"));
+
+ /* send the passphrase to gpg */
+ n = strlen (gpg->passwd);
+ do {
+ do {
+ w = write (gpg->passwd_fd, gpg->passwd + nwritten, n - nwritten);
+ } while (w == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (w > 0)
+ nwritten += w;
+ } while (nwritten < n && w != -1);
+
+ /* zero and free our passwd buffer */
+ memset (gpg->passwd, 0, n);
+ g_free (gpg->passwd);
+ gpg->passwd = NULL;
+
+ if (w == -1)
+ goto exception;
+
+ gpg->send_passwd = FALSE;
+ }
+
+ if ((polls[3].revents & (POLLOUT|POLLHUP)) && gpg->istream) {
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("writing to gpg's stdin...\n"));
+
+ /* write our stream to gpg's stdin */
+ nread = camel_stream_read (gpg->istream, buffer, sizeof (buffer));
+ if (nread > 0) {
+ ssize_t w, nwritten = 0;
+
+ do {
+ do {
+ w = write (gpg->stdin_fd, buffer + nwritten, nread - nwritten);
+ } while (w == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (w > 0)
+ nwritten += w;
+ } while (nwritten < nread && w != -1);
+
+ if (w == -1)
+ goto exception;
+
+ d(printf ("wrote %d (out of %d) bytes to gpg's stdin\n", nwritten, nread));
+ }
+
+ if (camel_stream_eos (gpg->istream)) {
+ d(printf ("closing gpg's stdin\n"));
+ close (gpg->stdin_fd);
+ gpg->stdin_fd = -1;
+ }
+ }
+
+ return 0;
+
+exception:
+ /* always called on an i/o error */
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to execute gpg: %s"), g_strerror(errno));
+ gpg_ctx_op_cancel(gpg);
+
+ return -1;
+}
+
+static gboolean
+gpg_ctx_op_complete (struct _GpgCtx *gpg)
+{
+ return gpg->complete && gpg->seen_eof1 && gpg->seen_eof2;}
+
+
+#if 0
+static gboolean
+gpg_ctx_op_exited (struct _GpgCtx *gpg)
+{
+ pid_t retval;
+ int status;
+
+ if (gpg->exited)
+ return TRUE;
+
+ retval = waitpid (gpg->pid, &status, WNOHANG);
+ if (retval == gpg->pid) {
+ gpg->exit_status = status;
+ gpg->exited = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif
+
+static int
+gpg_ctx_op_wait (struct _GpgCtx *gpg)
+{
+ sigset_t mask, omask;
+ pid_t retval;
+ int status;
+
+ if (!gpg->exited) {
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGALRM);
+ sigprocmask (SIG_BLOCK, &mask, &omask);
+ alarm (1);
+ retval = waitpid (gpg->pid, &status, 0);
+ alarm (0);
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+
+ if (retval == (pid_t) -1 && errno == EINTR) {
+ /* The child is hanging: send a friendly reminder. */
+ kill (gpg->pid, SIGTERM);
+ sleep (1);
+ retval = waitpid (gpg->pid, &status, WNOHANG);
+ if (retval == (pid_t) 0) {
+ /* Still hanging; use brute force. */
+ kill (gpg->pid, SIGKILL);
+ sleep (1);
+ retval = waitpid (gpg->pid, &status, WNOHANG);
+ }
+ }
+ } else {
+ status = gpg->exit_status;
+ retval = gpg->pid;
+ }
+
+ if (retval != (pid_t) -1 && WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else
+ return -1;
+}
+
+
+
+static int
+gpg_sign (CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ struct _GpgCtx *gpg = NULL;
+ CamelStream *ostream = camel_stream_mem_new(), *istream;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ int res = -1;
+ CamelMimePart *sigpart;
+ CamelMultipartSigned *mps;
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+
+ /* FIXME: stream this, we stream output at least */
+ istream = camel_stream_mem_new();
+ if (camel_cipher_canonical_to_stream(ipart, CAMEL_MIME_FILTER_CANON_STRIP|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_FROM,
+ istream) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not generate signing data: %s"), g_strerror(errno));
+ goto fail;
+ }
+
+#ifdef GPG_LOG
+ if (camel_debug_start("gpg:sign")) {
+ char *name;
+ CamelStream *out;
+
+ name = g_strdup_printf("camel-gpg.%d.sign-data", logid++);
+ out = camel_stream_fs_new_with_name(name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (out) {
+ printf("Writing gpg signing data to '%s'\n", name);
+ camel_stream_write_to_stream(istream, out);
+ camel_stream_reset(istream);
+ camel_object_unref(out);
+ }
+ g_free(name);
+ camel_debug_end();
+ }
+#endif
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_SIGN);
+ gpg_ctx_set_hash (gpg, hash);
+ gpg_ctx_set_armor (gpg, TRUE);
+ gpg_ctx_set_userid (gpg, userid);
+ gpg_ctx_set_istream (gpg, istream);
+ gpg_ctx_set_ostream (gpg, ostream);
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg: %s"), g_strerror (errno));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+
+ dw = camel_data_wrapper_new();
+ camel_stream_reset(ostream);
+ camel_data_wrapper_construct_from_stream(dw, ostream);
+
+ sigpart = camel_mime_part_new();
+ ct = camel_content_type_new("application", "pgp-signature");
+ camel_content_type_set_param(ct, "name", "signature.asc");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)sigpart, dw);
+ camel_object_unref(dw);
+
+ camel_mime_part_set_description(sigpart, _("This is a digitally signed message part"));
+
+ mps = camel_multipart_signed_new();
+ ct = camel_content_type_new("multipart", "signed");
+ camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
+ camel_content_type_set_param(ct, "protocol", context->sign_protocol);
+ camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
+ camel_content_type_unref(ct);
+ camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
+
+ mps->signature = sigpart;
+ mps->contentraw = istream;
+ camel_stream_reset(istream);
+ camel_object_ref(istream);
+
+ camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
+fail:
+ camel_object_unref(ostream);
+
+ if (gpg)
+ gpg_ctx_free (gpg);
+
+ return res;
+}
+
+
+static char *
+swrite (CamelMimePart *sigpart)
+{
+ CamelStream *ostream;
+ char *template;
+ int fd, ret;
+
+ template = g_strdup ("/tmp/evolution-pgp.XXXXXX");
+ fd = mkstemp (template);
+ if (fd == -1) {
+ g_free (template);
+ return NULL;
+ }
+
+ /* TODO: This should probably just write the decoded message content out, not the part + headers */
+
+ ostream = camel_stream_fs_new_with_fd (fd);
+ ret = camel_data_wrapper_write_to_stream((CamelDataWrapper *)sigpart, ostream);
+ if (ret != -1) {
+ ret = camel_stream_flush (ostream);
+ if (ret != -1)
+ ret = camel_stream_close (ostream);
+ }
+
+ camel_object_unref(ostream);
+
+ if (ret == -1) {
+ unlink (template);
+ g_free (template);
+ return NULL;
+ }
+
+ return template;
+}
+
+static CamelCipherValidity *
+gpg_verify (CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
+{
+ CamelCipherValidity *validity;
+ const char *diagnostics = NULL, *tmp;
+ struct _GpgCtx *gpg = NULL;
+ char *sigfile = NULL;
+ CamelContentType *ct;
+ CamelMimePart *sigpart;
+ CamelStream *istream = NULL;
+ CamelMultipart *mps;
+
+ mps = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)ipart);
+ ct = ((CamelDataWrapper *)mps)->mime_type;
+ tmp = camel_content_type_param(ct, "protocol");
+ if (!camel_content_type_is(ct, "multipart", "signed")
+ || !CAMEL_IS_MULTIPART_SIGNED(mps)
+ || tmp == NULL
+ || g_ascii_strcasecmp(tmp, context->sign_protocol) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ return NULL;
+ }
+
+ if (!(istream = camel_multipart_signed_get_content_stream ((CamelMultipartSigned *) mps, NULL))) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ return NULL;
+ }
+
+ if (!(sigpart = camel_multipart_get_part (mps, CAMEL_MULTIPART_SIGNED_SIGNATURE))) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ camel_object_unref (istream);
+ return NULL;
+ }
+
+#ifdef GPG_LOG
+ if (camel_debug_start("gpg:sign")) {
+ char *name;
+ CamelStream *out;
+
+ name = g_strdup_printf("camel-gpg.%d.verify.data", logid);
+ out = camel_stream_fs_new_with_name(name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (out) {
+ printf("Writing gpg verify data to '%s'\n", name);
+ camel_stream_write_to_stream(istream, out);
+ camel_stream_reset(istream);
+ camel_object_unref(out);
+ }
+ g_free(name);
+ name = g_strdup_printf("camel-gpg.%d.verify.signature", logid++);
+ out = camel_stream_fs_new_with_name(name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (out) {
+ printf("Writing gpg verify signature to '%s'\n", name);
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)sigpart, out);
+ camel_object_unref(out);
+ }
+ g_free(name);
+ camel_debug_end();
+ }
+#endif
+
+ sigfile = swrite (sigpart);
+ if (sigfile == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: could not create temp file: %s"),
+ g_strerror (errno));
+ goto exception;
+ }
+
+ camel_stream_reset(istream);
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY);
+ gpg_ctx_set_hash (gpg, camel_cipher_id_to_hash(context, camel_content_type_param(ct, "micalg")));
+ gpg_ctx_set_sigfile (gpg, sigfile);
+ gpg_ctx_set_istream (gpg, istream);
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg."));
+ goto exception;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto exception;
+ }
+
+ gpg_ctx_op_wait (gpg);
+ validity = camel_cipher_validity_new ();
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_cipher_validity_set_description (validity, diagnostics);
+ if (gpg->validsig) {
+ if (gpg->trust == GPG_TRUST_UNDEFINED || gpg->trust == GPG_TRUST_NONE)
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN;
+ else if (gpg->trust != GPG_TRUST_NEVER)
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_GOOD;
+ else
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ } else {
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ }
+
+ gpg_ctx_free (gpg);
+
+ if (sigfile) {
+ unlink (sigfile);
+ g_free (sigfile);
+ }
+
+ return validity;
+
+ exception:
+
+ if (gpg != NULL)
+ gpg_ctx_free (gpg);
+
+ if (istream)
+ camel_object_unref(istream);
+
+ if (sigfile) {
+ unlink (sigfile);
+ g_free (sigfile);
+ }
+
+ return NULL;
+}
+
+static int
+gpg_encrypt (CamelCipherContext *context, const char *userid, GPtrArray *recipients, struct _CamelMimePart *ipart, struct _CamelMimePart *opart, CamelException *ex)
+{
+ CamelGpgContext *ctx = (CamelGpgContext *) context;
+ struct _GpgCtx *gpg;
+ int i, res = -1;
+ CamelStream *istream, *ostream, *vstream;
+ CamelMimePart *encpart, *verpart;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ CamelMultipartEncrypted *mpe;
+
+ ostream = camel_stream_mem_new();
+ istream = camel_stream_mem_new();
+ if (camel_cipher_canonical_to_stream(ipart, CAMEL_MIME_FILTER_CANON_CRLF, istream) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not generate encrypting data: %s"), g_strerror(errno));
+ goto fail1;
+ }
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_ENCRYPT);
+ gpg_ctx_set_armor (gpg, TRUE);
+ gpg_ctx_set_userid (gpg, userid);
+ gpg_ctx_set_istream (gpg, istream);
+ gpg_ctx_set_ostream (gpg, ostream);
+ gpg_ctx_set_always_trust (gpg, ctx->always_trust);
+
+ for (i = 0; i < recipients->len; i++) {
+ gpg_ctx_add_recipient (gpg, recipients->pdata[i]);
+ }
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ /* FIXME: move tihs to a common routine */
+ while (!gpg_ctx_op_complete(gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics : _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+
+ dw = camel_data_wrapper_new();
+ camel_data_wrapper_construct_from_stream(dw, ostream);
+
+ encpart = camel_mime_part_new();
+ ct = camel_content_type_new("application", "octet-stream");
+ camel_content_type_set_param(ct, "name", "encrypted.asc");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)encpart, dw);
+ camel_object_unref(dw);
+
+ camel_mime_part_set_description(encpart, _("This is a digitally encrypted message part"));
+
+ vstream = camel_stream_mem_new();
+ camel_stream_write(vstream, "Version: 1\n", strlen("Version: 1\n"));
+ camel_stream_reset(vstream);
+
+ verpart = camel_mime_part_new();
+ dw = camel_data_wrapper_new();
+ camel_data_wrapper_set_mime_type(dw, context->encrypt_protocol);
+ camel_data_wrapper_construct_from_stream(dw, vstream);
+ camel_object_unref(vstream);
+ camel_medium_set_content_object((CamelMedium *)verpart, dw);
+ camel_object_unref(dw);
+
+ mpe = camel_multipart_encrypted_new();
+ ct = camel_content_type_new("multipart", "encrypted");
+ camel_content_type_set_param(ct, "protocol", context->encrypt_protocol);
+ camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mpe, ct);
+ camel_content_type_unref(ct);
+ camel_multipart_set_boundary((CamelMultipart *)mpe, NULL);
+
+ mpe->decrypted = ipart;
+ camel_object_ref(ipart);
+
+ camel_multipart_add_part((CamelMultipart *)mpe, verpart);
+ camel_object_unref(verpart);
+ camel_multipart_add_part((CamelMultipart *)mpe, encpart);
+ camel_object_unref(encpart);
+
+ camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mpe);
+fail:
+ gpg_ctx_free(gpg);
+fail1:
+ camel_object_unref(istream);
+ camel_object_unref(ostream);
+
+ return res;
+}
+
+static CamelCipherValidity *
+gpg_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ struct _GpgCtx *gpg;
+ CamelCipherValidity *valid = NULL;
+ CamelStream *ostream, *istream;
+ CamelDataWrapper *content;
+ CamelMimePart *encrypted;
+ CamelMultipart *mp;
+
+ mp = (CamelMultipart *) camel_medium_get_content_object ((CamelMedium *) ipart);
+ if (!(encrypted = camel_multipart_get_part (mp, CAMEL_MULTIPART_ENCRYPTED_CONTENT))) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to decrypt MIME part: protocol error"));
+ return NULL;
+ }
+
+ content = camel_medium_get_content_object ((CamelMedium *) encrypted);
+
+ istream = camel_stream_mem_new();
+ camel_data_wrapper_decode_to_stream (content, istream);
+ camel_stream_reset(istream);
+
+ ostream = camel_stream_mem_new();
+ camel_stream_mem_set_secure((CamelStreamMem *)ostream);
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_DECRYPT);
+ gpg_ctx_set_istream (gpg, istream);
+ gpg_ctx_set_ostream (gpg, ostream);
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ camel_stream_reset(ostream);
+ if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream) != -1) {
+ valid = camel_cipher_validity_new();
+ valid->encrypt.description = g_strdup(_("Encrypted content"));
+ valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
+
+ if (gpg->hadsig) {
+ if (gpg->validsig) {
+ if (gpg->trust == GPG_TRUST_UNDEFINED || gpg->trust == GPG_TRUST_NONE)
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN;
+ else if (gpg->trust != GPG_TRUST_NEVER)
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_GOOD;
+ else
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ } else if (gpg->nopubkey) {
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN;
+ } else {
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ }
+ }
+ } else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unable to parse message content"));
+ }
+
+ fail:
+ camel_object_unref(ostream);
+ camel_object_unref(istream);
+ gpg_ctx_free (gpg);
+
+ return valid;
+}
+
+static int
+gpg_import_keys (CamelCipherContext *context, CamelStream *istream, CamelException *ex)
+{
+ struct _GpgCtx *gpg;
+ int res = -1;
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_IMPORT);
+ gpg_ctx_set_istream (gpg, istream);
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg: %s"),
+ errno ? g_strerror (errno) : _("Unknown"));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+fail:
+ gpg_ctx_free (gpg);
+
+ return res;
+}
+
+static int
+gpg_export_keys (CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
+{
+ struct _GpgCtx *gpg;
+ int i;
+ int res = -1;
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_EXPORT);
+ gpg_ctx_set_armor (gpg, TRUE);
+ gpg_ctx_set_ostream (gpg, ostream);
+
+ for (i = 0; i < keys->len; i++) {
+ gpg_ctx_add_recipient (gpg, keys->pdata[i]);
+ }
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg: %s"),
+ errno ? g_strerror (errno) : _("Unknown"));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+fail:
+ gpg_ctx_free (gpg);
+
+ return res;
+}
+
+/* ********************************************************************** */
+
+static void
+camel_gpg_context_class_init (CamelGpgContextClass *klass)
+{
+ CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS (klass);
+
+ parent_class = CAMEL_CIPHER_CONTEXT_CLASS (camel_type_get_global_classfuncs (camel_cipher_context_get_type ()));
+
+ cipher_class->hash_to_id = gpg_hash_to_id;
+ cipher_class->id_to_hash = gpg_id_to_hash;
+ cipher_class->sign = gpg_sign;
+ cipher_class->verify = gpg_verify;
+ cipher_class->encrypt = gpg_encrypt;
+ cipher_class->decrypt = gpg_decrypt;
+ cipher_class->import_keys = gpg_import_keys;
+ cipher_class->export_keys = gpg_export_keys;
+}
+
+static void
+camel_gpg_context_init (CamelGpgContext *context)
+{
+ CamelCipherContext *cipher = (CamelCipherContext *) context;
+
+ context->always_trust = FALSE;
+
+ cipher->sign_protocol = "application/pgp-signature";
+ cipher->encrypt_protocol = "application/pgp-encrypted";
+ cipher->key_protocol = "application/pgp-keys";
+}
+
+static void
+camel_gpg_context_finalise (CamelObject *object)
+{
+ ;
+}
+
+CamelType
+camel_gpg_context_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_cipher_context_get_type (),
+ "CamelGpgContext",
+ sizeof (CamelGpgContext),
+ sizeof (CamelGpgContextClass),
+ (CamelObjectClassInitFunc) camel_gpg_context_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_gpg_context_init,
+ (CamelObjectFinalizeFunc) camel_gpg_context_finalise);
+ }
+
+ return type;
+}
+
+
diff --git a/camel/camel-html-parser.c b/camel/camel-html-parser.c
new file mode 100644
index 0000000000..17bf52f04c
--- /dev/null
+++ b/camel/camel-html-parser.c
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2001 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/** WARNING
+ **
+ ** DO NOT USE THIS CODE OUTSIDE OF CAMEL
+ **
+ ** IT IS SUBJECT TO CHANGE OR MAY VANISH AT ANY TIME
+ **/
+
+#include "camel-html-parser.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gunicode.h>
+#include <ctype.h>
+
+/* if defined, must also compile in dump_tag() below somewhere */
+#define d(x)
+
+static void camel_html_parser_class_init (CamelHTMLParserClass *klass);
+static void camel_html_parser_init (CamelObject *o);
+static void camel_html_parser_finalize (CamelObject *o);
+
+static CamelObjectClass *camel_html_parser_parent;
+
+/* Parser definitions, see below object code for details */
+
+typedef struct _CamelHTMLParserPrivate CamelHTMLParserPrivate;
+
+struct _CamelHTMLParserPrivate {
+ char *inbuf,
+ *inptr,
+ *inend,
+ *start;
+ enum _camel_html_parser_t state;
+ char *charset;
+ int eof;
+ GString *tag;
+ GString *ent;
+ char ent_utf8[8];
+ int attr;
+ GPtrArray *attrs;
+ GPtrArray *values;
+ int quote;
+};
+
+static void tokenise_setup(void);
+static CamelHTMLParserPrivate *tokenise_init(void);
+static void tokenise_free(CamelHTMLParserPrivate *p);
+static int tokenise_step(CamelHTMLParserPrivate *p, char **datap, int *lenp);
+
+/* ********************************************************************** */
+
+CamelType
+camel_html_parser_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_object_get_type (), "CamelHTMLParser",
+ sizeof (CamelHTMLParser),
+ sizeof (CamelHTMLParserClass),
+ (CamelObjectClassInitFunc) camel_html_parser_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_html_parser_init,
+ (CamelObjectFinalizeFunc) camel_html_parser_finalize);
+ }
+
+ return type;
+}
+
+static void
+camel_html_parser_finalize(CamelObject *o)
+{
+ CamelHTMLParser *f = (CamelHTMLParser *)o;
+
+ tokenise_free(f->priv);
+}
+
+static void
+camel_html_parser_init (CamelObject *o)
+{
+ CamelHTMLParser *f = (CamelHTMLParser *)o;
+
+ f->priv = tokenise_init();
+}
+
+static void
+camel_html_parser_class_init (CamelHTMLParserClass *klass)
+{
+ camel_html_parser_parent = CAMEL_OBJECT_CLASS (camel_type_get_global_classfuncs (camel_object_get_type ()));
+
+ tokenise_setup();
+}
+
+/**
+ * camel_html_parser_new:
+ *
+ * Create a new CamelHTMLParser object.
+ *
+ * Return value: A new CamelHTMLParser widget.
+ **/
+CamelHTMLParser *
+camel_html_parser_new (void)
+{
+ CamelHTMLParser *new = CAMEL_HTML_PARSER ( camel_object_new (camel_html_parser_get_type ()));
+ return new;
+}
+
+
+void camel_html_parser_set_data(CamelHTMLParser *hp, const char *start, int len, int last)
+{
+ CamelHTMLParserPrivate *p = hp->priv;
+
+ p->inptr = p->inbuf = (char *)start;
+ p->inend = (char *)start+len;
+ p->eof = last;
+}
+
+camel_html_parser_t camel_html_parser_step(CamelHTMLParser *hp, const char **datap, int *lenp)
+{
+ return tokenise_step(hp->priv, (char **)datap, lenp);
+}
+
+const char *camel_html_parser_left(CamelHTMLParser *hp, int *lenp)
+{
+ CamelHTMLParserPrivate *p = hp->priv;
+
+ if (lenp)
+ *lenp = p->inend - p->inptr;
+
+ return p->inptr;
+}
+
+const char *camel_html_parser_tag(CamelHTMLParser *hp)
+{
+ return hp->priv->tag->str;
+}
+
+const char *camel_html_parser_attr(CamelHTMLParser *hp, const char *name)
+{
+ int i;
+ CamelHTMLParserPrivate *p = hp->priv;
+
+ for (i=0;i<p->attrs->len;i++) {
+ if (!g_ascii_strcasecmp(((GString *)p->attrs->pdata[i])->str, name)) {
+ return ((GString *)p->values->pdata[i])->str;
+ }
+ }
+
+ return NULL;
+}
+
+const GPtrArray *camel_html_parser_attr_list(CamelHTMLParser *hp, const GPtrArray **values)
+{
+ if (values)
+ *values = hp->priv->values;
+
+ return hp->priv->attrs;
+}
+
+/* this map taken out of libxml */
+static struct {
+ unsigned int val;
+ const char *name;
+} entity_map[] = {
+/*
+ * the 4 absolute ones,
+ */
+ { 34, "quot", /* quotation mark = APL quote, U+0022 ISOnum */ },
+ { 38, "amp", /* ampersand, U+0026 ISOnum */ },
+ { 60, "lt", /* less-than sign, U+003C ISOnum */ },
+ { 62, "gt", /* greater-than sign, U+003E ISOnum */ },
+
+/*
+ * A bunch still in the 128-255 range
+ * Replacing them depend really on the charset used.
+ */
+ { 39, "apos", /* single quote */ },
+ { 160, "nbsp", /* no-break space = non-breaking space, U+00A0 ISOnum */ },
+ { 161, "iexcl",/* inverted exclamation mark, U+00A1 ISOnum */ },
+ { 162, "cent", /* cent sign, U+00A2 ISOnum */ },
+ { 163, "pound",/* pound sign, U+00A3 ISOnum */ },
+ { 164, "curren",/* currency sign, U+00A4 ISOnum */ },
+ { 165, "yen", /* yen sign = yuan sign, U+00A5 ISOnum */ },
+ { 166, "brvbar",/* broken bar = broken vertical bar, U+00A6 ISOnum */ },
+ { 167, "sect", /* section sign, U+00A7 ISOnum */ },
+ { 168, "uml", /* diaeresis = spacing diaeresis, U+00A8 ISOdia */ },
+ { 169, "copy", /* copyright sign, U+00A9 ISOnum */ },
+ { 170, "ordf", /* feminine ordinal indicator, U+00AA ISOnum */ },
+ { 171, "laquo",/* left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum */ },
+ { 172, "not", /* not sign, U+00AC ISOnum */ },
+ { 173, "shy", /* soft hyphen = discretionary hyphen, U+00AD ISOnum */ },
+ { 174, "reg", /* registered sign = registered trade mark sign, U+00AE ISOnum */ },
+ { 175, "macr", /* macron = spacing macron = overline = APL overbar, U+00AF ISOdia */ },
+ { 176, "deg", /* degree sign, U+00B0 ISOnum */ },
+ { 177, "plusmn",/* plus-minus sign = plus-or-minus sign, U+00B1 ISOnum */ },
+ { 178, "sup2", /* superscript two = superscript digit two = squared, U+00B2 ISOnum */ },
+ { 179, "sup3", /* superscript three = superscript digit three = cubed, U+00B3 ISOnum */ },
+ { 180, "acute",/* acute accent = spacing acute, U+00B4 ISOdia */ },
+ { 181, "micro",/* micro sign, U+00B5 ISOnum */ },
+ { 182, "para", /* pilcrow sign = paragraph sign, U+00B6 ISOnum */ },
+ { 183, "middot",/* middle dot = Georgian comma Greek middle dot, U+00B7 ISOnum */ },
+ { 184, "cedil",/* cedilla = spacing cedilla, U+00B8 ISOdia */ },
+ { 185, "sup1", /* superscript one = superscript digit one, U+00B9 ISOnum */ },
+ { 186, "ordm", /* masculine ordinal indicator, U+00BA ISOnum */ },
+ { 187, "raquo",/* right-pointing double angle quotation mark right pointing guillemet, U+00BB ISOnum */ },
+ { 188, "frac14",/* vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum */ },
+ { 189, "frac12",/* vulgar fraction one half = fraction one half, U+00BD ISOnum */ },
+ { 190, "frac34",/* vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum */ },
+ { 191, "iquest",/* inverted question mark = turned question mark, U+00BF ISOnum */ },
+ { 192, "Agrave",/* latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 */ },
+ { 193, "Aacute",/* latin capital letter A with acute, U+00C1 ISOlat1 */ },
+ { 194, "Acirc",/* latin capital letter A with circumflex, U+00C2 ISOlat1 */ },
+ { 195, "Atilde",/* latin capital letter A with tilde, U+00C3 ISOlat1 */ },
+ { 196, "Auml", /* latin capital letter A with diaeresis, U+00C4 ISOlat1 */ },
+ { 197, "Aring",/* latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 */ },
+ { 198, "AElig",/* latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 */ },
+ { 199, "Ccedil",/* latin capital letter C with cedilla, U+00C7 ISOlat1 */ },
+ { 200, "Egrave",/* latin capital letter E with grave, U+00C8 ISOlat1 */ },
+ { 201, "Eacute",/* latin capital letter E with acute, U+00C9 ISOlat1 */ },
+ { 202, "Ecirc",/* latin capital letter E with circumflex, U+00CA ISOlat1 */ },
+ { 203, "Euml", /* latin capital letter E with diaeresis, U+00CB ISOlat1 */ },
+ { 204, "Igrave",/* latin capital letter I with grave, U+00CC ISOlat1 */ },
+ { 205, "Iacute",/* latin capital letter I with acute, U+00CD ISOlat1 */ },
+ { 206, "Icirc",/* latin capital letter I with circumflex, U+00CE ISOlat1 */ },
+ { 207, "Iuml", /* latin capital letter I with diaeresis, U+00CF ISOlat1 */ },
+ { 208, "ETH", /* latin capital letter ETH, U+00D0 ISOlat1 */ },
+ { 209, "Ntilde",/* latin capital letter N with tilde, U+00D1 ISOlat1 */ },
+ { 210, "Ograve",/* latin capital letter O with grave, U+00D2 ISOlat1 */ },
+ { 211, "Oacute",/* latin capital letter O with acute, U+00D3 ISOlat1 */ },
+ { 212, "Ocirc",/* latin capital letter O with circumflex, U+00D4 ISOlat1 */ },
+ { 213, "Otilde",/* latin capital letter O with tilde, U+00D5 ISOlat1 */ },
+ { 214, "Ouml", /* latin capital letter O with diaeresis, U+00D6 ISOlat1 */ },
+ { 215, "times",/* multiplication sign, U+00D7 ISOnum */ },
+ { 216, "Oslash",/* latin capital letter O with stroke latin capital letter O slash, U+00D8 ISOlat1 */ },
+ { 217, "Ugrave",/* latin capital letter U with grave, U+00D9 ISOlat1 */ },
+ { 218, "Uacute",/* latin capital letter U with acute, U+00DA ISOlat1 */ },
+ { 219, "Ucirc",/* latin capital letter U with circumflex, U+00DB ISOlat1 */ },
+ { 220, "Uuml", /* latin capital letter U with diaeresis, U+00DC ISOlat1 */ },
+ { 221, "Yacute",/* latin capital letter Y with acute, U+00DD ISOlat1 */ },
+ { 222, "THORN",/* latin capital letter THORN, U+00DE ISOlat1 */ },
+ { 223, "szlig",/* latin small letter sharp s = ess-zed, U+00DF ISOlat1 */ },
+ { 224, "agrave",/* latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 */ },
+ { 225, "aacute",/* latin small letter a with acute, U+00E1 ISOlat1 */ },
+ { 226, "acirc",/* latin small letter a with circumflex, U+00E2 ISOlat1 */ },
+ { 227, "atilde",/* latin small letter a with tilde, U+00E3 ISOlat1 */ },
+ { 228, "auml", /* latin small letter a with diaeresis, U+00E4 ISOlat1 */ },
+ { 229, "aring",/* latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 */ },
+ { 230, "aelig",/* latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 */ },
+ { 231, "ccedil",/* latin small letter c with cedilla, U+00E7 ISOlat1 */ },
+ { 232, "egrave",/* latin small letter e with grave, U+00E8 ISOlat1 */ },
+ { 233, "eacute",/* latin small letter e with acute, U+00E9 ISOlat1 */ },
+ { 234, "ecirc",/* latin small letter e with circumflex, U+00EA ISOlat1 */ },
+ { 235, "euml", /* latin small letter e with diaeresis, U+00EB ISOlat1 */ },
+ { 236, "igrave",/* latin small letter i with grave, U+00EC ISOlat1 */ },
+ { 237, "iacute",/* latin small letter i with acute, U+00ED ISOlat1 */ },
+ { 238, "icirc",/* latin small letter i with circumflex, U+00EE ISOlat1 */ },
+ { 239, "iuml", /* latin small letter i with diaeresis, U+00EF ISOlat1 */ },
+ { 240, "eth", /* latin small letter eth, U+00F0 ISOlat1 */ },
+ { 241, "ntilde",/* latin small letter n with tilde, U+00F1 ISOlat1 */ },
+ { 242, "ograve",/* latin small letter o with grave, U+00F2 ISOlat1 */ },
+ { 243, "oacute",/* latin small letter o with acute, U+00F3 ISOlat1 */ },
+ { 244, "ocirc",/* latin small letter o with circumflex, U+00F4 ISOlat1 */ },
+ { 245, "otilde",/* latin small letter o with tilde, U+00F5 ISOlat1 */ },
+ { 246, "ouml", /* latin small letter o with diaeresis, U+00F6 ISOlat1 */ },
+ { 247, "divide",/* division sign, U+00F7 ISOnum */ },
+ { 248, "oslash",/* latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 */ },
+ { 249, "ugrave",/* latin small letter u with grave, U+00F9 ISOlat1 */ },
+ { 250, "uacute",/* latin small letter u with acute, U+00FA ISOlat1 */ },
+ { 251, "ucirc",/* latin small letter u with circumflex, U+00FB ISOlat1 */ },
+ { 252, "uuml", /* latin small letter u with diaeresis, U+00FC ISOlat1 */ },
+ { 253, "yacute",/* latin small letter y with acute, U+00FD ISOlat1 */ },
+ { 254, "thorn",/* latin small letter thorn with, U+00FE ISOlat1 */ },
+ { 255, "yuml", /* latin small letter y with diaeresis, U+00FF ISOlat1 */ },
+
+/*
+ * Anything below should really be kept as entities references
+ */
+ { 402, "fnof", /* latin small f with hook = function = florin, U+0192 ISOtech */ },
+
+ { 913, "Alpha",/* greek capital letter alpha, U+0391 */ },
+ { 914, "Beta", /* greek capital letter beta, U+0392 */ },
+ { 915, "Gamma",/* greek capital letter gamma, U+0393 ISOgrk3 */ },
+ { 916, "Delta",/* greek capital letter delta, U+0394 ISOgrk3 */ },
+ { 917, "Epsilon",/* greek capital letter epsilon, U+0395 */ },
+ { 918, "Zeta", /* greek capital letter zeta, U+0396 */ },
+ { 919, "Eta", /* greek capital letter eta, U+0397 */ },
+ { 920, "Theta",/* greek capital letter theta, U+0398 ISOgrk3 */ },
+ { 921, "Iota", /* greek capital letter iota, U+0399 */ },
+ { 922, "Kappa",/* greek capital letter kappa, U+039A */ },
+ { 923, "Lambda"/* greek capital letter lambda, U+039B ISOgrk3 */ },
+ { 924, "Mu", /* greek capital letter mu, U+039C */ },
+ { 925, "Nu", /* greek capital letter nu, U+039D */ },
+ { 926, "Xi", /* greek capital letter xi, U+039E ISOgrk3 */ },
+ { 927, "Omicron",/* greek capital letter omicron, U+039F */ },
+ { 928, "Pi", /* greek capital letter pi, U+03A0 ISOgrk3 */ },
+ { 929, "Rho", /* greek capital letter rho, U+03A1 */ },
+ { 931, "Sigma",/* greek capital letter sigma, U+03A3 ISOgrk3 */ },
+ { 932, "Tau", /* greek capital letter tau, U+03A4 */ },
+ { 933, "Upsilon",/* greek capital letter upsilon, U+03A5 ISOgrk3 */ },
+ { 934, "Phi", /* greek capital letter phi, U+03A6 ISOgrk3 */ },
+ { 935, "Chi", /* greek capital letter chi, U+03A7 */ },
+ { 936, "Psi", /* greek capital letter psi, U+03A8 ISOgrk3 */ },
+ { 937, "Omega",/* greek capital letter omega, U+03A9 ISOgrk3 */ },
+
+ { 945, "alpha",/* greek small letter alpha, U+03B1 ISOgrk3 */ },
+ { 946, "beta", /* greek small letter beta, U+03B2 ISOgrk3 */ },
+ { 947, "gamma",/* greek small letter gamma, U+03B3 ISOgrk3 */ },
+ { 948, "delta",/* greek small letter delta, U+03B4 ISOgrk3 */ },
+ { 949, "epsilon",/* greek small letter epsilon, U+03B5 ISOgrk3 */ },
+ { 950, "zeta", /* greek small letter zeta, U+03B6 ISOgrk3 */ },
+ { 951, "eta", /* greek small letter eta, U+03B7 ISOgrk3 */ },
+ { 952, "theta",/* greek small letter theta, U+03B8 ISOgrk3 */ },
+ { 953, "iota", /* greek small letter iota, U+03B9 ISOgrk3 */ },
+ { 954, "kappa",/* greek small letter kappa, U+03BA ISOgrk3 */ },
+ { 955, "lambda",/* greek small letter lambda, U+03BB ISOgrk3 */ },
+ { 956, "mu", /* greek small letter mu, U+03BC ISOgrk3 */ },
+ { 957, "nu", /* greek small letter nu, U+03BD ISOgrk3 */ },
+ { 958, "xi", /* greek small letter xi, U+03BE ISOgrk3 */ },
+ { 959, "omicron",/* greek small letter omicron, U+03BF NEW */ },
+ { 960, "pi", /* greek small letter pi, U+03C0 ISOgrk3 */ },
+ { 961, "rho", /* greek small letter rho, U+03C1 ISOgrk3 */ },
+ { 962, "sigmaf",/* greek small letter final sigma, U+03C2 ISOgrk3 */ },
+ { 963, "sigma",/* greek small letter sigma, U+03C3 ISOgrk3 */ },
+ { 964, "tau", /* greek small letter tau, U+03C4 ISOgrk3 */ },
+ { 965, "upsilon",/* greek small letter upsilon, U+03C5 ISOgrk3 */ },
+ { 966, "phi", /* greek small letter phi, U+03C6 ISOgrk3 */ },
+ { 967, "chi", /* greek small letter chi, U+03C7 ISOgrk3 */ },
+ { 968, "psi", /* greek small letter psi, U+03C8 ISOgrk3 */ },
+ { 969, "omega",/* greek small letter omega, U+03C9 ISOgrk3 */ },
+ { 977, "thetasym",/* greek small letter theta symbol, U+03D1 NEW */ },
+ { 978, "upsih",/* greek upsilon with hook symbol, U+03D2 NEW */ },
+ { 982, "piv", /* greek pi symbol, U+03D6 ISOgrk3 */ },
+
+ { 8226, "bull", /* bullet = black small circle, U+2022 ISOpub */ },
+ { 8230, "hellip",/* horizontal ellipsis = three dot leader, U+2026 ISOpub */ },
+ { 8242, "prime",/* prime = minutes = feet, U+2032 ISOtech */ },
+ { 8243, "Prime",/* double prime = seconds = inches, U+2033 ISOtech */ },
+ { 8254, "oline",/* overline = spacing overscore, U+203E NEW */ },
+ { 8260, "frasl",/* fraction slash, U+2044 NEW */ },
+
+ { 8472, "weierp",/* script capital P = power set = Weierstrass p, U+2118 ISOamso */ },
+ { 8465, "image",/* blackletter capital I = imaginary part, U+2111 ISOamso */ },
+ { 8476, "real", /* blackletter capital R = real part symbol, U+211C ISOamso */ },
+ { 8482, "trade",/* trade mark sign, U+2122 ISOnum */ },
+ { 8501, "alefsym",/* alef symbol = first transfinite cardinal, U+2135 NEW */ },
+ { 8592, "larr", /* leftwards arrow, U+2190 ISOnum */ },
+ { 8593, "uarr", /* upwards arrow, U+2191 ISOnum */ },
+ { 8594, "rarr", /* rightwards arrow, U+2192 ISOnum */ },
+ { 8595, "darr", /* downwards arrow, U+2193 ISOnum */ },
+ { 8596, "harr", /* left right arrow, U+2194 ISOamsa */ },
+ { 8629, "crarr",/* downwards arrow with corner leftwards = carriage return, U+21B5 NEW */ },
+ { 8656, "lArr", /* leftwards double arrow, U+21D0 ISOtech */ },
+ { 8657, "uArr", /* upwards double arrow, U+21D1 ISOamsa */ },
+ { 8658, "rArr", /* rightwards double arrow, U+21D2 ISOtech */ },
+ { 8659, "dArr", /* downwards double arrow, U+21D3 ISOamsa */ },
+ { 8660, "hArr", /* left right double arrow, U+21D4 ISOamsa */ },
+
+
+ { 8704, "forall",/* for all, U+2200 ISOtech */ },
+ { 8706, "part", /* partial differential, U+2202 ISOtech */ },
+ { 8707, "exist",/* there exists, U+2203 ISOtech */ },
+ { 8709, "empty",/* empty set = null set = diameter, U+2205 ISOamso */ },
+ { 8711, "nabla",/* nabla = backward difference, U+2207 ISOtech */ },
+ { 8712, "isin", /* element of, U+2208 ISOtech */ },
+ { 8713, "notin",/* not an element of, U+2209 ISOtech */ },
+ { 8715, "ni", /* contains as member, U+220B ISOtech */ },
+ { 8719, "prod", /* n-ary product = product sign, U+220F ISOamsb */ },
+ { 8721, "sum", /* n-ary sumation, U+2211 ISOamsb */ },
+ { 8722, "minus",/* minus sign, U+2212 ISOtech */ },
+ { 8727, "lowast",/* asterisk operator, U+2217 ISOtech */ },
+ { 8730, "radic",/* square root = radical sign, U+221A ISOtech */ },
+ { 8733, "prop", /* proportional to, U+221D ISOtech */ },
+ { 8734, "infin",/* infinity, U+221E ISOtech */ },
+ { 8736, "ang", /* angle, U+2220 ISOamso */ },
+ { 8743, "and", /* logical and = wedge, U+2227 ISOtech */ },
+ { 8744, "or", /* logical or = vee, U+2228 ISOtech */ },
+ { 8745, "cap", /* intersection = cap, U+2229 ISOtech */ },
+ { 8746, "cup", /* union = cup, U+222A ISOtech */ },
+ { 8747, "int", /* integral, U+222B ISOtech */ },
+ { 8756, "there4",/* therefore, U+2234 ISOtech */ },
+ { 8764, "sim", /* tilde operator = varies with = similar to, U+223C ISOtech */ },
+ { 8773, "cong", /* approximately equal to, U+2245 ISOtech */ },
+ { 8776, "asymp",/* almost equal to = asymptotic to, U+2248 ISOamsr */ },
+ { 8800, "ne", /* not equal to, U+2260 ISOtech */ },
+ { 8801, "equiv",/* identical to, U+2261 ISOtech */ },
+ { 8804, "le", /* less-than or equal to, U+2264 ISOtech */ },
+ { 8805, "ge", /* greater-than or equal to, U+2265 ISOtech */ },
+ { 8834, "sub", /* subset of, U+2282 ISOtech */ },
+ { 8835, "sup", /* superset of, U+2283 ISOtech */ },
+ { 8836, "nsub", /* not a subset of, U+2284 ISOamsn */ },
+ { 8838, "sube", /* subset of or equal to, U+2286 ISOtech */ },
+ { 8839, "supe", /* superset of or equal to, U+2287 ISOtech */ },
+ { 8853, "oplus",/* circled plus = direct sum, U+2295 ISOamsb */ },
+ { 8855, "otimes",/* circled times = vector product, U+2297 ISOamsb */ },
+ { 8869, "perp", /* up tack = orthogonal to = perpendicular, U+22A5 ISOtech */ },
+ { 8901, "sdot", /* dot operator, U+22C5 ISOamsb */ },
+ { 8968, "lceil",/* left ceiling = apl upstile, U+2308 ISOamsc */ },
+ { 8969, "rceil",/* right ceiling, U+2309 ISOamsc */ },
+ { 8970, "lfloor",/* left floor = apl downstile, U+230A ISOamsc */ },
+ { 8971, "rfloor",/* right floor, U+230B ISOamsc */ },
+ { 9001, "lang", /* left-pointing angle bracket = bra, U+2329 ISOtech */ },
+ { 9002, "rang", /* right-pointing angle bracket = ket, U+232A ISOtech */ },
+ { 9674, "loz", /* lozenge, U+25CA ISOpub */ },
+
+ { 9824, "spades",/* black spade suit, U+2660 ISOpub */ },
+ { 9827, "clubs",/* black club suit = shamrock, U+2663 ISOpub */ },
+ { 9829, "hearts",/* black heart suit = valentine, U+2665 ISOpub */ },
+ { 9830, "diams",/* black diamond suit, U+2666 ISOpub */ },
+
+ { 338, "OElig",/* latin capital ligature OE, U+0152 ISOlat2 */ },
+ { 339, "oelig",/* latin small ligature oe, U+0153 ISOlat2 */ },
+ { 352, "Scaron",/* latin capital letter S with caron, U+0160 ISOlat2 */ },
+ { 353, "scaron",/* latin small letter s with caron, U+0161 ISOlat2 */ },
+ { 376, "Yuml", /* latin capital letter Y with diaeresis, U+0178 ISOlat2 */ },
+ { 710, "circ", /* modifier letter circumflex accent, U+02C6 ISOpub */ },
+ { 732, "tilde",/* small tilde, U+02DC ISOdia */ },
+
+ { 8194, "ensp", /* en space, U+2002 ISOpub */ },
+ { 8195, "emsp", /* em space, U+2003 ISOpub */ },
+ { 8201, "thinsp",/* thin space, U+2009 ISOpub */ },
+ { 8204, "zwnj", /* zero width non-joiner, U+200C NEW RFC 2070 */ },
+ { 8205, "zwj", /* zero width joiner, U+200D NEW RFC 2070 */ },
+ { 8206, "lrm", /* left-to-right mark, U+200E NEW RFC 2070 */ },
+ { 8207, "rlm", /* right-to-left mark, U+200F NEW RFC 2070 */ },
+ { 8211, "ndash",/* en dash, U+2013 ISOpub */ },
+ { 8212, "mdash",/* em dash, U+2014 ISOpub */ },
+ { 8216, "lsquo",/* left single quotation mark, U+2018 ISOnum */ },
+ { 8217, "rsquo",/* right single quotation mark, U+2019 ISOnum */ },
+ { 8218, "sbquo",/* single low-9 quotation mark, U+201A NEW */ },
+ { 8220, "ldquo",/* left double quotation mark, U+201C ISOnum */ },
+ { 8221, "rdquo",/* right double quotation mark, U+201D ISOnum */ },
+ { 8222, "bdquo",/* double low-9 quotation mark, U+201E NEW */ },
+ { 8224, "dagger",/* dagger, U+2020 ISOpub */ },
+ { 8225, "Dagger",/* double dagger, U+2021 ISOpub */ },
+ { 8240, "permil",/* per mille sign, U+2030 ISOtech */ },
+ { 8249, "lsaquo",/* single left-pointing angle quotation mark, U+2039 ISO proposed */ },
+ { 8250, "rsaquo",/* single right-pointing angle quotation mark, U+203A ISO proposed */ },
+ { 8364, "euro", /* euro sign, U+20AC NEW */ }
+};
+
+static GHashTable *entities;
+
+/* this cannot be called in a thread context */
+static void tokenise_setup(void)
+{
+ int i;
+
+ if (entities == NULL) {
+ entities = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<sizeof(entity_map)/sizeof(entity_map[0]);i++) {
+ g_hash_table_insert(entities, (char *)entity_map[i].name, GUINT_TO_POINTER(entity_map[i].val));
+ }
+ }
+}
+
+static CamelHTMLParserPrivate *tokenise_init(void)
+{
+ CamelHTMLParserPrivate *p;
+
+ p = g_malloc(sizeof(*p));
+ p->state = CAMEL_HTML_PARSER_DATA;
+
+ p->attr = 0;
+ p->attrs = g_ptr_array_new();
+ p->values = g_ptr_array_new();
+ p->tag = g_string_new("");
+ p->ent = g_string_new("");
+ p->charset = NULL;
+
+ if (entities == NULL)
+ tokenise_setup();
+
+ return p;
+}
+
+static void tokenise_free(CamelHTMLParserPrivate *p)
+{
+ int i;
+
+ g_string_free(p->tag, TRUE);
+ g_string_free(p->ent, TRUE);
+ g_free(p->charset);
+
+ for (i=0;i<p->attrs->len;i++)
+ g_string_free(p->attrs->pdata[i], TRUE);
+
+ for (i=0;i<p->values->len;i++)
+ g_string_free(p->values->pdata[i], TRUE);
+
+ g_free(p);
+}
+
+static int convert_entity(const char *e, char *ent)
+{
+ unsigned int val;
+
+ if (e[0] == '#')
+ return g_unichar_to_utf8(atoi(e+1), ent);
+
+ val = GPOINTER_TO_UINT(g_hash_table_lookup(entities, e));
+ if (ent)
+ return g_unichar_to_utf8(val, ent);
+ else
+ return 0;
+}
+
+#if 0
+static void dump_tag(CamelHTMLParserPrivate *p)
+{
+ int i;
+
+ printf("got tag: %s\n", p->tag->str);
+ printf("%d attributes:\n", p->attr);
+ for (i=0;i<p->attr;i++) {
+ printf(" %s = '%s'\n", ((GString *)p->attrs->pdata[i])->str, ((GString *)p->values->pdata[i])->str);
+ }
+}
+#endif
+
+static int tokenise_step(CamelHTMLParserPrivate *p, char **datap, int *lenp)
+{
+ char *in = p->inptr;
+ char *inend = p->inend;
+ char c;
+ int state = p->state, ret, len;
+ char *start = p->inptr;
+
+ d(printf("Tokenise step\n"));
+
+ while (in < inend) {
+ c = *in++;
+ switch (state) {
+ case CAMEL_HTML_PARSER_DATA:
+ if (c == '<') {
+ ret = state;
+ state = CAMEL_HTML_PARSER_TAG;
+ p->attr = 0;
+ g_string_truncate(p->tag, 0);
+ d(printf("got data '%.*s'\n", in-start-1, start));
+ *datap = start;
+ *lenp = in-start-1;
+ goto done;
+ } else if (c=='&') {
+ ret = state;
+ state = CAMEL_HTML_PARSER_ENT;
+ g_string_truncate(p->ent, 0);
+ g_string_append_c(p->ent, c);
+ d(printf("got data '%.*s'\n", in-start-1, start));
+ *datap = start;
+ *lenp = in-start-1;
+ goto done;
+ }
+ break;
+ case CAMEL_HTML_PARSER_ENT:
+ if (c==';') {
+ len = convert_entity(p->ent->str+1, p->ent_utf8);
+ if (len == 0) {
+ /* handle broken entity */
+ g_string_append_c(p->ent, c);
+ ret = state = CAMEL_HTML_PARSER_DATA;
+ *datap = p->ent->str;
+ *lenp = p->ent->len;
+ goto done;
+ } else {
+ d(printf("got entity: %s = %s\n", p->ent->str, p->ent_utf8));
+ ret = state;
+ state = CAMEL_HTML_PARSER_DATA;
+ *datap = p->ent_utf8;
+ *lenp = len;
+ goto done;
+ }
+ } else if (isalnum(c) || c=='#') { /* FIXME: right type */
+ g_string_append_c(p->ent, c);
+ } else {
+ /* handle broken entity */
+ g_string_append_c(p->ent, c);
+ ret = state = CAMEL_HTML_PARSER_DATA;
+ *datap = p->ent->str;
+ *lenp = p->ent->len;
+ goto done;
+ }
+ break;
+ case CAMEL_HTML_PARSER_TAG:
+ if (c == '!') {
+ state = CAMEL_HTML_PARSER_COMMENT0;
+ g_string_append_c(p->tag, c);
+ } else if (c == '>') {
+ d(dump_tag(p));
+ ret = CAMEL_HTML_PARSER_ELEMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ goto done;
+ } else if (c == ' ' || c=='\n' || c=='\t') {
+ state = CAMEL_HTML_PARSER_ATTR0;
+ } else {
+ g_string_append_c(p->tag, c);
+ }
+ break;
+ /* check for <!-- */
+ case CAMEL_HTML_PARSER_COMMENT0:
+ if (c == '-') {
+ g_string_append_c(p->tag, c);
+ if (p->tag->len == 3) {
+ g_string_truncate(p->tag, 0);
+ state = CAMEL_HTML_PARSER_COMMENT;
+ }
+ } else {
+ /* got something else, probbly dtd entity */
+ state = CAMEL_HTML_PARSER_DTDENT;
+ }
+ break;
+ case CAMEL_HTML_PARSER_DTDENT:
+ if (c == '>') {
+ ret = CAMEL_HTML_PARSER_DTDENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ *datap = start;
+ *lenp = in-start-1;
+ goto done;
+ }
+ break;
+ case CAMEL_HTML_PARSER_COMMENT:
+ if (c == '>' && p->tag->len == 2) {
+ ret = CAMEL_HTML_PARSER_COMMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ *datap = start;
+ *lenp = in-start-1;
+ goto done;
+ } else if (c=='-') {
+ /* we dont care if we get 'n' --'s before the > */
+ if (p->tag->len < 2)
+ g_string_append_c(p->tag, c);
+ } else {
+ g_string_truncate(p->tag, 0);
+ }
+ break;
+ case CAMEL_HTML_PARSER_ATTR0: /* pre-attribute whitespace */
+ if (c == '>') {
+ d(dump_tag(p));
+ ret = CAMEL_HTML_PARSER_ELEMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ goto done;
+ } else if (c == ' ' || c=='\n' || c=='\t') {
+ } else {
+ if (p->attrs->len <= p->attr) {
+ g_ptr_array_add(p->attrs, g_string_new(""));
+ g_ptr_array_add(p->values, g_string_new(""));
+ } else {
+ g_string_truncate(p->attrs->pdata[p->attr], 0);
+ g_string_truncate(p->values->pdata[p->attr], 0);
+ }
+ g_string_append_c(p->attrs->pdata[p->attr], c);
+ state = CAMEL_HTML_PARSER_ATTR;
+ }
+ break;
+ case CAMEL_HTML_PARSER_ATTR:
+ if (c == '>') {
+ d(dump_tag(p));
+ ret = CAMEL_HTML_PARSER_ELEMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ goto done;
+ } else if (c == '=') {
+ state = CAMEL_HTML_PARSER_VAL0;
+ } else if (c == ' ' || c=='\n' || c=='\t') {
+ state = CAMEL_HTML_PARSER_ATTR0;
+ p->attr++;
+ } else {
+ g_string_append_c(p->attrs->pdata[p->attr], c);
+ }
+ break;
+ case CAMEL_HTML_PARSER_VAL0:
+ if (c == '>') {
+ d(printf("value truncated\n"));
+ d(dump_tag(p));
+ ret = CAMEL_HTML_PARSER_ELEMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ goto done;
+ } else if (c == '\'' || c == '\"') {
+ p->quote = c;
+ state = CAMEL_HTML_PARSER_VAL;
+ } else if (c == ' ' || c=='\n' || c=='\t') {
+ } else {
+ g_string_append_c(p->values->pdata[p->attr], c);
+ p->quote = 0;
+ state = CAMEL_HTML_PARSER_VAL;
+ }
+ break;
+ case CAMEL_HTML_PARSER_VAL:
+ do_val:
+ if (p->quote) {
+ if (c == '>') {
+ d(printf("value truncated\n"));
+ d(dump_tag(p));
+ ret = CAMEL_HTML_PARSER_ELEMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ p->attr++;
+ goto done;
+ } else if (c == p->quote) {
+ state = CAMEL_HTML_PARSER_ATTR0;
+ p->attr++;
+ } else if (c=='&') {
+ state = CAMEL_HTML_PARSER_VAL_ENT;
+ g_string_truncate(p->ent, 0);
+ } else {
+ g_string_append_c(p->values->pdata[p->attr], c);
+ }
+ } else if (c == '>') {
+ d(dump_tag(p));
+ ret = CAMEL_HTML_PARSER_ELEMENT;
+ state = CAMEL_HTML_PARSER_DATA;
+ p->attr++;
+ goto done;
+ } else if (c == ' ' || c=='\n' || c=='\t') {
+ state = CAMEL_HTML_PARSER_ATTR0;
+ p->attr++;
+ } else if (c=='&') {
+ state = CAMEL_HTML_PARSER_VAL_ENT;
+ g_string_truncate(p->ent, 0);
+ } else {
+ g_string_append_c(p->values->pdata[p->attr], c);
+ }
+ break;
+ case CAMEL_HTML_PARSER_VAL_ENT:
+ if (c==';') {
+ state = CAMEL_HTML_PARSER_VAL;
+ len = convert_entity(p->ent->str+1, p->ent_utf8);
+ if (len == 0) {
+ /* fallback; broken entity, just output it and see why we ended */
+ g_string_append(p->values->pdata[p->attr], p->ent->str);
+ g_string_append_c(p->values->pdata[p->attr], ';');
+ } else {
+ d(printf("got entity: %s = %s\n", p->ent->str, p->ent_utf8));
+ g_string_append(p->values->pdata[p->attr], p->ent_utf8);
+ }
+ } else if (isalnum(c) || c=='#') { /* FIXME: right type */
+ g_string_append_c(p->ent, c);
+ } else {
+ /* fallback; broken entity, just output it and see why we ended */
+ g_string_append(p->values->pdata[p->attr], p->ent->str);
+ goto do_val;
+ }
+ break;
+ }
+ }
+
+ if (p->eof) {
+ /* FIXME: what about other truncated states? */
+ switch (state) {
+ case CAMEL_HTML_PARSER_DATA:
+ case CAMEL_HTML_PARSER_COMMENT:
+ if (in > start) {
+ ret = state;
+ *datap = start;
+ *lenp = in-start-1;
+ } else {
+ ret = CAMEL_HTML_PARSER_EOF;
+ state = CAMEL_HTML_PARSER_EOF;
+ }
+ break;
+ default:
+ ret = CAMEL_HTML_PARSER_EOF;
+ state = CAMEL_HTML_PARSER_EOF;
+ }
+ } else {
+ /* we only care about remaining data for this buffer, everything else has its own copy */
+ switch (state) {
+ case CAMEL_HTML_PARSER_DATA:
+ case CAMEL_HTML_PARSER_COMMENT:
+ if (in > start) {
+ ret = state;
+ *datap = start;
+ *lenp = in-start-1;
+ } else {
+ ret = CAMEL_HTML_PARSER_EOD;
+ }
+ break;
+ default:
+ ret = CAMEL_HTML_PARSER_EOD;
+ }
+ }
+
+done:
+ p->start = start;
+ p->state = state;
+ p->inptr = in;
+
+ return ret;
+}
diff --git a/camel/camel-http-stream.c b/camel/camel-http-stream.c
new file mode 100644
index 0000000000..53c0241a2b
--- /dev/null
+++ b/camel/camel-http-stream.c
@@ -0,0 +1,612 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "camel-http-stream.h"
+
+#include "camel-mime-utils.h"
+#include "camel-stream-buffer.h"
+#include "camel-tcp-stream-raw.h"
+#ifdef HAVE_SSL
+#include "camel-tcp-stream-ssl.h"
+#endif
+#include "camel-exception.h"
+#include "camel-session.h"
+#include "camel-service.h" /* for hostname stuff */
+
+#define d(x)
+
+static CamelStreamClass *parent_class = NULL;
+
+static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
+static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
+static int stream_flush (CamelStream *stream);
+static int stream_close (CamelStream *stream);
+static int stream_reset (CamelStream *stream);
+
+static void
+camel_http_stream_class_init (CamelHttpStreamClass *camel_http_stream_class)
+{
+ CamelStreamClass *camel_stream_class =
+ CAMEL_STREAM_CLASS (camel_http_stream_class);
+
+ parent_class = CAMEL_STREAM_CLASS (camel_type_get_global_classfuncs (camel_stream_get_type ()));
+
+ /* virtual method overload */
+ camel_stream_class->read = stream_read;
+ camel_stream_class->write = stream_write;
+ camel_stream_class->flush = stream_flush;
+ camel_stream_class->close = stream_close;
+ camel_stream_class->reset = stream_reset;
+}
+
+static void
+camel_http_stream_init (gpointer object, gpointer klass)
+{
+ CamelHttpStream *http = CAMEL_HTTP_STREAM (object);
+
+ http->parser = NULL;
+ http->content_type = NULL;
+ http->headers = NULL;
+ http->session = NULL;
+ http->url = NULL;
+ http->proxy = NULL;
+ http->authrealm = NULL;
+ http->authpass = NULL;
+ http->statuscode = 0;
+ http->raw = NULL;
+}
+
+static void
+camel_http_stream_finalize (CamelObject *object)
+{
+ CamelHttpStream *http = CAMEL_HTTP_STREAM (object);
+
+ if (http->parser)
+ camel_object_unref(http->parser);
+
+ if (http->content_type)
+ camel_content_type_unref (http->content_type);
+
+ if (http->headers)
+ camel_header_raw_clear (&http->headers);
+
+ if (http->session)
+ camel_object_unref(http->session);
+
+ if (http->url)
+ camel_url_free (http->url);
+
+ if (http->proxy)
+ camel_url_free (http->proxy);
+
+ g_free (http->authrealm);
+ g_free (http->authpass);
+
+ if (http->raw)
+ camel_object_unref(http->raw);
+ if (http->read)
+ camel_object_unref(http->read);
+}
+
+CamelType
+camel_http_stream_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_stream_get_type (),
+ "CamelHttpStream",
+ sizeof (CamelHttpStream),
+ sizeof (CamelHttpStreamClass),
+ (CamelObjectClassInitFunc) camel_http_stream_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_http_stream_init,
+ (CamelObjectFinalizeFunc) camel_http_stream_finalize);
+ }
+
+ return type;
+}
+
+/**
+ * camel_http_stream_new:
+ * @method: HTTP method
+ * @session: active session
+ * @url: URL to act upon
+ *
+ * Return value: a http stream
+ **/
+CamelStream *
+camel_http_stream_new (CamelHttpMethod method, struct _CamelSession *session, CamelURL *url)
+{
+ CamelHttpStream *stream;
+ char *str;
+
+ g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
+ g_return_val_if_fail(url != NULL, NULL);
+
+ stream = CAMEL_HTTP_STREAM (camel_object_new (camel_http_stream_get_type ()));
+
+ stream->method = method;
+ stream->session = session;
+ camel_object_ref(session);
+
+ str = camel_url_to_string (url, 0);
+ stream->url = camel_url_new (str, NULL);
+ g_free (str);
+
+ return (CamelStream *)stream;
+}
+
+#define SSL_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+
+static CamelStream *
+http_connect (CamelHttpStream *http, CamelURL *url)
+{
+ CamelStream *stream = NULL;
+ struct addrinfo *ai, hints = { 0 };
+ int errsave;
+ char *serv;
+
+ d(printf("connecting to http stream @ '%s'\n", url->host));
+
+ if (!strcasecmp (url->protocol, "https")) {
+#ifdef HAVE_SSL
+ stream = camel_tcp_stream_ssl_new (http->session, url->host, SSL_FLAGS);
+#endif
+ } else {
+ stream = camel_tcp_stream_raw_new ();
+ }
+
+ if (stream == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", url->port);
+ } else {
+ serv = url->protocol;
+ }
+ hints.ai_socktype = SOCK_STREAM;
+
+ ai = camel_getaddrinfo(url->host, serv, &hints, NULL);
+ if (ai == NULL) {
+ camel_object_unref (stream);
+ return NULL;
+ }
+
+ if (camel_tcp_stream_connect (CAMEL_TCP_STREAM (stream), ai) == -1) {
+ errsave = errno;
+ camel_object_unref (stream);
+ camel_freeaddrinfo(ai);
+ errno = errsave;
+ return NULL;
+ }
+
+ camel_freeaddrinfo(ai);
+
+ http->raw = stream;
+ http->read = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
+
+ return stream;
+}
+
+static void
+http_disconnect(CamelHttpStream *http)
+{
+ if (http->raw) {
+ camel_object_unref(http->raw);
+ http->raw = NULL;
+ }
+
+ if (http->read) {
+ camel_object_unref(http->read);
+ http->read = NULL;
+ }
+
+ if (http->parser) {
+ camel_object_unref(http->parser);
+ http->parser = NULL;
+ }
+}
+
+static const char *
+http_next_token (const unsigned char *in)
+{
+ const unsigned char *inptr = in;
+
+ while (*inptr && !isspace ((int) *inptr))
+ inptr++;
+
+ while (*inptr && isspace ((int) *inptr))
+ inptr++;
+
+ return (const char *) inptr;
+}
+
+static int
+http_get_statuscode (CamelHttpStream *http)
+{
+ const char *token;
+ char buffer[4096];
+
+ if (camel_stream_buffer_gets ((CamelStreamBuffer *)http->read, buffer, sizeof (buffer)) <= 0)
+ return -1;
+
+ d(printf("HTTP Status: %s\n", buffer));
+
+ /* parse the HTTP status code */
+ if (!strncasecmp (buffer, "HTTP/", 5)) {
+ token = http_next_token (buffer);
+ http->statuscode = camel_header_decode_int (&token);
+ return http->statuscode;
+ }
+
+ http_disconnect(http);
+
+ return -1;
+}
+
+static int
+http_get_headers (CamelHttpStream *http)
+{
+ struct _camel_header_raw *headers, *node, *tail;
+ const char *type;
+ char *buf;
+ size_t len;
+ int err;
+
+ if (http->parser)
+ camel_object_unref (http->parser);
+
+ http->parser = camel_mime_parser_new ();
+ camel_mime_parser_init_with_stream (http->parser, http->read);
+
+ switch (camel_mime_parser_step (http->parser, &buf, &len)) {
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ headers = camel_mime_parser_headers_raw (http->parser);
+ if (http->content_type)
+ camel_content_type_unref (http->content_type);
+ type = camel_header_raw_find (&headers, "Content-Type", NULL);
+ if (type)
+ http->content_type = camel_content_type_decode (type);
+ else
+ http->content_type = NULL;
+
+ if (http->headers)
+ camel_header_raw_clear (&http->headers);
+
+ http->headers = NULL;
+ tail = (struct _camel_header_raw *) &http->headers;
+
+ d(printf("HTTP Headers:\n"));
+ while (headers) {
+ d(printf(" %s:%s\n", headers->name, headers->value));
+ node = g_new (struct _camel_header_raw, 1);
+ node->next = NULL;
+ node->name = g_strdup (headers->name);
+ node->value = g_strdup (headers->value);
+ node->offset = headers->offset;
+ tail->next = node;
+ tail = node;
+ headers = headers->next;
+ }
+
+ break;
+ default:
+ g_warning ("Invalid state encountered???: %d", camel_mime_parser_state (http->parser));
+ }
+
+ err = camel_mime_parser_errno (http->parser);
+
+ if (err != 0) {
+ camel_object_unref (http->parser);
+ http->parser = NULL;
+ goto exception;
+ }
+
+ camel_mime_parser_drop_step (http->parser);
+
+ return 0;
+
+ exception:
+ http_disconnect(http);
+
+ return -1;
+}
+
+static int
+http_method_invoke (CamelHttpStream *http)
+{
+ const char *method = NULL;
+ char *url;
+
+ switch (http->method) {
+ case CAMEL_HTTP_METHOD_GET:
+ method = "GET";
+ break;
+ case CAMEL_HTTP_METHOD_HEAD:
+ method = "HEAD";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ url = camel_url_to_string (http->url, 0);
+ d(printf("HTTP Stream Sending: %s %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\n",
+ method,
+ http->proxy ? url : http->url->path,
+ http->user_agent ? http->user_agent : "CamelHttpStream/1.0",
+ http->url->host));
+ if (camel_stream_printf (http->raw, "%s %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\n",
+ method,
+ http->proxy ? url : http->url->path,
+ http->user_agent ? http->user_agent : "CamelHttpStream/1.0",
+ http->url->host) == -1) {
+ http_disconnect(http);
+ g_free (url);
+ return -1;
+ }
+ g_free (url);
+
+ if (http->authrealm)
+ d(printf("HTTP Stream Sending: WWW-Authenticate: %s\n", http->authrealm));
+
+ if (http->authrealm && camel_stream_printf (http->raw, "WWW-Authenticate: %s\r\n", http->authrealm) == -1) {
+ http_disconnect(http);
+ return -1;
+ }
+
+ if (http->authpass && http->proxy)
+ d(printf("HTTP Stream Sending: Proxy-Aurhorization: Basic %s\n", http->authpass));
+
+ if (http->authpass && http->proxy && camel_stream_printf (http->raw, "Proxy-Authorization: Basic %s\r\n",
+ http->authpass) == -1) {
+ http_disconnect(http);
+ return -1;
+ }
+
+ /* end the headers */
+ if (camel_stream_write (http->raw, "\r\n", 2) == -1 || camel_stream_flush (http->raw) == -1) {
+ http_disconnect(http);
+ return -1;
+ }
+
+ return 0;
+}
+
+static ssize_t
+stream_read (CamelStream *stream, char *buffer, size_t n)
+{
+ CamelHttpStream *http = CAMEL_HTTP_STREAM (stream);
+ const char *parser_buf;
+ ssize_t nread;
+
+ if (http->method != CAMEL_HTTP_METHOD_GET && http->method != CAMEL_HTTP_METHOD_HEAD) {
+ errno = EIO;
+ return -1;
+ }
+
+ redirect:
+
+ if (!http->raw) {
+ if (http_connect (http, http->proxy ? http->proxy : http->url) == NULL)
+ return -1;
+
+ if (http_method_invoke (http) == -1)
+ return -1;
+
+ if (http_get_statuscode (http) == -1)
+ return -1;
+
+ if (http_get_headers (http) == -1)
+ return -1;
+
+ switch (http->statuscode) {
+ case 200:
+ case 206:
+ /* we are OK to go... */
+ break;
+ case 301:
+ case 302: {
+ char *loc;
+ CamelURL *url;
+
+ camel_content_type_unref (http->content_type);
+ http->content_type = NULL;
+ http_disconnect(http);
+
+ loc = g_strdup(camel_header_raw_find(&http->headers, "Location", NULL));
+ if (loc == NULL) {
+ camel_header_raw_clear(&http->headers);
+ return -1;
+ }
+
+ /* redirect... */
+ g_strstrip(loc);
+ d(printf("HTTP redirect, location = %s\n", loc));
+ url = camel_url_new_with_base(http->url, loc);
+ camel_url_free (http->url);
+ http->url = url;
+ if (url == NULL)
+ http->url = camel_url_new(loc, NULL);
+ g_free(loc);
+ if (http->url == NULL) {
+ camel_header_raw_clear (&http->headers);
+ return -1;
+ }
+ d(printf(" redirect url = %p\n", http->url));
+ camel_header_raw_clear (&http->headers);
+
+ goto redirect;
+ break; }
+ case 407:
+ /* failed proxy authentication? */
+ default:
+ /* unknown error */
+ http_disconnect(http);
+ return -1;
+ }
+ }
+
+ nread = camel_mime_parser_read (http->parser, &parser_buf, n);
+
+ if (nread > 0)
+ memcpy (buffer, parser_buf, nread);
+ else if (nread == 0)
+ stream->eos = TRUE;
+
+ return nread;
+}
+
+static ssize_t
+stream_write (CamelStream *stream, const char *buffer, size_t n)
+{
+ return -1;
+}
+
+static int
+stream_flush (CamelStream *stream)
+{
+ CamelHttpStream *http = (CamelHttpStream *) stream;
+
+ if (http->raw)
+ return camel_stream_flush (http->raw);
+ else
+ return 0;
+}
+
+static int
+stream_close (CamelStream *stream)
+{
+ CamelHttpStream *http = (CamelHttpStream *) stream;
+
+ if (http->raw) {
+ if (camel_stream_close (http->raw) == -1)
+ return -1;
+
+ http_disconnect(http);
+ }
+
+ return 0;
+}
+
+static int
+stream_reset (CamelStream *stream)
+{
+ CamelHttpStream *http = CAMEL_HTTP_STREAM (stream);
+
+ if (http->raw)
+ http_disconnect(http);
+
+ return 0;
+};
+
+CamelContentType *
+camel_http_stream_get_content_type (CamelHttpStream *http_stream)
+{
+ g_return_val_if_fail (CAMEL_IS_HTTP_STREAM (http_stream), NULL);
+
+ if (!http_stream->content_type && !http_stream->raw) {
+ if (http_connect (http_stream, http_stream->url) == NULL)
+ return NULL;
+
+ if (http_method_invoke (http_stream) == -1)
+ return NULL;
+
+ if (http_get_headers (http_stream) == -1)
+ return NULL;
+ }
+
+ if (http_stream->content_type)
+ camel_content_type_ref (http_stream->content_type);
+
+ return http_stream->content_type;
+}
+
+void
+camel_http_stream_set_user_agent (CamelHttpStream *http_stream, const char *user_agent)
+{
+ g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
+
+ g_free (http_stream->user_agent);
+ http_stream->user_agent = g_strdup (user_agent);
+}
+
+void
+camel_http_stream_set_proxy (CamelHttpStream *http_stream, const char *proxy_url)
+{
+ g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
+
+ if (http_stream->proxy)
+ camel_url_free (http_stream->proxy);
+
+ if (proxy_url == NULL)
+ http_stream->proxy = NULL;
+ else
+ http_stream->proxy = camel_url_new (proxy_url, NULL);
+
+ if (http_stream->proxy) {
+ char *basic, *basic64;
+
+ basic = g_strdup_printf("%s:%s", http_stream->proxy->user?http_stream->proxy->user:"",
+ http_stream->proxy->passwd?http_stream->proxy->passwd:"");
+ basic64 = camel_base64_encode_simple(basic, strlen(basic));
+ memset(basic, 0, strlen(basic));
+ g_free(basic);
+ camel_http_stream_set_proxy_authpass(http_stream, basic64);
+ memset(basic64, 0, strlen(basic64));
+ g_free(basic64);
+ } else {
+ camel_http_stream_set_proxy_authpass(http_stream, NULL);
+ }
+}
+
+void
+camel_http_stream_set_proxy_authrealm (CamelHttpStream *http_stream, const char *proxy_authrealm)
+{
+ g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
+
+ g_free (http_stream->authrealm);
+ http_stream->authrealm = g_strdup (proxy_authrealm);
+}
+
+void
+camel_http_stream_set_proxy_authpass (CamelHttpStream *http_stream, const char *proxy_authpass)
+{
+ g_return_if_fail (CAMEL_IS_HTTP_STREAM (http_stream));
+
+ g_free (http_stream->authpass);
+ http_stream->authpass = g_strdup (proxy_authpass);
+}
diff --git a/camel/camel-lock-helper.c b/camel/camel-lock-helper.c
new file mode 100644
index 0000000000..9c97c83565
--- /dev/null
+++ b/camel/camel-lock-helper.c
@@ -0,0 +1,391 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2001 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/* lock helper process */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <errno.h>
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <utime.h>
+
+#include <time.h>
+
+#include <string.h>
+
+#define SETEUID_SAVES (1)
+
+/* we try and include as little as possible */
+
+#include "camel-lock-helper.h"
+#include "camel-lock.h"
+
+#define d(x)
+
+/* keeps track of open locks */
+struct _lock_info {
+ struct _lock_info *next;
+ uid_t uid;
+ int id;
+ int depth;
+ time_t stamp; /* when last updated */
+ char path[1];
+};
+
+static int lock_id = 0;
+static struct _lock_info *lock_info_list;
+static uid_t lock_root_uid = -1;
+static uid_t lock_real_uid = -1;
+
+/* utility functions */
+
+static int read_n(int fd, void *buffer, int inlen)
+{
+ char *p = buffer;
+ int len, left = inlen;
+
+ do {
+ len = read(fd, p, left);
+ if (len == -1) {
+ if (errno != EINTR)
+ return -1;
+ } else {
+ left -= len;
+ p += len;
+ }
+ } while (left > 0 && len != 0);
+
+ return inlen - left;
+}
+
+static int write_n(int fd, void *buffer, int inlen)
+{
+ char *p = buffer;
+ int len, left = inlen;
+
+ do {
+ len = write(fd, p, left);
+ if (len == -1) {
+ if (errno != EINTR)
+ return -1;
+ } else {
+ left -= len;
+ p += len;
+ }
+ } while (left > 0);
+
+ return inlen;
+}
+
+void
+camel_exception_setv (CamelException *ex, ExceptionId id, const char *format, ...)
+{
+ ;
+}
+
+void
+camel_exception_clear (CamelException *exception)
+{
+ ;
+}
+
+char *gettext (const char *msgid);
+
+char *
+gettext (const char *msgid)
+{
+ return NULL;
+}
+
+static int lock_path(const char *path, guint32 *lockid)
+{
+ struct _lock_info *info = NULL;
+ int res = CAMEL_LOCK_HELPER_STATUS_OK;
+ struct stat st;
+
+ d(fprintf(stderr, "locking path '%s' id = %d\n", path, lock_id));
+
+ /* check to see if we have it locked already, make the lock 'recursive' */
+ /* we could also error i suppose, but why bother */
+ info = lock_info_list;
+ while (info) {
+ if (!strcmp(info->path, path)) {
+ info->depth++;
+ return CAMEL_LOCK_HELPER_STATUS_OK;
+ }
+ info = info->next;
+ }
+
+ /* check we are allowed to lock it, we must own it, be able to write to it, and it has to exist */
+ if (stat(path, &st) == -1
+ || st.st_uid != getuid()
+ || !S_ISREG(st.st_mode)
+ || (st.st_mode & 0400) == 0) {
+ return CAMEL_LOCK_HELPER_STATUS_INVALID;
+ }
+
+ info = malloc(sizeof(*info) + strlen(path));
+ if (info == NULL) {
+ res = CAMEL_LOCK_HELPER_STATUS_NOMEM;
+ goto fail;
+ }
+
+ /* we try the real uid first, and if that fails, try the 'root id' */
+ if (camel_lock_dot(path, NULL) == -1) {
+#ifdef SETEUID_SAVES
+ if (lock_real_uid != lock_root_uid) {
+ if (seteuid(lock_root_uid) != -1) {
+ if (camel_lock_dot(path, NULL) == -1) {
+ seteuid(lock_real_uid);
+ res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
+ goto fail;
+ }
+ seteuid(lock_real_uid);
+ } else {
+ res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
+ goto fail;
+ }
+ } else {
+ res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
+ goto fail;
+ }
+#else
+ res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
+ goto fail;
+#endif
+ } else {
+ info->uid = lock_real_uid;
+ }
+
+ strcpy(info->path, path);
+ info->id = lock_id;
+ info->depth = 1;
+ info->next = lock_info_list;
+ info->stamp = time(0);
+ lock_info_list = info;
+
+ if (lockid)
+ *lockid = lock_id;
+
+ lock_id++;
+
+ d(fprintf(stderr, "lock ok\n"));
+
+ return res;
+fail:
+ d(fprintf(stderr, "lock failed\n"));
+
+ if (info)
+ free(info);
+
+ return res;
+}
+
+static int unlock_id(guint32 lockid)
+{
+ struct _lock_info *info, *p;
+
+ d(fprintf(stderr, "unlocking id '%d'\n", lockid));
+
+ p = (struct _lock_info *)&lock_info_list;
+ info = p->next;
+ while (info) {
+ if (info->id == lockid) {
+ d(fprintf(stderr, "found id %d path '%s'\n", lockid, info->path));
+ info->depth--;
+ if (info->depth <= 0) {
+#ifdef SETEUID_SAVES
+ if (info->uid != lock_real_uid) {
+ seteuid(lock_root_uid);
+ camel_unlock_dot(info->path);
+ seteuid(lock_real_uid);
+ } else
+#endif
+ camel_unlock_dot(info->path);
+
+ p->next = info->next;
+ free(info);
+ }
+
+ return CAMEL_LOCK_HELPER_STATUS_OK;
+ }
+ p = info;
+ info = info->next;
+ }
+
+ d(fprintf(stderr, "unknown id asked to be unlocked %d\n", lockid));
+ return CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+}
+
+static void lock_touch(const char *path)
+{
+ char *name;
+
+ /* we could also check that we haven't had our lock stolen from us here */
+
+ name = alloca(strlen(path) + 10);
+ sprintf(name, "%s.lock", path);
+
+ d(fprintf(stderr, "Updating lock %s\n", name));
+ utime(name, NULL);
+}
+
+static void setup_process(void)
+{
+ struct sigaction sa;
+ sigset_t sigset;
+
+ /* ignore sigint/sigio */
+ sa.sa_handler = SIG_IGN;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGIO);
+ sigaddset(&sigset, SIGINT);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+
+ sigaction (SIGIO, &sa, NULL);
+ sigaction (SIGINT, &sa, NULL);
+
+ /* FIXME: add more sanity checks/setup here */
+
+#ifdef SETEUID_SAVES
+ /* here we change to the real user id, this is probably not particularly
+ portable so may need configure checks */
+ lock_real_uid = getuid();
+ lock_root_uid = geteuid();
+ if (lock_real_uid != lock_root_uid)
+ seteuid(lock_real_uid);
+#endif
+}
+
+int main(int argc, char **argv)
+{
+ struct _CamelLockHelperMsg msg;
+ int len;
+ int res;
+ char *path;
+ fd_set rset;
+ struct timeval tv;
+ struct _lock_info *info;
+
+ setup_process();
+
+ do {
+ /* do a poll/etc, so we can refresh the .locks as required ... */
+ FD_ZERO(&rset);
+ FD_SET(STDIN_FILENO, &rset);
+
+ /* check the minimum timeout we need to refresh the next oldest lock */
+ if (lock_info_list) {
+ time_t now = time(0);
+ time_t left;
+ time_t delay = CAMEL_DOT_LOCK_REFRESH;
+
+ info = lock_info_list;
+ while (info) {
+ left = CAMEL_DOT_LOCK_REFRESH - (now - info->stamp);
+ left = MAX(left, 0);
+ delay = MIN(left, delay);
+ info = info->next;
+ }
+
+ tv.tv_sec = delay;
+ tv.tv_usec = 0;
+ }
+
+ d(fprintf(stderr, "lock helper waiting for input\n"));
+ if (select(STDIN_FILENO+1, &rset, NULL, NULL, lock_info_list?&tv:NULL) == -1) {
+ if (errno == EINTR)
+ break;
+
+ continue;
+ }
+
+ /* did we get a timeout? scan for any locks that need updating */
+ if (!FD_ISSET(STDIN_FILENO, &rset)) {
+ time_t now = time(0);
+ time_t left;
+
+ d(fprintf(stderr, "Got a timeout, checking locks\n"));
+
+ info = lock_info_list;
+ while (info) {
+ left = (now - info->stamp);
+ if (left >= CAMEL_DOT_LOCK_REFRESH) {
+ lock_touch(info->path);
+ info->stamp = now;
+ }
+ info = info->next;
+ }
+
+ continue;
+ }
+
+
+ len = read_n(STDIN_FILENO, &msg, sizeof(msg));
+ if (len == 0)
+ break;
+
+ res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+ if (len == sizeof(msg) && msg.magic == CAMEL_LOCK_HELPER_MAGIC) {
+ switch(msg.id) {
+ case CAMEL_LOCK_HELPER_LOCK:
+ res = CAMEL_LOCK_HELPER_STATUS_NOMEM;
+ if (msg.data > 0xffff) {
+ res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+ } else if ((path = malloc(msg.data+1)) != NULL) {
+ res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
+ len = read_n(STDIN_FILENO, path, msg.data);
+ if (len == msg.data) {
+ path[len] = 0;
+ res = lock_path(path, &msg.data);
+ }
+ free(path);
+ }
+ break;
+ case CAMEL_LOCK_HELPER_UNLOCK:
+ res = unlock_id(msg.data);
+ break;
+ }
+ }
+ d(fprintf(stderr, "returning result %d\n", res));
+ msg.id = res;
+ msg.magic = CAMEL_LOCK_HELPER_RETURN_MAGIC;
+ write_n(STDOUT_FILENO, &msg, sizeof(msg));
+ } while (1);
+
+ d(fprintf(stderr, "parent exited, clsoing down remaining id's\n"));
+ while (lock_info_list)
+ unlock_id(lock_info_list->id);
+
+ return 0;
+}
diff --git a/camel/camel-lock.c b/camel/camel-lock.c
new file mode 100644
index 0000000000..eeae181f5b
--- /dev/null
+++ b/camel/camel-lock.c
@@ -0,0 +1,423 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Author: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999 Ximian (www.ximian.com/).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+#include <time.h>
+
+#ifdef USE_DOT
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif
+
+#ifdef USE_FCNTL
+#include <unistd.h>
+#include <fcntl.h>
+#endif
+
+#ifdef USE_FLOCK
+#include <sys/file.h>
+#endif
+
+#include <glib.h>
+
+#include "camel-lock.h"
+#include "camel-i18n.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+/**
+ * camel_lock_dot:
+ * @path:
+ * @ex:
+ *
+ * Create an exclusive lock using .lock semantics.
+ * All locks are equivalent to write locks (exclusive).
+ *
+ * Return value: -1 on error, sets @ex appropriately.
+ **/
+int
+camel_lock_dot(const char *path, CamelException *ex)
+{
+#ifdef USE_DOT
+ char *locktmp, *lock;
+ int retry = 0;
+ int fdtmp;
+ struct stat st;
+
+ /* TODO: Is there a reliable way to refresh the lock, if we're still busy with it?
+ Does it matter? We will normally also use fcntl too ... */
+
+ /* use alloca, save cleaning up afterwards */
+ lock = alloca(strlen(path) + strlen(".lock") + 1);
+ sprintf(lock, "%s.lock", path);
+ locktmp = alloca(strlen(path) + strlen("XXXXXX") + 1);
+
+#ifndef HAVE_MKSTEMP
+ sprintf(locktmp, "%sXXXXXX", path);
+ if (mktemp(locktmp) == NULL) {
+ /* well, this is really only a programatic error */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not create lock file for %s: %s"),
+ path, g_strerror (errno));
+ return -1;
+ }
+#endif
+
+ while (retry < CAMEL_LOCK_DOT_RETRY) {
+
+ d(printf("trying to lock '%s', attempt %d\n", lock, retry));
+
+ if (retry > 0)
+ sleep(CAMEL_LOCK_DOT_DELAY);
+
+#ifdef HAVE_MKSTEMP
+ sprintf(locktmp, "%sXXXXXX", path);
+ fdtmp = mkstemp(locktmp);
+#else
+ fdtmp = open(locktmp, O_RDWR|O_CREAT|O_EXCL, 0600);
+#endif
+ if (fdtmp == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not create lock file for %s: %s"),
+ path, g_strerror (errno));
+ return -1;
+ }
+ close(fdtmp);
+
+ /* apparently return code from link can be unreliable for nfs (see link(2)), so we ignore it */
+ link(locktmp, lock);
+
+ /* but we check stat instead (again, see link(2)) */
+ if (stat(locktmp, &st) == -1) {
+ d(printf("Our lock file %s vanished!?\n", locktmp));
+
+ /* well that was unexpected, try cleanup/retry */
+ unlink(locktmp);
+ unlink(lock);
+ } else {
+ d(printf("tmp lock created, link count is %d\n", st.st_nlink));
+
+ unlink(locktmp);
+
+ /* if we had 2 links, we have created the .lock, return ok, otherwise we need to keep trying */
+ if (st.st_nlink == 2)
+ return 0;
+ }
+
+ /* check for stale lock, kill it */
+ if (stat(lock, &st) == 0) {
+ time_t now = time(0);
+ (printf("There is an existing lock %ld seconds old\n", now-st.st_ctime));
+ if (st.st_ctime < now - CAMEL_LOCK_DOT_STALE) {
+ d(printf("Removing it now\n"));
+ unlink(lock);
+ }
+ }
+
+ retry++;
+ }
+
+ d(printf("failed to get lock after %d retries\n", retry));
+
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Timed out trying to get lock file on %s. Try again later."), path);
+ return -1;
+#else /* ! USE_DOT */
+ return 0;
+#endif
+}
+
+/**
+ * camel_unlock_dot:
+ * @path:
+ *
+ * Attempt to unlock a .lock lock.
+ **/
+void
+camel_unlock_dot(const char *path)
+{
+#ifdef USE_DOT
+ char *lock;
+
+ lock = alloca(strlen(path) + strlen(".lock") + 1);
+ sprintf(lock, "%s.lock", path);
+ d(printf("unlocking %s\n", lock));
+ (void)unlink(lock);
+#endif
+}
+
+/**
+ * camel_lock_fcntl:
+ * @fd:
+ * @type:
+ * @ex:
+ *
+ * Create a lock using fcntl(2).
+ *
+ * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ,
+ * to create exclusive or shared read locks
+ *
+ * Return value: -1 on error.
+ **/
+int
+camel_lock_fcntl(int fd, CamelLockType type, CamelException *ex)
+{
+#ifdef USE_FCNTL
+ struct flock lock;
+
+ d(printf("fcntl locking %d\n", fd));
+
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = type==CAMEL_LOCK_READ?F_RDLCK:F_WRLCK;
+ if (fcntl(fd, F_SETLK, &lock) == -1) {
+ /* If we get a 'locking not vailable' type error,
+ we assume the filesystem doesn't support fcntl() locking */
+ /* this is somewhat system-dependent */
+ if (errno != EINVAL && errno != ENOLCK) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to get lock using fcntl(2): %s"),
+ g_strerror (errno));
+ return -1;
+ } else {
+ static int failed = 0;
+
+ if (failed == 0)
+ fprintf(stderr, "fcntl(2) locking appears not to work on this filesystem");
+ failed++;
+ }
+ }
+#endif
+ return 0;
+}
+
+/**
+ * camel_unlock_fcntl:
+ * @fd:
+ *
+ * Unlock an fcntl lock.
+ **/
+void
+camel_unlock_fcntl(int fd)
+{
+#ifdef USE_FCNTL
+ struct flock lock;
+
+ d(printf("fcntl unlocking %d\n", fd));
+
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_UNLCK;
+ fcntl(fd, F_SETLK, &lock);
+#endif
+}
+
+/**
+ * camel_lock_flock:
+ * @fd:
+ * @type:
+ * @ex:
+ *
+ * Create a lock using flock(2).
+ *
+ * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ,
+ * to create exclusive or shared read locks
+ *
+ * Return value: -1 on error.
+ **/
+int
+camel_lock_flock(int fd, CamelLockType type, CamelException *ex)
+{
+#ifdef USE_FLOCK
+ int op;
+
+ d(printf("flock locking %d\n", fd));
+
+ if (type == CAMEL_LOCK_READ)
+ op = LOCK_SH|LOCK_NB;
+ else
+ op = LOCK_EX|LOCK_NB;
+
+ if (flock(fd, op) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to get lock using flock(2): %s"),
+ g_strerror (errno));
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+/**
+ * camel_unlock_flock:
+ * @fd:
+ *
+ * Unlock an flock lock.
+ **/
+void
+camel_unlock_flock(int fd)
+{
+#ifdef USE_FLOCK
+ d(printf("flock unlocking %d\n", fd));
+
+ (void)flock(fd, LOCK_UN);
+#endif
+}
+
+/**
+ * camel_lock_folder:
+ * @path: Path to the file to lock (used for .locking only).
+ * @fd: Open file descriptor of the right type to lock.
+ * @type: Type of lock, CAMEL_LOCK_READ or CAMEL_LOCK_WRITE.
+ * @ex:
+ *
+ * Attempt to lock a folder, multiple attempts will be made using all
+ * locking strategies available.
+ *
+ * Return value: -1 on error, @ex will describe the locking system that failed.
+ **/
+int
+camel_lock_folder(const char *path, int fd, CamelLockType type, CamelException *ex)
+{
+ int retry = 0;
+
+ while (retry < CAMEL_LOCK_RETRY) {
+ if (retry > 0)
+ sleep(CAMEL_LOCK_DELAY);
+
+ if (camel_lock_fcntl(fd, type, ex) == 0) {
+ if (camel_lock_flock(fd, type, ex) == 0) {
+ if (camel_lock_dot(path, ex) == 0)
+ return 0;
+ camel_unlock_flock(fd);
+ }
+ camel_unlock_fcntl(fd);
+ }
+ retry++;
+ }
+
+ return -1;
+}
+
+/**
+ * camel_unlock_folder:
+ * @path: Filename of folder.
+ * @fd: Open descrptor on which locks were placed.
+ *
+ * Free a lock on a folder.
+ **/
+void
+camel_unlock_folder(const char *path, int fd)
+{
+ camel_unlock_dot(path);
+ camel_unlock_flock(fd);
+ camel_unlock_fcntl(fd);
+}
+
+#if 0
+int main(int argc, char **argv)
+{
+ CamelException *ex;
+ int fd1, fd2;
+
+ ex = camel_exception_new();
+
+#if 0
+ if (camel_lock_dot("mylock", ex) == 0) {
+ if (camel_lock_dot("mylock", ex) == 0) {
+ printf("Got lock twice?\n");
+ } else {
+ printf("failed to get lock 2: %s\n", camel_exception_get_description(ex));
+ }
+ camel_unlock_dot("mylock");
+ } else {
+ printf("failed to get lock 1: %s\n", camel_exception_get_description(ex));
+ }
+
+ camel_exception_clear(ex);
+#endif
+
+ fd1 = open("mylock", O_RDWR);
+ fd2 = open("mylock", O_RDWR);
+
+ if (camel_lock_fcntl(fd1, CAMEL_LOCK_WRITE, ex) == 0) {
+ printf("got fcntl write lock once\n");
+ sleep(5);
+ if (camel_lock_fcntl(fd2, CAMEL_LOCK_WRITE, ex) == 0) {
+ printf("got fcntl write lock twice!\n");
+ } else {
+ printf("failed to get write lock: %s\n", camel_exception_get_description(ex));
+ }
+
+ camel_exception_clear(ex);
+
+ if (camel_lock_fcntl(fd2, CAMEL_LOCK_READ, ex) == 0) {
+ printf("got fcntl read lock as well?\n");
+ camel_unlock_fcntl(fd2);
+ } else {
+ printf("failed to get read lock: %s\n", camel_exception_get_description(ex));
+ }
+
+ camel_exception_clear(ex);
+ camel_unlock_fcntl(fd1);
+ } else {
+ printf("failed to get write lock at all: %s\n", camel_exception_get_description(ex));
+ }
+
+ if (camel_lock_fcntl(fd1, CAMEL_LOCK_READ, ex) == 0) {
+ printf("got fcntl read lock once\n");
+ sleep(5);
+ if (camel_lock_fcntl(fd2, CAMEL_LOCK_WRITE, ex) == 0) {
+ printf("got fcntl write lock too?!\n");
+ } else {
+ printf("failed to get write lock: %s\n", camel_exception_get_description(ex));
+ }
+
+ camel_exception_clear(ex);
+
+ if (camel_lock_fcntl(fd2, CAMEL_LOCK_READ, ex) == 0) {
+ printf("got fcntl read lock twice\n");
+ camel_unlock_fcntl(fd2);
+ } else {
+ printf("failed to get read lock: %s\n", camel_exception_get_description(ex));
+ }
+
+ camel_exception_clear(ex);
+ camel_unlock_fcntl(fd1);
+ }
+
+ close(fd1);
+ close(fd2);
+
+ return 0;
+}
+#endif
diff --git a/camel/camel-mime-filter-canon.c b/camel/camel-mime-filter-canon.c
new file mode 100644
index 0000000000..121d49311e
--- /dev/null
+++ b/camel/camel-mime-filter-canon.c
@@ -0,0 +1,190 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002 Ximian, Inc.
+ *
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* canonicalisation filter, used for secure mime incoming and outgoing */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+
+#include "camel-mime-filter-canon.h"
+
+static void filter (CamelMimeFilter *f, char *in, size_t len, size_t prespace,
+ char **out, size_t *outlen, size_t *outprespace);
+static void complete (CamelMimeFilter *f, char *in, size_t len,
+ size_t prespace, char **out, size_t *outlen,
+ size_t *outprespace);
+static void reset (CamelMimeFilter *f);
+
+
+static void
+camel_mime_filter_canon_class_init (CamelMimeFilterCanonClass *klass)
+{
+ CamelMimeFilterClass *mime_filter_class = (CamelMimeFilterClass *) klass;
+
+ mime_filter_class->filter = filter;
+ mime_filter_class->complete = complete;
+ mime_filter_class->reset = reset;
+}
+
+CamelType
+camel_mime_filter_canon_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_mime_filter_get_type(), "CamelMimeFilterCanon",
+ sizeof (CamelMimeFilterCanon),
+ sizeof (CamelMimeFilterCanonClass),
+ (CamelObjectClassInitFunc) camel_mime_filter_canon_class_init,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ return type;
+}
+
+static void
+filter_run(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace, int last)
+{
+ register unsigned char *inptr, c;
+ const unsigned char *inend, *start;
+ char *starto;
+ register char *o;
+ int lf = 0;
+ guint32 flags;
+
+ flags = ((CamelMimeFilterCanon *)f)->flags;
+
+ /* first, work out how much space we need */
+ inptr = in;
+ inend = in+len;
+ while (inptr < inend)
+ if (*inptr++ == '\n')
+ lf++;
+
+ /* worst case, extra 3 chars per line
+ "From \n" -> "=46rom \r\n"
+ We add 1 extra incase we're called from complete, when we didn't end in \n */
+
+ camel_mime_filter_set_size(f, len+lf*3+4, FALSE);
+
+ o = f->outbuf;
+ inptr = in;
+ start = inptr;
+ starto = o;
+ while (inptr < inend) {
+ /* first, check start of line, we always start at the start of the line */
+ c = *inptr;
+ if (flags & CAMEL_MIME_FILTER_CANON_FROM && c == 'F') {
+ inptr++;
+ if (inptr < inend-4) {
+ if (strncmp(inptr, "rom ", 4) == 0) {
+ strcpy(o, "=46rom ");
+ inptr+=4;
+ o+= 7;
+ } else
+ *o++ = 'F';
+ } else if (last)
+ *o++ = 'F';
+ else
+ break;
+ }
+
+ /* now scan for end of line */
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c == '\n') {
+ /* check to strip trailing space */
+ if (flags & CAMEL_MIME_FILTER_CANON_STRIP) {
+ while (o>starto && (o[-1] == ' ' || o[-1] == '\t' || o[-1]=='\r'))
+ o--;
+ }
+ /* check end of line canonicalisation */
+ if (o>starto) {
+ if (flags & CAMEL_MIME_FILTER_CANON_CRLF) {
+ if (o[-1] != '\r')
+ *o++ = '\r';
+ } else {
+ if (o[-1] == '\r')
+ o--;
+ }
+ } else if (flags & CAMEL_MIME_FILTER_CANON_CRLF) {
+ /* empty line */
+ *o++ = '\r';
+ }
+
+ *o++ = c;
+ start = inptr;
+ starto = o;
+ break;
+ } else
+ *o++ = c;
+ }
+ }
+
+ /* TODO: We should probably track if we end somewhere in the middle of a line,
+ otherwise we potentially backup a full line, which could be large */
+
+ /* we got to the end of the data without finding anything, backup to start and re-process next time around */
+ if (last) {
+ *outlen = o - f->outbuf;
+ } else {
+ camel_mime_filter_backup(f, start, inend - start);
+ *outlen = starto - f->outbuf;
+ }
+
+ *out = f->outbuf;
+ *outprespace = f->outpre;
+}
+
+static void
+filter(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace)
+{
+ filter_run(f, in, len, prespace, out, outlen, outprespace, FALSE);
+}
+
+static void
+complete(CamelMimeFilter *f, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace)
+{
+ filter_run(f, in, len, prespace, out, outlen, outprespace, TRUE);
+}
+
+static void
+reset (CamelMimeFilter *f)
+{
+ /* no-op */
+}
+
+CamelMimeFilter *
+camel_mime_filter_canon_new(guint32 flags)
+{
+ CamelMimeFilterCanon *chomp = CAMEL_MIME_FILTER_CANON (camel_object_new (CAMEL_MIME_FILTER_CANON_TYPE));
+
+ chomp->flags = flags;
+
+ return (CamelMimeFilter *) chomp;
+}
diff --git a/camel/camel-mime-filter-enriched.c b/camel/camel-mime-filter-enriched.c
new file mode 100644
index 0000000000..4a679e3ced
--- /dev/null
+++ b/camel/camel-mime-filter-enriched.c
@@ -0,0 +1,603 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <camel/camel-string-utils.h>
+
+#include "camel-mime-filter-enriched.h"
+
+/* text/enriched is rfc1896 */
+
+typedef char * (*EnrichedParamParser) (const char *inptr, int inlen);
+
+static char *param_parse_colour (const char *inptr, int inlen);
+static char *param_parse_font (const char *inptr, int inlen);
+static char *param_parse_lang (const char *inptr, int inlen);
+
+static struct {
+ char *enriched;
+ char *html;
+ gboolean needs_param;
+ EnrichedParamParser parse_param; /* parses *and* validates the input */
+} enriched_tags[] = {
+ { "bold", "<b>", FALSE, NULL },
+ { "/bold", "</b>", FALSE, NULL },
+ { "italic", "<i>", FALSE, NULL },
+ { "/italic", "</i>", FALSE, NULL },
+ { "fixed", "<tt>", FALSE, NULL },
+ { "/fixed", "</tt>", FALSE, NULL },
+ { "smaller", "<font size=-1>", FALSE, NULL },
+ { "/smaller", "</font>", FALSE, NULL },
+ { "bigger", "<font size=+1>", FALSE, NULL },
+ { "/bigger", "</font>", FALSE, NULL },
+ { "underline", "<u>", FALSE, NULL },
+ { "/underline", "</u>", FALSE, NULL },
+ { "center", "<p align=center>", FALSE, NULL },
+ { "/center", "</p>", FALSE, NULL },
+ { "flushleft", "<p align=left>", FALSE, NULL },
+ { "/flushleft", "</p>", FALSE, NULL },
+ { "flushright", "<p align=right>", FALSE, NULL },
+ { "/flushright", "</p>", FALSE, NULL },
+ { "excerpt", "<blockquote>", FALSE, NULL },
+ { "/excerpt", "</blockquote>", FALSE, NULL },
+ { "paragraph", "<p>", FALSE, NULL },
+ { "signature", "<address>", FALSE, NULL },
+ { "/signature", "</address>", FALSE, NULL },
+ { "comment", "<!-- ", FALSE, NULL },
+ { "/comment", " -->", FALSE, NULL },
+ { "np", "<hr>", FALSE, NULL },
+ { "fontfamily", "<font face=\"%s\">", TRUE, param_parse_font },
+ { "/fontfamily", "</font>", FALSE, NULL },
+ { "color", "<font color=\"%s\">", TRUE, param_parse_colour },
+ { "/color", "</font>", FALSE, NULL },
+ { "lang", "<span lang=\"%s\">", TRUE, param_parse_lang },
+ { "/lang", "</span>", FALSE, NULL },
+
+ /* don't handle this tag yet... */
+ { "paraindent", "<!-- ", /* TRUE */ FALSE, NULL },
+ { "/paraindent", " -->", FALSE, NULL },
+
+ /* as soon as we support all the tags that can have a param
+ * tag argument, these should be unnecessary, but we'll keep
+ * them anyway just in case? */
+ { "param", "<!-- ", FALSE, NULL },
+ { "/param", " -->", FALSE, NULL },
+};
+
+#define NUM_ENRICHED_TAGS (sizeof (enriched_tags) / sizeof (enriched_tags[0]))
+
+static GHashTable *enriched_hash = NULL;
+
+
+static void camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *klass);
+static void camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter);
+static void camel_mime_filter_enriched_finalize (CamelObject *obj);
+
+static void filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
+ char **out, size_t *outlen, size_t *outprespace);
+static void filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
+ char **out, size_t *outlen, size_t *outprespace);
+static void filter_reset (CamelMimeFilter *filter);
+
+
+static CamelMimeFilterClass *parent_class = NULL;
+
+
+CamelType
+camel_mime_filter_enriched_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_mime_filter_get_type (),
+ "CamelMimeFilterEnriched",
+ sizeof (CamelMimeFilterEnriched),
+ sizeof (CamelMimeFilterEnrichedClass),
+ (CamelObjectClassInitFunc) camel_mime_filter_enriched_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_mime_filter_enriched_init,
+ (CamelObjectFinalizeFunc) camel_mime_filter_enriched_finalize);
+ }
+
+ return type;
+}
+
+static void
+camel_mime_filter_enriched_class_init (CamelMimeFilterEnrichedClass *klass)
+{
+ CamelMimeFilterClass *filter_class = (CamelMimeFilterClass *) klass;
+ int i;
+
+ parent_class = CAMEL_MIME_FILTER_CLASS (camel_mime_filter_get_type ());
+
+ filter_class->reset = filter_reset;
+ filter_class->filter = filter_filter;
+ filter_class->complete = filter_complete;
+
+ if (!enriched_hash) {
+ enriched_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
+ for (i = 0; i < NUM_ENRICHED_TAGS; i++)
+ g_hash_table_insert (enriched_hash, enriched_tags[i].enriched,
+ enriched_tags[i].html);
+ }
+}
+
+static void
+camel_mime_filter_enriched_finalize (CamelObject *obj)
+{
+ ;
+}
+
+static void
+camel_mime_filter_enriched_init (CamelMimeFilterEnriched *filter)
+{
+ filter->flags = 0;
+ filter->nofill = 0;
+}
+
+
+#if 0
+static gboolean
+enriched_tag_needs_param (const char *tag)
+{
+ int i;
+
+ for (i = 0; i < NUM_ENRICHED_TAGS; i++)
+ if (!g_ascii_strcasecmp (tag, enriched_tags[i].enriched))
+ return enriched_tags[i].needs_param;
+
+ return FALSE;
+}
+#endif
+
+static gboolean
+html_tag_needs_param (const char *tag)
+{
+ return strstr (tag, "%s") != NULL;
+}
+
+static const char *valid_colours[] = {
+ "red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"
+};
+
+#define NUM_VALID_COLOURS (sizeof (valid_colours) / sizeof (valid_colours[0]))
+
+static char *
+param_parse_colour (const char *inptr, int inlen)
+{
+ const char *inend, *end;
+ guint32 rgb = 0;
+ guint v;
+ int i;
+
+ for (i = 0; i < NUM_VALID_COLOURS; i++) {
+ if (!strncasecmp (inptr, valid_colours[i], inlen))
+ return g_strdup (valid_colours[i]);
+ }
+
+ /* check for numeric r/g/b in the format: ####,####,#### */
+ if (inptr[4] != ',' || inptr[9] != ',') {
+ /* okay, mailer must have used a string name that
+ * rfc1896 did not specify? do some simple scanning
+ * action, a colour name MUST be [a-zA-Z] */
+ end = inptr;
+ inend = inptr + inlen;
+ while (end < inend && ((*end >= 'a' && *end <= 'z') || (*end >= 'A' && *end <= 'Z')))
+ end++;
+
+ return g_strndup (inptr, end - inptr);
+ }
+
+ for (i = 0; i < 3; i++) {
+ v = strtoul (inptr, (char **) &end, 16);
+ if (end != inptr + 4)
+ goto invalid_format;
+
+ v >>= 8;
+ rgb = (rgb << 8) | (v & 0xff);
+
+ inptr += 5;
+ }
+
+ return g_strdup_printf ("#%.6X", rgb);
+
+ invalid_format:
+
+ /* default colour? */
+ return g_strdup ("black");
+}
+
+static char *
+param_parse_font (const char *fontfamily, int inlen)
+{
+ register const char *inptr = fontfamily;
+ const char *inend = inptr + inlen;
+
+ /* don't allow any of '"', '<', nor '>' */
+ while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
+ inptr++;
+
+ return g_strndup (fontfamily, inptr - fontfamily);
+}
+
+static char *
+param_parse_lang (const char *lang, int inlen)
+{
+ register const char *inptr = lang;
+ const char *inend = inptr + inlen;
+
+ /* don't allow any of '"', '<', nor '>' */
+ while (inptr < inend && *inptr != '"' && *inptr != '<' && *inptr != '>')
+ inptr++;
+
+ return g_strndup (lang, inptr - lang);
+}
+
+static char *
+param_parse (const char *enriched, const char *inptr, int inlen)
+{
+ int i;
+
+ for (i = 0; i < NUM_ENRICHED_TAGS; i++) {
+ if (!g_ascii_strcasecmp (enriched, enriched_tags[i].enriched))
+ return enriched_tags[i].parse_param (inptr, inlen);
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+#define IS_RICHTEXT CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT
+
+static void
+enriched_to_html (CamelMimeFilter *filter, char *in, size_t inlen, size_t prespace,
+ char **out, size_t *outlen, size_t *outprespace, gboolean flush)
+{
+ CamelMimeFilterEnriched *enriched = (CamelMimeFilterEnriched *) filter;
+ const char *tag, *inend, *outend;
+ register const char *inptr;
+ register char *outptr;
+
+ camel_mime_filter_set_size (filter, inlen * 2 + 6, FALSE);
+
+ inptr = in;
+ inend = in + inlen;
+ outptr = filter->outbuf;
+ outend = filter->outbuf + filter->outsize;
+
+ retry:
+ do {
+ while (inptr < inend && outptr < outend && !strchr (" <>&\n", *inptr))
+ *outptr++ = *inptr++;
+
+ if (outptr == outend)
+ goto backup;
+
+ if ((inptr + 1) >= inend)
+ break;
+
+ switch (*inptr++) {
+ case ' ':
+ while (inptr < inend && (outptr + 7) < outend && *inptr == ' ') {
+ memcpy (outptr, "&nbsp;", 6);
+ outptr += 6;
+ inptr++;
+ }
+
+ if (outptr < outend)
+ *outptr++ = ' ';
+
+ break;
+ case '\n':
+ if (!(enriched->flags & IS_RICHTEXT)) {
+ /* text/enriched */
+ if (enriched->nofill > 0) {
+ if ((outptr + 4) < outend) {
+ memcpy (outptr, "<br>", 4);
+ outptr += 4;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ } else if (*inptr == '\n') {
+ if ((outptr + 4) >= outend) {
+ inptr--;
+ goto backup;
+ }
+
+ while (inptr < inend && (outptr + 4) < outend && *inptr == '\n') {
+ memcpy (outptr, "<br>", 4);
+ outptr += 4;
+ inptr++;
+ }
+ } else {
+ *outptr++ = ' ';
+ }
+ } else {
+ /* text/richtext */
+ *outptr++ = ' ';
+ }
+ break;
+ case '>':
+ if ((outptr + 4) < outend) {
+ memcpy (outptr, "&gt;", 4);
+ outptr += 4;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ break;
+ case '&':
+ if ((outptr + 5) < outend) {
+ memcpy (outptr, "&amp;", 5);
+ outptr += 5;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ break;
+ case '<':
+ if (!(enriched->flags & IS_RICHTEXT)) {
+ /* text/enriched */
+ if (*inptr == '<') {
+ if ((outptr + 4) < outend) {
+ memcpy (outptr, "&lt;", 4);
+ outptr += 4;
+ inptr++;
+ break;
+ } else {
+ inptr--;
+ goto backup;
+ }
+ }
+ } else {
+ /* text/richtext */
+ if ((inend - inptr) >= 3 && (outptr + 4) < outend) {
+ if (strncmp (inptr, "lt>", 3) == 0) {
+ memcpy (outptr, "&lt;", 4);
+ outptr += 4;
+ inptr += 3;
+ break;
+ } else if (strncmp (inptr, "nl>", 3) == 0) {
+ memcpy (outptr, "<br>", 4);
+ outptr += 4;
+ inptr += 3;
+ break;
+ }
+ } else {
+ inptr--;
+ goto backup;
+ }
+ }
+
+ tag = inptr;
+ while (inptr < inend && *inptr != '>')
+ inptr++;
+
+ if (inptr == inend) {
+ inptr = tag - 1;
+ goto need_input;
+ }
+
+ if (!strncasecmp (tag, "nofill>", 7)) {
+ if ((outptr + 5) < outend) {
+ enriched->nofill++;
+ } else {
+ inptr = tag - 1;
+ goto backup;
+ }
+ } else if (!strncasecmp (tag, "/nofill>", 8)) {
+ if ((outptr + 6) < outend) {
+ enriched->nofill--;
+ } else {
+ inptr = tag - 1;
+ goto backup;
+ }
+ } else {
+ const char *html_tag;
+ char *enriched_tag;
+ int len;
+
+ len = inptr - tag;
+ enriched_tag = g_alloca (len + 1);
+ memcpy (enriched_tag, tag, len);
+ enriched_tag[len] = '\0';
+
+ html_tag = g_hash_table_lookup (enriched_hash, enriched_tag);
+
+ if (html_tag) {
+ if (html_tag_needs_param (html_tag)) {
+ const char *start;
+ char *param;
+
+ while (inptr < inend && *inptr != '<')
+ inptr++;
+
+ if (inptr == inend || (inend - inptr) <= 15) {
+ inptr = tag - 1;
+ goto need_input;
+ }
+
+ if (strncasecmp (inptr, "<param>", 7) != 0) {
+ /* ignore the enriched command tag... */
+ inptr -= 1;
+ goto loop;
+ }
+
+ inptr += 7;
+ start = inptr;
+
+ while (inptr < inend && *inptr != '<')
+ inptr++;
+
+ if (inptr == inend || (inend - inptr) <= 8) {
+ inptr = tag - 1;
+ goto need_input;
+ }
+
+ if (strncasecmp (inptr, "</param>", 8) != 0) {
+ /* ignore the enriched command tag... */
+ inptr += 7;
+ goto loop;
+ }
+
+ len = inptr - start;
+ param = param_parse (enriched_tag, start, len);
+ len = strlen (param);
+
+ inptr += 7;
+
+ len += strlen (html_tag);
+
+ if ((outptr + len) < outend) {
+ outptr += snprintf (outptr, len, html_tag, param);
+ g_free (param);
+ } else {
+ g_free (param);
+ inptr = tag - 1;
+ goto backup;
+ }
+ } else {
+ len = strlen (html_tag);
+ if ((outptr + len) < outend) {
+ memcpy (outptr, html_tag, len);
+ outptr += len;
+ } else {
+ inptr = tag - 1;
+ goto backup;
+ }
+ }
+ }
+ }
+
+ loop:
+ inptr++;
+ break;
+ default:
+ break;
+ }
+ } while (inptr < inend);
+
+ need_input:
+
+ /* the reason we ignore @flush here is because if there isn't
+ enough input to parse a tag, then there's nothing we can
+ do. */
+
+ if (inptr < inend)
+ camel_mime_filter_backup (filter, inptr, (unsigned) (inend - inptr));
+
+ *out = filter->outbuf;
+ *outlen = outptr - filter->outbuf;
+ *outprespace = filter->outpre;
+
+ return;
+
+ backup:
+
+ if (flush) {
+ size_t offset, grow;
+
+ grow = (inend - inptr) * 2 + 20;
+ offset = outptr - filter->outbuf;
+ camel_mime_filter_set_size (filter, filter->outsize + grow, TRUE);
+ outend = filter->outbuf + filter->outsize;
+ outptr = filter->outbuf + offset;
+
+ goto retry;
+ } else {
+ camel_mime_filter_backup (filter, inptr, (unsigned) (inend - inptr));
+ }
+
+ *out = filter->outbuf;
+ *outlen = outptr - filter->outbuf;
+ *outprespace = filter->outpre;
+}
+
+static void
+filter_filter (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
+ char **out, size_t *outlen, size_t *outprespace)
+{
+ enriched_to_html (filter, in, len, prespace, out, outlen, outprespace, FALSE);
+}
+
+static void
+filter_complete (CamelMimeFilter *filter, char *in, size_t len, size_t prespace,
+ char **out, size_t *outlen, size_t *outprespace)
+{
+ enriched_to_html (filter, in, len, prespace, out, outlen, outprespace, TRUE);
+}
+
+static void
+filter_reset (CamelMimeFilter *filter)
+{
+ CamelMimeFilterEnriched *enriched = (CamelMimeFilterEnriched *) filter;
+
+ enriched->nofill = 0;
+}
+
+
+/**
+ * camel_mime_filter_enriched_new:
+ * @flags:
+ *
+ * Creates a new CamelMimeFilterEnriched object.
+ *
+ * Returns a new CamelMimeFilter object.
+ **/
+CamelMimeFilter *
+camel_mime_filter_enriched_new (guint32 flags)
+{
+ CamelMimeFilterEnriched *new;
+
+ new = (CamelMimeFilterEnriched *) camel_object_new (CAMEL_TYPE_MIME_FILTER_ENRICHED);
+ new->flags = flags;
+
+ return CAMEL_MIME_FILTER (new);
+}
+
+char *
+camel_enriched_to_html(const char *in, guint32 flags)
+{
+ CamelMimeFilter *filter;
+ size_t outlen, outpre;
+ char *outbuf;
+
+ if (in == NULL)
+ return NULL;
+
+ filter = camel_mime_filter_enriched_new(flags);
+
+ camel_mime_filter_complete(filter, (char *)in, strlen(in), 0, &outbuf, &outlen, &outpre);
+ outbuf = g_strndup (outbuf, outlen);
+ camel_object_unref (filter);
+
+ return outbuf;
+}
diff --git a/camel/camel-mime-parser.c b/camel/camel-mime-parser.c
new file mode 100644
index 0000000000..ddf1b6b4a9
--- /dev/null
+++ b/camel/camel-mime-parser.c
@@ -0,0 +1,1930 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* What should hopefully be a fast mail parser */
+
+/* Do not change this code without asking me (Michael Zucchi) first
+
+ There is almost always a reason something was done a certain way.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <string.h>
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <regex.h>
+#include <ctype.h>
+
+#include <glib.h>
+#include "camel-mime-parser.h"
+#include "camel-mime-utils.h"
+#include "camel-mime-filter.h"
+#include "camel-stream.h"
+#include "camel-seekable-stream.h"
+
+#include "e-util/e-memory.h"
+
+#define r(x)
+#define h(x)
+#define c(x)
+#define d(x)
+
+/*#define PRESERVE_HEADERS*/
+
+/*#define PURIFY*/
+
+#define MEMPOOL
+
+#ifdef PURIFY
+int inend_id = -1,
+ inbuffer_id = -1;
+#endif
+
+#define SCAN_BUF 4096 /* size of read buffer */
+#define SCAN_HEAD 128 /* headroom guaranteed to be before each read buffer */
+
+/* a little hacky, but i couldn't be bothered renaming everything */
+#define _header_scan_state _CamelMimeParserPrivate
+#define _PRIVATE(o) (((CamelMimeParser *)(o))->priv)
+
+struct _header_scan_state {
+
+ /* global state */
+
+ enum _camel_mime_parser_state state;
+
+ /* for building headers during scanning */
+ char *outbuf;
+ char *outptr;
+ char *outend;
+
+ int fd; /* input for a fd input */
+ CamelStream *stream; /* or for a stream */
+
+ int ioerrno; /* io error state */
+
+ /* for scanning input buffers */
+ char *realbuf; /* the real buffer, SCAN_HEAD*2 + SCAN_BUF bytes */
+ char *inbuf; /* points to a subset of the allocated memory, the underflow */
+ char *inptr; /* (upto SCAN_HEAD) is for use by filters so they dont copy all data */
+ char *inend;
+
+ int atleast;
+
+ off_t seek; /* current offset to start of buffer */
+ int unstep; /* how many states to 'unstep' (repeat the current state) */
+
+ unsigned int midline:1; /* are we mid-line interrupted? */
+ unsigned int scan_from:1; /* do we care about From lines? */
+ unsigned int scan_pre_from:1; /* do we return pre-from data? */
+ unsigned int eof:1; /* reached eof? */
+
+ off_t start_of_from; /* where from started */
+ off_t start_of_boundary; /* where the last boundary started */
+ off_t start_of_headers; /* where headers started from the last scan */
+
+ off_t header_start; /* start of last header, or -1 */
+
+ /* filters to apply to all content before output */
+ int filterid; /* id of next filter */
+ struct _header_scan_filter *filters;
+
+ /* per message/part info */
+ struct _header_scan_stack *parts;
+
+};
+
+struct _header_scan_stack {
+ struct _header_scan_stack *parent;
+
+ enum _camel_mime_parser_state savestate; /* state at invocation of this part */
+
+#ifdef MEMPOOL
+ EMemPool *pool; /* memory pool to keep track of headers/etc at this level */
+#endif
+ struct _camel_header_raw *headers; /* headers for this part */
+
+ CamelContentType *content_type;
+
+ /* I dont use GString's casue you can't efficiently append a buffer to them */
+ GByteArray *pretext; /* for multipart types, save the pre-boundary data here */
+ GByteArray *posttext; /* for multipart types, save the post-boundary data here */
+ int prestage; /* used to determine if it is a pre-boundary or post-boundary data segment */
+
+ GByteArray *from_line; /* the from line */
+
+ char *boundary; /* for multipart/ * boundaries, including leading -- and trailing -- for the final part */
+ int boundarylen; /* actual length of boundary, including leading -- if there is one */
+ int boundarylenfinal; /* length of boundary, including trailing -- if there is one */
+ int atleast; /* the biggest boundary from here to the parent */
+};
+
+struct _header_scan_filter {
+ struct _header_scan_filter *next;
+ int id;
+ CamelMimeFilter *filter;
+};
+
+static void folder_scan_step(struct _header_scan_state *s, char **databuffer, size_t *datalength);
+static void folder_scan_drop_step(struct _header_scan_state *s);
+static int folder_scan_init_with_fd(struct _header_scan_state *s, int fd);
+static int folder_scan_init_with_stream(struct _header_scan_state *s, CamelStream *stream);
+static struct _header_scan_state *folder_scan_init(void);
+static void folder_scan_close(struct _header_scan_state *s);
+static struct _header_scan_stack *folder_scan_content(struct _header_scan_state *s, int *lastone, char **data, size_t *length);
+static struct _header_scan_stack *folder_scan_header(struct _header_scan_state *s, int *lastone);
+static int folder_scan_skip_line(struct _header_scan_state *s, GByteArray *save);
+static off_t folder_seek(struct _header_scan_state *s, off_t offset, int whence);
+static off_t folder_tell(struct _header_scan_state *s);
+static int folder_read(struct _header_scan_state *s);
+static void folder_push_part(struct _header_scan_state *s, struct _header_scan_stack *h);
+
+#ifdef MEMPOOL
+static void header_append_mempool(struct _header_scan_state *s, struct _header_scan_stack *h, char *header, int offset);
+#endif
+
+static void camel_mime_parser_class_init (CamelMimeParserClass *klass);
+static void camel_mime_parser_init (CamelMimeParser *obj);
+
+#if d(!)0
+static char *states[] = {
+ "CAMEL_MIME_PARSER_STATE_INITIAL",
+ "CAMEL_MIME_PARSER_STATE_PRE_FROM", /* pre-from data */
+ "CAMEL_MIME_PARSER_STATE_FROM", /* got 'From' line */
+ "CAMEL_MIME_PARSER_STATE_HEADER", /* toplevel header */
+ "CAMEL_MIME_PARSER_STATE_BODY", /* scanning body of message */
+ "CAMEL_MIME_PARSER_STATE_MULTIPART", /* got multipart header */
+ "CAMEL_MIME_PARSER_STATE_MESSAGE", /* rfc822/news message */
+
+ "CAMEL_MIME_PARSER_STATE_PART", /* part of a multipart */
+
+ "CAMEL_MIME_PARSER_STATE_EOF", /* end of file */
+ "CAMEL_MIME_PARSER_STATE_PRE_FROM_END",
+ "CAMEL_MIME_PARSER_STATE_FROM_END",
+ "CAMEL_MIME_PARSER_STATE_HEAER_END",
+ "CAMEL_MIME_PARSER_STATE_BODY_END",
+ "CAMEL_MIME_PARSER_STATE_MULTIPART_END",
+ "CAMEL_MIME_PARSER_STATE_MESSAGE_END",
+};
+#endif
+
+static CamelObjectClass *camel_mime_parser_parent;
+
+static void
+camel_mime_parser_class_init (CamelMimeParserClass *klass)
+{
+ camel_mime_parser_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
+}
+
+static void
+camel_mime_parser_init (CamelMimeParser *obj)
+{
+ struct _header_scan_state *s;
+
+ s = folder_scan_init();
+ _PRIVATE(obj) = s;
+}
+
+static void
+camel_mime_parser_finalise(CamelObject *o)
+{
+ struct _header_scan_state *s = _PRIVATE(o);
+#ifdef PURIFY
+ purify_watch_remove_all();
+#endif
+ folder_scan_close(s);
+}
+
+CamelType
+camel_mime_parser_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_object_get_type (), "CamelMimeParser",
+ sizeof (CamelMimeParser),
+ sizeof (CamelMimeParserClass),
+ (CamelObjectClassInitFunc) camel_mime_parser_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_mime_parser_init,
+ (CamelObjectFinalizeFunc) camel_mime_parser_finalise);
+ }
+
+ return type;
+}
+
+/**
+ * camel_mime_parser_new:
+ *
+ * Create a new CamelMimeParser object.
+ *
+ * Return value: A new CamelMimeParser widget.
+ **/
+CamelMimeParser *
+camel_mime_parser_new (void)
+{
+ CamelMimeParser *new = CAMEL_MIME_PARSER ( camel_object_new (camel_mime_parser_get_type ()));
+ return new;
+}
+
+
+/**
+ * camel_mime_parser_filter_add:
+ * @m:
+ * @mf:
+ *
+ * Add a filter that will be applied to any body content before it is passed
+ * to the caller. Filters may be pipelined to perform multi-pass operations
+ * on the content, and are applied in the order they were added.
+ *
+ * Note that filters are only applied to the body content of messages, and once
+ * a filter has been set, all content returned by a filter_step() with a state
+ * of CAMEL_MIME_PARSER_STATE_BODY will have passed through the filter.
+ *
+ * Return value: An id that may be passed to filter_remove() to remove
+ * the filter, or -1 if the operation failed.
+ **/
+int
+camel_mime_parser_filter_add(CamelMimeParser *m, CamelMimeFilter *mf)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+ struct _header_scan_filter *f, *new;
+
+ new = g_malloc(sizeof(*new));
+ new->filter = mf;
+ new->id = s->filterid++;
+ if (s->filterid == -1)
+ s->filterid++;
+ new->next = 0;
+ camel_object_ref((CamelObject *)mf);
+
+ /* yes, this is correct, since 'next' is the first element of the struct */
+ f = (struct _header_scan_filter *)&s->filters;
+ while (f->next)
+ f = f->next;
+ f->next = new;
+ return new->id;
+}
+
+/**
+ * camel_mime_parser_filter_remove:
+ * @m:
+ * @id:
+ *
+ * Remove a processing filter from the pipeline. There is no
+ * restriction on the order the filters can be removed.
+ **/
+void
+camel_mime_parser_filter_remove(CamelMimeParser *m, int id)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+ struct _header_scan_filter *f, *old;
+
+ f = (struct _header_scan_filter *)&s->filters;
+ while (f && f->next) {
+ old = f->next;
+ if (old->id == id) {
+ camel_object_unref((CamelObject *)old->filter);
+ f->next = old->next;
+ g_free(old);
+ /* there should only be a single matching id, but
+ scan the whole lot anyway */
+ }
+ f = f->next;
+ }
+}
+
+/**
+ * camel_mime_parser_header:
+ * @m:
+ * @name: Name of header.
+ * @offset: Pointer that can receive the offset of the header in
+ * the stream from the start of parsing.
+ *
+ * Lookup a header by name.
+ *
+ * Return value: The header value, or NULL if the header is not
+ * defined.
+ **/
+const char *
+camel_mime_parser_header(CamelMimeParser *m, const char *name, int *offset)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ if (s->parts &&
+ s->parts->headers) {
+ return camel_header_raw_find(&s->parts->headers, name, offset);
+ }
+ return NULL;
+}
+
+/**
+ * camel_mime_parser_headers_raw:
+ * @m:
+ *
+ * Get the list of the raw headers which are defined for the
+ * current state of the parser. These headers are valid
+ * until the next call to parser_step(), or parser_drop_step().
+ *
+ * Return value: The raw headers, or NULL if there are no headers
+ * defined for the current part or state. These are READ ONLY.
+ **/
+struct _camel_header_raw *
+camel_mime_parser_headers_raw(CamelMimeParser *m)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ if (s->parts)
+ return s->parts->headers;
+ return NULL;
+}
+
+static const char *
+byte_array_to_string(GByteArray *array)
+{
+ if (array == NULL)
+ return NULL;
+
+ if (array->len == 0 || array->data[array->len-1] != '\0')
+ g_byte_array_append(array, "", 1);
+
+ return array->data;
+}
+
+/**
+ * camel_mime_parser_preface:
+ * @m:
+ *
+ * Retrieve the preface text for the current multipart.
+ * Can only be used when the state is CAMEL_MIME_PARSER_STATE_MULTIPART_END.
+ *
+ * Return value: The preface text, or NULL if there wasn't any.
+ **/
+const char *
+camel_mime_parser_preface(CamelMimeParser *m)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ if (s->parts)
+ return byte_array_to_string(s->parts->pretext);
+
+ return NULL;
+}
+
+/**
+ * camel_mime_parser_postface:
+ * @m:
+ *
+ * Retrieve the postface text for the current multipart.
+ * Only returns valid data when the current state if
+ * CAMEL_MIME_PARSER_STATE_MULTIPART_END.
+ *
+ * Return value: The postface text, or NULL if there wasn't any.
+ **/
+const char *
+camel_mime_parser_postface(CamelMimeParser *m)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ if (s->parts)
+ return byte_array_to_string(s->parts->posttext);
+
+ return NULL;
+}
+
+/**
+ * camel_mime_parser_from_line:
+ * @m:
+ *
+ * Get the last scanned "From " line, from a recently scanned from.
+ * This should only be called in the CAMEL_MIME_PARSER_STATE_FROM state. The
+ * from line will include the closing \n found (if there was one).
+ *
+ * The return value will remain valid while in the CAMEL_MIME_PARSER_STATE_FROM
+ * state, or any deeper state.
+ *
+ * Return value: The From line, or NULL if called out of context.
+ **/
+const char *
+camel_mime_parser_from_line(CamelMimeParser *m)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ if (s->parts)
+ return byte_array_to_string(s->parts->from_line);
+
+ return NULL;
+}
+
+/**
+ * camel_mime_parser_init_with_fd:
+ * @m:
+ * @fd: A valid file descriptor.
+ *
+ * Initialise the scanner with an fd. The scanner's offsets
+ * will be relative to the current file position of the file
+ * descriptor. As a result, seekable descritors should
+ * be seeked using the parser seek functions.
+ *
+ * Return value: Returns -1 on error.
+ **/
+int
+camel_mime_parser_init_with_fd(CamelMimeParser *m, int fd)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ return folder_scan_init_with_fd(s, fd);
+}
+
+/**
+ * camel_mime_parser_init_with_stream:
+ * @m:
+ * @stream:
+ *
+ * Initialise the scanner with a source stream. The scanner's
+ * offsets will be relative to the current file position of
+ * the stream. As a result, seekable streams should only
+ * be seeked using the parser seek function.
+ *
+ * Return value: -1 on error.
+ **/
+int
+camel_mime_parser_init_with_stream(CamelMimeParser *m, CamelStream *stream)
+{
+ struct _header_scan_state *s = _PRIVATE(m);
+
+ return folder_scan_init_with_stream(s, stream);
+}
+
+/**
+ * camel_mime_parser_scan_from:
+ * @parser: MIME parser object
+ * @scan_from: #TRUE if the scanner should scan From lines.
+ *
+ * Tell the scanner if it should scan "^From " lines or not.
+ *
+ * If the scanner is scanning from lines, two additional
+ * states CAMEL_MIME_PARSER_STATE_FROM and CAMEL_MIME_PARSER_STATE_FROM_END will be returned
+ * to the caller during parsing.
+ *
+ * This may also be preceeded by an optional
+ * CAMEL_MIME_PARSER_STATE_PRE_FROM state which contains the scanned data
+ * found before the From line is encountered. See also
+ * scan_pre_from().
+ **/
+void
+camel_mime_parser_scan_from (CamelMimeParser *parser, gboolean scan_from)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ s->scan_from = scan_from;
+}
+
+/**
+ * camel_mime_parser_scan_pre_from:
+ * @parser: MIME parser object
+ * @scan_pre_from: #TRUE if we want to get pre-from data.
+ *
+ * Tell the scanner whether we want to know abou the pre-from
+ * data during a scan. If we do, then we may get an additional
+ * state CAMEL_MIME_PARSER_STATE_PRE_FROM which returns the specified data.
+ **/
+void
+camel_mime_parser_scan_pre_from (CamelMimeParser *parser, gboolean scan_pre_from)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ s->scan_pre_from = scan_pre_from;
+}
+
+/**
+ * camel_mime_parser_content_type:
+ * @parser: MIME parser object
+ *
+ * Get the content type defined in the current part.
+ *
+ * Return value: A content_type structure, or NULL if there
+ * is no content-type defined for this part of state of the
+ * parser.
+ **/
+CamelContentType *
+camel_mime_parser_content_type (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ /* FIXME: should this search up until it's found the 'right'
+ content-type? can it? */
+ if (s->parts)
+ return s->parts->content_type;
+
+ return NULL;
+}
+
+/**
+ * camel_mime_parser_unstep:
+ * @parser: MIME parser object
+ *
+ * Cause the last step operation to repeat itself. If this is
+ * called repeated times, then the same step will be repeated
+ * that many times.
+ *
+ * Note that it is not possible to scan back using this function,
+ * only to have a way of peeking the next state.
+ **/
+void
+camel_mime_parser_unstep (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ s->unstep++;
+}
+
+/**
+ * camel_mime_parser_drop_step:
+ * @parser: MIME parser object
+ *
+ * Drop the last step call. This should only be used
+ * in conjunction with seeking of the stream as the
+ * stream may be in an undefined state relative to the
+ * state of the parser.
+ *
+ * Use this call with care.
+ **/
+void
+camel_mime_parser_drop_step (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ s->unstep = 0;
+ folder_scan_drop_step(s);
+}
+
+/**
+ * camel_mime_parser_step:
+ * @parser: MIME parser object
+ * @databuffer: Pointer to accept a pointer to the data
+ * associated with this step (if any). May be #NULL,
+ * in which case datalength is also ingored.
+ * @datalength: Pointer to accept a pointer to the data
+ * length associated with this step (if any).
+ *
+ * Parse the next part of the MIME message. If _unstep()
+ * has been called, then continue to return the same state
+ * for that many calls.
+ *
+ * If the step is CAMEL_MIME_PARSER_STATE_BODY then the databuffer and datalength
+ * pointers will be setup to point to the internal data buffer
+ * of the scanner and may be processed as required. Any
+ * filters will have already been applied to this data.
+ *
+ * Refer to the state diagram elsewhere for a full listing of
+ * the states an application is gauranteed to get from the
+ * scanner.
+ *
+ * Return value: The current new state of the parser
+ * is returned.
+ **/
+enum _camel_mime_parser_state
+camel_mime_parser_step (CamelMimeParser *parser, char **databuffer, size_t *datalength)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ d(printf("OLD STATE: '%s' :\n", states[s->state]));
+
+ if (s->unstep <= 0) {
+ char *dummy;
+ size_t dummylength;
+
+ if (databuffer == NULL) {
+ databuffer = &dummy;
+ datalength = &dummylength;
+ }
+
+ folder_scan_step(s, databuffer, datalength);
+ } else
+ s->unstep--;
+
+ d(printf("NEW STATE: '%s' :\n", states[s->state]));
+
+ return s->state;
+}
+
+/**
+ * camel_mime_parser_read:
+ * @parser: MIME parser object
+ * @databuffer:
+ * @len:
+ *
+ * Read at most @len bytes from the internal mime parser buffer.
+ *
+ * Returns the address of the internal buffer in @databuffer,
+ * and the length of useful data.
+ *
+ * @len may be specified as INT_MAX, in which case you will
+ * get the full remainder of the buffer at each call.
+ *
+ * Note that no parsing of the data read through this function
+ * occurs, so no state changes occur, but the seek position
+ * is updated appropriately.
+ *
+ * Return value: The number of bytes available, or -1 on error.
+ **/
+int
+camel_mime_parser_read (CamelMimeParser *parser, const char **databuffer, int len)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+ int there;
+
+ if (len == 0)
+ return 0;
+
+ d(printf("parser::read() reading %d bytes\n", len));
+
+ there = MIN(s->inend - s->inptr, len);
+ d(printf("parser::read() there = %d bytes\n", there));
+ if (there > 0) {
+ *databuffer = s->inptr;
+ s->inptr += there;
+ return there;
+ }
+
+ if (folder_read(s) == -1)
+ return -1;
+
+ there = MIN(s->inend - s->inptr, len);
+ d(printf("parser::read() had to re-read, now there = %d bytes\n", there));
+
+ *databuffer = s->inptr;
+ s->inptr += there;
+
+ return there;
+}
+
+/**
+ * camel_mime_parser_tell:
+ * @parser: MIME parser object
+ *
+ * Return the current scanning offset. The meaning of this
+ * value will depend on the current state of the parser.
+ *
+ * An incomplete listing of the states:
+ *
+ * CAMEL_MIME_PARSER_STATE_INITIAL, The start of the current message.
+ * CAMEL_MIME_PARSER_STATE_HEADER, CAMEL_MIME_PARSER_STATE_MESSAGE, CAMEL_MIME_PARSER_STATE_MULTIPART, the character
+ * position immediately after the end of the header.
+ * CAMEL_MIME_PARSER_STATE_BODY, Position within the message of the start
+ * of the current data block.
+ * CAMEL_MIME_PARSER_STATE_*_END, The position of the character starting
+ * the next section of the scan (the last position + 1 of
+ * the respective current state).
+ *
+ * Return value: See above.
+ **/
+off_t
+camel_mime_parser_tell (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return folder_tell(s);
+}
+
+/**
+ * camel_mime_parser_tell_start_headers:
+ * @parser: MIME parser object
+ *
+ * Find out the position within the file of where the
+ * headers started, this is cached by the parser
+ * at the time.
+ *
+ * Return value: The header start position, or -1 if
+ * no headers were scanned in the current state.
+ **/
+off_t
+camel_mime_parser_tell_start_headers (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->start_of_headers;
+}
+
+/**
+ * camel_mime_parser_tell_start_from:
+ * @parser: MIME parser object
+ *
+ * If the parser is scanning From lines, then this returns
+ * the position of the start of the From line.
+ *
+ * Return value: The start of the from line, or -1 if there
+ * was no From line, or From lines are not being scanned.
+ **/
+off_t
+camel_mime_parser_tell_start_from (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->start_of_from;
+}
+
+/**
+ * camel_mime_parser_tell_start_boundary:
+ * @parser: MIME parser object
+ *
+ * When parsing a multipart, this returns the start of the last
+ * boundary.
+ *
+ * Return value: The start of the boundary, or -1 if there
+ * was no boundary encountered yet.
+ **/
+off_t
+camel_mime_parser_tell_start_boundary(CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->start_of_boundary;
+}
+
+/**
+ * camel_mime_parser_seek:
+ * @parser: MIME parser object
+ * @offset: Number of bytes to offset the seek by.
+ * @whence: SEEK_SET, SEEK_CUR, SEEK_END
+ *
+ * Reset the source position to a known value.
+ *
+ * Note that if the source stream/descriptor was not
+ * positioned at 0 to begin with, and an absolute seek
+ * is specified (whence != SEEK_CUR), then the seek
+ * position may not match the desired seek position.
+ *
+ * Return value: The new seek offset, or -1 on
+ * an error (for example, trying to seek on a non-seekable
+ * stream or file descriptor).
+ **/
+off_t
+camel_mime_parser_seek(CamelMimeParser *parser, off_t offset, int whence)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return folder_seek(s, offset, whence);
+}
+
+/**
+ * camel_mime_parser_state:
+ * @parser: MIME parser object
+ *
+ * Get the current parser state.
+ *
+ * Return value: The current parser state.
+ **/
+enum _camel_mime_parser_state
+camel_mime_parser_state (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->state;
+}
+
+/**
+ * camel_mime_parser_push_state:
+ * @mp: MIME parser object
+ * @newstate: New state
+ * @boundary: Boundary marker for state.
+ *
+ * Pre-load a new parser state. Used to post-parse multipart content
+ * without headers.
+ **/
+void
+camel_mime_parser_push_state(CamelMimeParser *mp, enum _camel_mime_parser_state newstate, const char *boundary)
+{
+ struct _header_scan_stack *h;
+ struct _header_scan_state *s = _PRIVATE(mp);
+
+ h = g_malloc0(sizeof(*h));
+ h->boundarylen = strlen(boundary)+2;
+ h->boundarylenfinal = h->boundarylen+2;
+ h->boundary = g_malloc(h->boundarylen+3);
+ sprintf(h->boundary, "--%s--", boundary);
+ folder_push_part(s, h);
+ s->state = newstate;
+}
+
+/**
+ * camel_mime_parser_stream:
+ * @parser: MIME parser object
+ *
+ * Get the stream, if any, the parser has been initialised
+ * with. May be used to setup sub-streams, but should not
+ * be read from directly (without saving and restoring
+ * the seek position in between).
+ *
+ * Return value: The stream from _init_with_stream(), or NULL
+ * if the parser is reading from a file descriptor or is
+ * uninitialised.
+ **/
+CamelStream *
+camel_mime_parser_stream (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->stream;
+}
+
+/**
+ * camel_mime_parser_fd:
+ * @parser: MIME parser object
+ *
+ * Return the file descriptor, if any, the parser has been
+ * initialised with.
+ *
+ * Should not be read from unless the parser it to terminate,
+ * or the seek offset can be reset before the next parse
+ * step.
+ *
+ * Return value: The file descriptor or -1 if the parser
+ * is reading from a stream or has not been initialised.
+ **/
+int
+camel_mime_parser_fd (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->fd;
+}
+
+/* Return errno of the parser, incase any error occured during processing */
+int
+camel_mime_parser_errno (CamelMimeParser *parser)
+{
+ struct _header_scan_state *s = _PRIVATE (parser);
+
+ return s->ioerrno;
+}
+
+/* ********************************************************************** */
+/* Implementation */
+/* ********************************************************************** */
+
+/* read the next bit of data, ensure there is enough room 'atleast' bytes */
+static int
+folder_read(struct _header_scan_state *s)
+{
+ int len;
+ int inoffset;
+
+ if (s->inptr<s->inend-s->atleast || s->eof)
+ return s->inend-s->inptr;
+#ifdef PURIFY
+ purify_watch_remove(inend_id);
+ purify_watch_remove(inbuffer_id);
+#endif
+ /* check for any remaning bytes (under the atleast limit( */
+ inoffset = s->inend - s->inptr;
+ if (inoffset>0) {
+ memcpy(s->inbuf, s->inptr, inoffset);
+ }
+ if (s->stream) {
+ len = camel_stream_read(s->stream, s->inbuf+inoffset, SCAN_BUF-inoffset);
+ } else {
+ len = read(s->fd, s->inbuf+inoffset, SCAN_BUF-inoffset);
+ }
+ r(printf("read %d bytes, offset = %d\n", len, inoffset));
+ if (len>=0) {
+ /* add on the last read block */
+ s->seek += s->inptr - s->inbuf;
+ s->inptr = s->inbuf;
+ s->inend = s->inbuf+len+inoffset;
+ s->eof = (len == 0);
+ r(printf("content = %d '%.*s'\n",s->inend - s->inptr, s->inend - s->inptr, s->inptr));
+ } else {
+ s->ioerrno = errno?errno:EIO;
+ }
+
+ g_assert(s->inptr<=s->inend);
+#ifdef PURIFY
+ inend_id = purify_watch(&s->inend);
+ inbuffer_id = purify_watch_n(s->inend+1, SCAN_HEAD-1, "rw");
+#endif
+ r(printf("content = %d '%.*s'\n", s->inend - s->inptr, s->inend - s->inptr, s->inptr));
+ /* set a sentinal, for the inner loops to check against */
+ s->inend[0] = '\n';
+ return s->inend-s->inptr;
+}
+
+/* return the current absolute position of the data pointer */
+static off_t
+folder_tell(struct _header_scan_state *s)
+{
+ return s->seek + (s->inptr - s->inbuf);
+}
+
+/*
+ need some way to prime the parser state, so this actually works for
+ other than top-level messages
+*/
+static off_t
+folder_seek(struct _header_scan_state *s, off_t offset, int whence)
+{
+ off_t newoffset;
+
+ if (s->stream) {
+ if (CAMEL_IS_SEEKABLE_STREAM(s->stream)) {
+ /* NOTE: assumes whence seekable stream == whence libc, which is probably
+ the case (or bloody well should've been) */
+ newoffset = camel_seekable_stream_seek((CamelSeekableStream *)s->stream, offset, whence);
+ } else {
+ newoffset = -1;
+ errno = EINVAL;
+ }
+ } else {
+ newoffset = lseek(s->fd, offset, whence);
+ }
+#ifdef PURIFY
+ purify_watch_remove(inend_id);
+ purify_watch_remove(inbuffer_id);
+#endif
+ if (newoffset != -1) {
+ s->seek = newoffset;
+ s->inptr = s->inbuf;
+ s->inend = s->inbuf;
+ s->eof = FALSE;
+ } else {
+ s->ioerrno = errno?errno:EIO;
+ }
+#ifdef PURIFY
+ inend_id = purify_watch(&s->inend);
+ inbuffer_id = purify_watch_n(s->inend+1, SCAN_HEAD-1, "rw");
+#endif
+ return newoffset;
+}
+
+static void
+folder_push_part(struct _header_scan_state *s, struct _header_scan_stack *h)
+{
+ if (s->parts && s->parts->atleast > h->boundarylenfinal)
+ h->atleast = s->parts->atleast;
+ else
+ h->atleast = MAX(h->boundarylenfinal, 1);
+
+ h->parent = s->parts;
+ s->parts = h;
+}
+
+static void
+folder_pull_part(struct _header_scan_state *s)
+{
+ struct _header_scan_stack *h;
+
+ h = s->parts;
+ if (h) {
+ s->parts = h->parent;
+ g_free(h->boundary);
+#ifdef MEMPOOL
+ e_mempool_destroy(h->pool);
+#else
+ camel_header_raw_clear(&h->headers);
+#endif
+ camel_content_type_unref(h->content_type);
+ if (h->pretext)
+ g_byte_array_free(h->pretext, TRUE);
+ if (h->posttext)
+ g_byte_array_free(h->posttext, TRUE);
+ if (h->from_line)
+ g_byte_array_free(h->from_line, TRUE);
+ g_free(h);
+ } else {
+ g_warning("Header stack underflow!\n");
+ }
+}
+
+static int
+folder_scan_skip_line(struct _header_scan_state *s, GByteArray *save)
+{
+ int atleast = s->atleast;
+ register char *inptr, *inend, c;
+ int len;
+
+ s->atleast = 1;
+
+ d(printf("skipping line\n"));
+
+ while ( (len = folder_read(s)) > 0 && len > s->atleast) { /* ensure we have at least enough room here */
+ inptr = s->inptr;
+ inend = s->inend;
+
+ c = -1;
+ while (inptr<inend
+ && (c = *inptr++)!='\n') {
+ d(printf("(%2x,%c)", c, isprint(c)?c:'.'));
+ ;
+ }
+
+ if (save)
+ g_byte_array_append(save, s->inptr, inptr-s->inptr);
+
+ s->inptr = inptr;
+
+ if (c=='\n') {
+ s->atleast = atleast;
+ return 0;
+ }
+ }
+
+ d(printf("couldn't find end of line?\n"));
+
+ s->atleast = atleast;
+
+ return -1; /* not found */
+}
+
+/* TODO: Is there any way to make this run faster? It gets called a lot ... */
+static struct _header_scan_stack *
+folder_boundary_check(struct _header_scan_state *s, const char *boundary, int *lastone)
+{
+ struct _header_scan_stack *part;
+ int len = s->inend - boundary; /* make sure we dont access past the buffer */
+
+ h(printf("checking boundary marker upto %d bytes\n", len));
+ part = s->parts;
+ while (part) {
+ h(printf(" boundary: %s\n", part->boundary));
+ h(printf(" against: '%.*s'\n", part->boundarylen, boundary));
+ if (part->boundary
+ && part->boundarylen <= len
+ && memcmp(boundary, part->boundary, part->boundarylen)==0) {
+ h(printf("matched boundary: %s\n", part->boundary));
+ /* again, make sure we're in range */
+ if (part->boundarylenfinal <= len) {
+ int extra = part->boundarylenfinal - part->boundarylen;
+
+ /* check the extra stuff on an final boundary, normally -- for mime parts */
+ if (extra>0) {
+ *lastone = memcmp(&boundary[part->boundarylen],
+ &part->boundary[part->boundarylen],
+ extra) == 0;
+ } else {
+ *lastone = TRUE;
+ }
+ h(printf("checking lastone = %s\n", *lastone?"TRUE":"FALSE"));
+ } else {
+ h(printf("not enough room to check last one?\n"));
+ *lastone = FALSE;
+ }
+ /*printf("ok, we found it! : %s \n", (*lastone)?"Last one":"More to come?");*/
+ return part;
+ }
+ part = part->parent;
+ }
+ return NULL;
+}
+
+#ifdef MEMPOOL
+static void
+header_append_mempool(struct _header_scan_state *s, struct _header_scan_stack *h, char *header, int offset)
+{
+ struct _camel_header_raw *l, *n;
+ char *content;
+
+ content = strchr(header, ':');
+ if (content) {
+ register int len;
+ n = e_mempool_alloc(h->pool, sizeof(*n));
+ n->next = NULL;
+
+ len = content-header;
+ n->name = e_mempool_alloc(h->pool, len+1);
+ memcpy(n->name, header, len);
+ n->name[len] = 0;
+
+ content++;
+
+ len = s->outptr - content;
+ n->value = e_mempool_alloc(h->pool, len+1);
+ memcpy(n->value, content, len);
+ n->value[len] = 0;
+
+ n->offset = offset;
+
+ l = (struct _camel_header_raw *)&h->headers;
+ while (l->next) {
+ l = l->next;
+ }
+ l->next = n;
+ }
+
+}
+
+#define header_raw_append_parse(a, b, c) (header_append_mempool(s, h, b, c))
+
+#endif
+
+/* Copy the string start->inptr into the header buffer (s->outbuf),
+ grow if necessary
+ remove trailing \r chars (\n's assumed already removed)
+ and track the start offset of the header */
+/* Basically an optimised version of g_byte_array_append() */
+#define header_append(s, start, inptr) \
+{ \
+ register int headerlen = inptr-start; \
+ \
+ if (headerlen > 0) { \
+ if (headerlen >= (s->outend - s->outptr)) { \
+ register char *outnew; \
+ register int len = ((s->outend - s->outbuf)+headerlen)*2+1; \
+ outnew = g_realloc(s->outbuf, len); \
+ s->outptr = s->outptr - s->outbuf + outnew; \
+ s->outbuf = outnew; \
+ s->outend = outnew + len; \
+ } \
+ if (start[headerlen-1] == '\r') \
+ headerlen--; \
+ memcpy(s->outptr, start, headerlen); \
+ s->outptr += headerlen; \
+ } \
+ if (s->header_start == -1) \
+ s->header_start = (start-s->inbuf) + s->seek; \
+}
+
+static struct _header_scan_stack *
+folder_scan_header(struct _header_scan_state *s, int *lastone)
+{
+ int atleast = s->atleast, newatleast;
+ char *start = NULL;
+ int len;
+ struct _header_scan_stack *h;
+ char *inend;
+ register char *inptr;
+
+ h(printf("scanning first bit\n"));
+
+ h = g_malloc0(sizeof(*h));
+#ifdef MEMPOOL
+ h->pool = e_mempool_new(8192, 4096, E_MEMPOOL_ALIGN_STRUCT);
+#endif
+
+ if (s->parts)
+ newatleast = s->parts->atleast;
+ else
+ newatleast = 1;
+ *lastone = FALSE;
+
+ do {
+ s->atleast = newatleast;
+
+ h(printf("atleast = %d\n", s->atleast));
+
+ while ((len = folder_read(s))>0 && len >= s->atleast) { /* ensure we have at least enough room here */
+ inptr = s->inptr;
+ inend = s->inend-s->atleast+1;
+
+ while (inptr<inend) {
+ if (!s->midline) {
+ if (folder_boundary_check(s, inptr, lastone)) {
+ if ((s->outptr>s->outbuf))
+ goto header_truncated; /* may not actually be truncated */
+
+ goto header_done;
+ }
+ }
+
+ start = inptr;
+
+ /* goto next line/sentinal */
+ while ((*inptr++)!='\n')
+ ;
+
+ g_assert(inptr<=s->inend+1);
+
+ /* check for sentinal or real end of line */
+ if (inptr > inend) {
+ h(printf("not at end of line yet, going further\n"));
+ /* didn't find end of line within our allowed area */
+ inptr = inend;
+ s->midline = TRUE;
+ header_append(s, start, inptr);
+ } else {
+ h(printf("got line part: '%.*s'\n", inptr-1-start, start));
+ /* got a line, strip and add it, process it */
+ s->midline = FALSE;
+#ifdef PRESERVE_HEADERS
+ header_append(s, start, inptr);
+#else
+ header_append(s, start, inptr-1);
+#endif
+ /* check for end of headers */
+ if (s->outbuf == s->outptr)
+ goto header_done;
+
+ /* check for continuation/compress headers, we have atleast 1 char here to work with */
+ if (inptr[0] == ' ' || inptr[0] == '\t') {
+ h(printf("continuation\n"));
+#ifndef PRESERVE_HEADERS
+ /* TODO: this wont catch multiple space continuation across a read boundary, but
+ that is assumed rare, and not fatal anyway */
+ do
+ inptr++;
+ while (*inptr == ' ' || *inptr == '\t');
+ inptr--;
+ *inptr = ' ';
+#endif
+ } else {
+ /* otherwise, complete header, add it */
+#ifdef PRESERVE_HEADERS
+ s->outptr--;
+ if (s->outptr[-1] == '\r')
+ s->outptr--;
+#endif
+ s->outptr[0] = 0;
+
+ h(printf("header '%s' at %d\n", s->outbuf, (int)s->header_start));
+
+ header_raw_append_parse(&h->headers, s->outbuf, s->header_start);
+ s->outptr = s->outbuf;
+ s->header_start = -1;
+ }
+ }
+ }
+ s->inptr = inptr;
+ }
+ h(printf("end of file? read %d bytes\n", len));
+ newatleast = 1;
+ } while (s->atleast > 1);
+
+ if ((s->outptr > s->outbuf) || s->inend > s->inptr) {
+ start = s->inptr;
+ inptr = s->inend;
+ if (inptr > start) {
+ if (inptr[-1] == '\n')
+ inptr--;
+ }
+ goto header_truncated;
+ }
+
+ s->atleast = atleast;
+
+ return h;
+
+header_truncated:
+ header_append(s, start, inptr);
+
+ s->outptr[0] = 0;
+ if (s->outbuf == s->outptr)
+ goto header_done;
+
+ header_raw_append_parse(&h->headers, s->outbuf, s->header_start);
+
+ s->outptr = s->outbuf;
+header_done:
+ s->inptr = inptr;
+ s->atleast = atleast;
+ s->header_start = -1;
+ return h;
+}
+
+static struct _header_scan_stack *
+folder_scan_content(struct _header_scan_state *s, int *lastone, char **data, size_t *length)
+{
+ int atleast = s->atleast, newatleast;
+ register char *inptr;
+ char *inend;
+ char *start;
+ int len;
+ struct _header_scan_stack *part;
+ int onboundary = FALSE;
+
+ c(printf("scanning content\n"));
+
+ part = s->parts;
+ if (part)
+ newatleast = part->atleast;
+ else
+ newatleast = 1;
+ *lastone = FALSE;
+
+ c(printf("atleast = %d\n", newatleast));
+
+ do {
+ s->atleast = newatleast;
+
+ while ((len = folder_read(s))>0 && len >= s->atleast) { /* ensure we have at least enough room here */
+ inptr = s->inptr;
+ if (s->eof)
+ inend = s->inend;
+ else
+ inend = s->inend-s->atleast+1;
+ start = inptr;
+
+ c(printf("inptr = %p, inend = %p\n", inptr, inend));
+
+ while (inptr<inend) {
+ if (!s->midline
+ && (part = folder_boundary_check(s, inptr, lastone))) {
+ onboundary = TRUE;
+
+ /* since we truncate the boundary data, we need at least 1 char here spare,
+ to remain in the same state */
+ if ( (inptr-start) > 1)
+ goto content;
+
+ /* otherwise, jump to the state of the boundary we actually found */
+ goto normal_exit;
+ }
+
+ /* goto the next line */
+ while ((*inptr++)!='\n')
+ ;
+
+ /* check the sentinal, if we went past the atleast limit, and reset it to there */
+ if (inptr > inend) {
+ s->midline = TRUE;
+ inptr = inend;
+ } else {
+ s->midline = FALSE;
+ }
+ }
+
+ c(printf("ran out of input, dumping what i have (%d) bytes midline = %s\n",
+ inptr-start, s->midline?"TRUE":"FALSE"));
+ goto content;
+ }
+ newatleast = 1;
+ } while (s->atleast > 1);
+
+ c(printf("length read = %d\n", len));
+
+ if (s->inend > s->inptr) {
+ start = s->inptr;
+ inptr = s->inend;
+ goto content;
+ }
+
+ *length = 0;
+ s->atleast = atleast;
+ return NULL;
+
+content:
+ /* treat eof as the last boundary in From mode */
+ if (s->scan_from && s->eof && s->atleast <= 1) {
+ onboundary = TRUE;
+ part = NULL;
+ } else {
+ part = s->parts;
+ }
+normal_exit:
+ s->atleast = atleast;
+ s->inptr = inptr;
+
+ *data = start;
+ /* if we hit a boundary, we should not include the closing \n */
+ if (onboundary && (inptr-start)>0)
+ *length = inptr-start-1;
+ else
+ *length = inptr-start;
+
+ /*printf("got %scontent: '%.*s'\n", s->midline?"partial ":"", inptr-start, start);*/
+
+ return part;
+}
+
+
+static void
+folder_scan_close(struct _header_scan_state *s)
+{
+ g_free(s->realbuf);
+ g_free(s->outbuf);
+ while (s->parts)
+ folder_pull_part(s);
+ if (s->fd != -1)
+ close(s->fd);
+ if (s->stream) {
+ camel_object_unref((CamelObject *)s->stream);
+ }
+ g_free(s);
+}
+
+
+static struct _header_scan_state *
+folder_scan_init(void)
+{
+ struct _header_scan_state *s;
+
+ s = g_malloc(sizeof(*s));
+
+ s->fd = -1;
+ s->stream = NULL;
+ s->ioerrno = 0;
+
+ s->outbuf = g_malloc(1024);
+ s->outptr = s->outbuf;
+ s->outend = s->outbuf+1024;
+
+ s->realbuf = g_malloc(SCAN_BUF + SCAN_HEAD*2);
+ s->inbuf = s->realbuf + SCAN_HEAD;
+ s->inptr = s->inbuf;
+ s->inend = s->inbuf;
+ s->atleast = 0;
+
+ s->seek = 0; /* current character position in file of the last read block */
+ s->unstep = 0;
+
+ s->header_start = -1;
+
+ s->start_of_from = -1;
+ s->start_of_headers = -1;
+ s->start_of_boundary = -1;
+
+ s->midline = FALSE;
+ s->scan_from = FALSE;
+ s->scan_pre_from = FALSE;
+ s->eof = FALSE;
+
+ s->filters = NULL;
+ s->filterid = 1;
+
+ s->parts = NULL;
+
+ s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
+ return s;
+}
+
+static void
+drop_states(struct _header_scan_state *s)
+{
+ while (s->parts) {
+ folder_scan_drop_step(s);
+ }
+ s->unstep = 0;
+ s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
+}
+
+static void
+folder_scan_reset(struct _header_scan_state *s)
+{
+ drop_states(s);
+ s->inend = s->inbuf;
+ s->inptr = s->inbuf;
+ s->inend[0] = '\n';
+ if (s->fd != -1) {
+ close(s->fd);
+ s->fd = -1;
+ }
+ if (s->stream) {
+ camel_object_unref((CamelObject *)s->stream);
+ s->stream = NULL;
+ }
+ s->ioerrno = 0;
+ s->eof = FALSE;
+}
+
+static int
+folder_scan_init_with_fd(struct _header_scan_state *s, int fd)
+{
+ folder_scan_reset(s);
+ s->fd = fd;
+
+ return 0;
+}
+
+static int
+folder_scan_init_with_stream(struct _header_scan_state *s, CamelStream *stream)
+{
+ folder_scan_reset(s);
+ s->stream = stream;
+ camel_object_ref((CamelObject *)stream);
+
+ return 0;
+}
+
+#define USE_FROM
+
+static void
+folder_scan_step(struct _header_scan_state *s, char **databuffer, size_t *datalength)
+{
+ struct _header_scan_stack *h, *hb;
+ const char *content;
+ const char *bound;
+ int type, state, seenlast;
+ CamelContentType *ct = NULL;
+ struct _header_scan_filter *f;
+ size_t presize;
+
+/* printf("\nSCAN PASS: state = %d '%s'\n", s->state, states[s->state]);*/
+
+tail_recurse:
+ d({
+ printf("\nSCAN STACK:\n");
+ printf(" '%s' :\n", states[s->state]);
+ hb = s->parts;
+ while (hb) {
+ printf(" '%s' : %s ", states[hb->savestate], hb->boundary);
+ if (hb->content_type) {
+ printf("(%s/%s)", hb->content_type->type, hb->content_type->subtype);
+ } else {
+ printf("(default)");
+ }
+ printf("\n");
+ hb = hb->parent;
+ }
+ printf("\n");
+ });
+
+ switch (s->state) {
+
+#ifdef USE_FROM
+ case CAMEL_MIME_PARSER_STATE_INITIAL:
+ if (s->scan_from) {
+ h = g_malloc0(sizeof(*h));
+ h->boundary = g_strdup("From ");
+ h->boundarylen = strlen(h->boundary);
+ h->boundarylenfinal = h->boundarylen;
+ h->from_line = g_byte_array_new();
+ folder_push_part(s, h);
+ s->state = CAMEL_MIME_PARSER_STATE_PRE_FROM;
+ } else {
+ s->start_of_from = -1;
+ goto scan_header;
+ }
+
+ case CAMEL_MIME_PARSER_STATE_PRE_FROM:
+
+ h = s->parts;
+ do {
+ hb = folder_scan_content(s, &state, databuffer, datalength);
+ if (s->scan_pre_from && *datalength > 0) {
+ d(printf("got pre-from content %d bytes\n", *datalength));
+ return;
+ }
+ } while (hb==h && *datalength>0);
+
+ if (*datalength==0 && hb==h) {
+ d(printf("found 'From '\n"));
+ s->start_of_from = folder_tell(s);
+ folder_scan_skip_line(s, h->from_line);
+ h->savestate = CAMEL_MIME_PARSER_STATE_INITIAL;
+ s->state = CAMEL_MIME_PARSER_STATE_FROM;
+ } else {
+ folder_pull_part(s);
+ s->state = CAMEL_MIME_PARSER_STATE_EOF;
+ }
+ return;
+#else
+ case CAMEL_MIME_PARSER_STATE_INITIAL:
+ case CAMEL_MIME_PARSER_STATE_PRE_FROM:
+#endif /* !USE_FROM */
+
+ scan_header:
+ case CAMEL_MIME_PARSER_STATE_FROM:
+ s->start_of_headers = folder_tell(s);
+ h = folder_scan_header(s, &state);
+#ifdef USE_FROM
+ if (s->scan_from)
+ h->savestate = CAMEL_MIME_PARSER_STATE_FROM_END;
+ else
+#endif
+ h->savestate = CAMEL_MIME_PARSER_STATE_EOF;
+
+ /* FIXME: should this check for MIME-Version: 1.0 as well? */
+
+ type = CAMEL_MIME_PARSER_STATE_HEADER;
+ if ( (content = camel_header_raw_find(&h->headers, "Content-Type", NULL))
+ && (ct = camel_content_type_decode(content))) {
+ if (!g_ascii_strcasecmp(ct->type, "multipart")) {
+ if (!camel_content_type_is(ct, "multipart", "signed")
+ && (bound = camel_content_type_param(ct, "boundary"))) {
+ d(printf("multipart, boundary = %s\n", bound));
+ h->boundarylen = strlen(bound)+2;
+ h->boundarylenfinal = h->boundarylen+2;
+ h->boundary = g_malloc(h->boundarylen+3);
+ sprintf(h->boundary, "--%s--", bound);
+ type = CAMEL_MIME_PARSER_STATE_MULTIPART;
+ } else {
+ /*camel_content_type_unref(ct);
+ ct = camel_content_type_decode("text/plain");*/
+/* We can't quite do this, as it will mess up all the offsets ... */
+/* camel_header_raw_replace(&h->headers, "Content-Type", "text/plain", offset);*/
+ /*g_warning("Multipart with no boundary, treating as text/plain");*/
+ }
+ } else if (!strcasecmp(ct->type, "message")) {
+ if (!strcasecmp(ct->subtype, "rfc822")
+ || !strcasecmp(ct->subtype, "news")
+ /*|| !g_ascii_strcasecmp(ct->subtype, "partial")*/) {
+ type = CAMEL_MIME_PARSER_STATE_MESSAGE;
+ }
+ }
+ } else {
+ /* make the default type for multipart/digest be message/rfc822 */
+ if ((s->parts
+ && camel_content_type_is(s->parts->content_type, "multipart", "digest"))) {
+ ct = camel_content_type_decode("message/rfc822");
+ type = CAMEL_MIME_PARSER_STATE_MESSAGE;
+ d(printf("parent was multipart/digest, autoupgrading to message/rfc822?\n"));
+ /* maybe we should do this too?
+ header_raw_append_parse(&h->headers, "Content-Type: message/rfc822", -1);*/
+ } else {
+ ct = camel_content_type_decode("text/plain");
+ }
+ }
+ h->content_type = ct;
+ folder_push_part(s, h);
+ s->state = type;
+ return;
+
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ s->state = CAMEL_MIME_PARSER_STATE_BODY;
+
+ case CAMEL_MIME_PARSER_STATE_BODY:
+ h = s->parts;
+ *datalength = 0;
+ presize = SCAN_HEAD;
+ f = s->filters;
+
+ do {
+ hb = folder_scan_content (s, &state, databuffer, datalength);
+
+ d(printf ("\n\nOriginal content: '"));
+ d(fwrite(*databuffer, sizeof(char), *datalength, stdout));
+ d(printf("'\n"));
+
+ if (*datalength > 0) {
+ while (f) {
+ camel_mime_filter_filter(f->filter, *databuffer, *datalength, presize,
+ databuffer, datalength, &presize);
+ d(printf("Filtered content (%s): '", ((CamelObject *)f->filter)->klass->name));
+ d(fwrite(*databuffer, sizeof(char), *datalength, stdout));
+ d(printf("'\n"));
+ f = f->next;
+ }
+ return;
+ }
+ } while (hb == h && *datalength > 0);
+
+ /* check for any filter completion data */
+ while (f) {
+ camel_mime_filter_complete(f->filter, *databuffer, *datalength, presize,
+ databuffer, datalength, &presize);
+ f = f->next;
+ }
+
+ if (*datalength > 0)
+ return;
+
+ s->state = CAMEL_MIME_PARSER_STATE_BODY_END;
+ break;
+
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ h = s->parts;
+ /* This mess looks for the next boundary on this
+ level. Once it finds the last one, it keeps going,
+ looking for post-multipart content ('postface').
+ Because messages might have duplicate boundaries for
+ different parts, it makes sure it stops if its already
+ found an end boundary for this part. It handles
+ truncated and missing boundaries appropriately too. */
+ seenlast = FALSE;
+ do {
+ do {
+ hb = folder_scan_content(s, &state, databuffer, datalength);
+ if (*datalength>0) {
+ /* instead of a new state, we'll just store it locally and provide
+ an accessor function */
+ d(printf("Multipart %s Content %p: '%.*s'\n",
+ h->prestage>0?"post":"pre", h, *datalength, *databuffer));
+ if (h->prestage > 0) {
+ if (h->posttext == NULL)
+ h->posttext = g_byte_array_new();
+ g_byte_array_append(h->posttext, *databuffer, *datalength);
+ } else {
+ if (h->pretext == NULL)
+ h->pretext = g_byte_array_new();
+ g_byte_array_append(h->pretext, *databuffer, *datalength);
+ }
+ }
+ } while (hb==h && *datalength>0);
+ h->prestage++;
+ if (*datalength==0 && hb==h && !seenlast) {
+ d(printf("got boundary: %s last=%d\n", hb->boundary, state));
+ s->start_of_boundary = folder_tell(s);
+ folder_scan_skip_line(s, NULL);
+ if (!state) {
+ s->state = CAMEL_MIME_PARSER_STATE_FROM;
+ folder_scan_step(s, databuffer, datalength);
+ s->parts->savestate = CAMEL_MIME_PARSER_STATE_MULTIPART; /* set return state for the new head part */
+ return;
+ } else
+ seenlast = TRUE;
+ } else {
+ break;
+ }
+ } while (1);
+
+ s->state = CAMEL_MIME_PARSER_STATE_MULTIPART_END;
+ break;
+
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ s->state = CAMEL_MIME_PARSER_STATE_FROM;
+ folder_scan_step(s, databuffer, datalength);
+ s->parts->savestate = CAMEL_MIME_PARSER_STATE_MESSAGE_END;
+ break;
+
+ case CAMEL_MIME_PARSER_STATE_FROM_END:
+ case CAMEL_MIME_PARSER_STATE_BODY_END:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART_END:
+ case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
+ s->state = s->parts->savestate;
+ folder_pull_part(s);
+ if (s->state & CAMEL_MIME_PARSER_STATE_END)
+ return;
+ goto tail_recurse;
+
+ case CAMEL_MIME_PARSER_STATE_EOF:
+ return;
+
+ default:
+ g_warning("Invalid state in camel-mime-parser: %d", s->state);
+ break;
+ }
+
+ return;
+}
+
+/* drops the current state back one */
+static void
+folder_scan_drop_step(struct _header_scan_state *s)
+{
+ switch (s->state) {
+ case CAMEL_MIME_PARSER_STATE_EOF:
+ s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
+ case CAMEL_MIME_PARSER_STATE_INITIAL:
+ return;
+
+ case CAMEL_MIME_PARSER_STATE_FROM:
+ case CAMEL_MIME_PARSER_STATE_PRE_FROM:
+ s->state = CAMEL_MIME_PARSER_STATE_INITIAL;
+ folder_pull_part(s);
+ return;
+
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+
+ case CAMEL_MIME_PARSER_STATE_FROM_END:
+ case CAMEL_MIME_PARSER_STATE_BODY_END:
+ case CAMEL_MIME_PARSER_STATE_MULTIPART_END:
+ case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
+
+ s->state = s->parts->savestate;
+ folder_pull_part(s);
+ if (s->state & CAMEL_MIME_PARSER_STATE_END) {
+ s->state &= ~CAMEL_MIME_PARSER_STATE_END;
+ }
+ return;
+ default:
+ /* FIXME: not sure if this is entirely right */
+ break;
+ }
+}
+
+#ifdef STANDALONE
+int main(int argc, char **argv)
+{
+ int fd;
+ struct _header_scan_state *s;
+ char *data;
+ size_t len;
+ int state;
+ char *name = "/tmp/evmail/Inbox";
+ struct _header_scan_stack *h;
+ int i;
+ int attach = 0;
+
+ if (argc==2)
+ name = argv[1];
+
+ printf("opening: %s", name);
+
+ for (i=1;i<argc;i++) {
+ const char *encoding = NULL, *charset = NULL;
+ char *attachname;
+
+ name = argv[i];
+ printf("opening: %s", name);
+
+ fd = open(name, O_RDONLY);
+ if (fd==-1) {
+ perror("Cannot open mailbox");
+ exit(1);
+ }
+ s = folder_scan_init();
+ folder_scan_init_with_fd(s, fd);
+ s->scan_from = FALSE;
+#if 0
+ h = g_malloc0(sizeof(*h));
+ h->savestate = CAMEL_MIME_PARSER_STATE_EOF;
+ folder_push_part(s, h);
+#endif
+ while (s->state != CAMEL_MIME_PARSER_STATE_EOF) {
+ folder_scan_step(s, &data, &len);
+ printf("\n -- PARSER STEP RETURN -- %d '%s'\n\n", s->state, states[s->state]);
+ switch (s->state) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ if (s->parts->content_type
+ && (charset = camel_content_type_param(s->parts->content_type, "charset"))) {
+ if (g_ascii_strcasecmp(charset, "us-ascii")) {
+#if 0
+ folder_push_filter_charset(s, "UTF-8", charset);
+#endif
+ } else {
+ charset = NULL;
+ }
+ } else {
+ charset = NULL;
+ }
+
+ encoding = camel_header_raw_find(&s->parts->headers, "Content-transfer-encoding", 0);
+ printf("encoding = '%s'\n", encoding);
+ if (encoding && !strncasecmp(encoding, " base64", 7)) {
+ printf("adding base64 filter\n");
+ attachname = g_strdup_printf("attach.%d.%d", i, attach++);
+#if 0
+ folder_push_filter_save(s, attachname);
+#endif
+ g_free(attachname);
+#if 0
+ folder_push_filter_mime(s, 0);
+#endif
+ }
+ if (encoding && !strncasecmp(encoding, " quoted-printable", 17)) {
+ printf("adding quoted-printable filter\n");
+ attachname = g_strdup_printf("attach.%d.%d", i, attach++);
+#if 0
+ folder_push_filter_save(s, attachname);
+#endif
+ g_free(attachname);
+#if 0
+ folder_push_filter_mime(s, 1);
+#endif
+ }
+
+ break;
+ case CAMEL_MIME_PARSER_STATE_BODY:
+ printf("got body %d '%.*s'\n", len, len, data);
+ break;
+ case CAMEL_MIME_PARSER_STATE_BODY_END:
+ printf("end body %d '%.*s'\n", len, len, data);
+ if (encoding && !strncasecmp(encoding, " base64", 7)) {
+ printf("removing filters\n");
+#if 0
+ folder_filter_pull(s);
+ folder_filter_pull(s);
+#endif
+ }
+ if (encoding && !strncasecmp(encoding, " quoted-printable", 17)) {
+ printf("removing filters\n");
+#if 0
+ folder_filter_pull(s);
+ folder_filter_pull(s);
+#endif
+ }
+ if (charset) {
+#if 0
+ folder_filter_pull(s);
+#endif
+ charset = NULL;
+ }
+ encoding = NULL;
+ break;
+ default:
+ break;
+ }
+ }
+ folder_scan_close(s);
+ close(fd);
+ }
+ return 0;
+}
+
+#endif /* STANDALONE */
+
diff --git a/camel/camel-mime-parser.h b/camel/camel-mime-parser.h
new file mode 100644
index 0000000000..34cf36ac16
--- /dev/null
+++ b/camel/camel-mime-parser.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef _CAMEL_MIME_PARSER_H
+#define _CAMEL_MIME_PARSER_H
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#include <camel/camel-object.h>
+
+#include <camel/camel-mime-utils.h>
+#include <camel/camel-mime-filter.h>
+#include <camel/camel-stream.h>
+
+#define CAMEL_MIME_PARSER(obj) CAMEL_CHECK_CAST (obj, camel_mime_parser_get_type (), CamelMimeParser)
+#define CAMEL_MIME_PARSER_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_mime_parser_get_type (), CamelMimeParserClass)
+#define CAMEL_IS_MIME_PARSER(obj) CAMEL_CHECK_TYPE (obj, camel_mime_parser_get_type ())
+
+typedef struct _CamelMimeParserClass CamelMimeParserClass;
+
+/* NOTE: if you add more states, you may need to bump the
+ start of the END tags to 16 or 32, etc - so they are
+ the same as the matching start tag, with a bit difference */
+enum _camel_mime_parser_state {
+ CAMEL_MIME_PARSER_STATE_INITIAL,
+ CAMEL_MIME_PARSER_STATE_PRE_FROM, /* data before a 'From' line */
+ CAMEL_MIME_PARSER_STATE_FROM, /* got 'From' line */
+ CAMEL_MIME_PARSER_STATE_HEADER, /* toplevel header */
+ CAMEL_MIME_PARSER_STATE_BODY, /* scanning body of message */
+ CAMEL_MIME_PARSER_STATE_MULTIPART, /* got multipart header */
+ CAMEL_MIME_PARSER_STATE_MESSAGE, /* rfc822 message */
+
+ CAMEL_MIME_PARSER_STATE_PART, /* part of a multipart */
+
+ CAMEL_MIME_PARSER_STATE_END = 8, /* bit mask for 'end' flags */
+
+ CAMEL_MIME_PARSER_STATE_EOF = 8, /* end of file */
+ CAMEL_MIME_PARSER_STATE_PRE_FROM_END, /* pre from end */
+ CAMEL_MIME_PARSER_STATE_FROM_END, /* end of whole from bracket */
+ CAMEL_MIME_PARSER_STATE_HEADER_END, /* dummy value */
+ CAMEL_MIME_PARSER_STATE_BODY_END, /* end of message */
+ CAMEL_MIME_PARSER_STATE_MULTIPART_END, /* end of multipart */
+ CAMEL_MIME_PARSER_STATE_MESSAGE_END, /* end of message */
+};
+
+struct _CamelMimeParser {
+ CamelObject parent;
+
+ struct _CamelMimeParserPrivate *priv;
+};
+
+struct _CamelMimeParserClass {
+ CamelObjectClass parent_class;
+
+ void (*message) (CamelMimeParser *parser, void *headers);
+ void (*part) (CamelMimeParser *parser);
+ void (*content) (CamelMimeParser *parser);
+};
+
+CamelType camel_mime_parser_get_type (void);
+CamelMimeParser *camel_mime_parser_new (void);
+
+/* quick-fix for parser not erroring, we can find out if it had an error afterwards */
+int camel_mime_parser_errno (CamelMimeParser *parser);
+
+/* using an fd will be a little faster, but not much (over a simple stream) */
+int camel_mime_parser_init_with_fd (CamelMimeParser *parser, int fd);
+int camel_mime_parser_init_with_stream (CamelMimeParser *parser, CamelStream *stream);
+
+/* get the stream or fd back of the parser */
+CamelStream *camel_mime_parser_stream (CamelMimeParser *parser);
+int camel_mime_parser_fd (CamelMimeParser *parser);
+
+/* scan 'From' separators? */
+void camel_mime_parser_scan_from (CamelMimeParser *parser, gboolean scan_from);
+/* Do we want to know about the pre-from data? */
+void camel_mime_parser_scan_pre_from (CamelMimeParser *parser, gboolean scan_pre_from);
+
+/* what headers to save, MUST include ^Content-Type: */
+int camel_mime_parser_set_header_regex (CamelMimeParser *parser, char *matchstr);
+
+/* normal interface */
+enum _camel_mime_parser_state camel_mime_parser_step (CamelMimeParser *parser, char **buf, size_t *buflen);
+void camel_mime_parser_unstep (CamelMimeParser *parser);
+void camel_mime_parser_drop_step (CamelMimeParser *parser);
+enum _camel_mime_parser_state camel_mime_parser_state (CamelMimeParser *parser);
+void camel_mime_parser_push_state(CamelMimeParser *mp, enum _camel_mime_parser_state newstate, const char *boundary);
+
+/* read through the parser */
+int camel_mime_parser_read (CamelMimeParser *parser, const char **databuffer, int len);
+
+/* get content type for the current part/header */
+CamelContentType *camel_mime_parser_content_type (CamelMimeParser *parser);
+
+/* get/change raw header by name */
+const char *camel_mime_parser_header (CamelMimeParser *parser, const char *name, int *offset);
+
+/* get all raw headers. READ ONLY! */
+struct _camel_header_raw *camel_mime_parser_headers_raw (CamelMimeParser *parser);
+
+/* get multipart pre/postface */
+const char *camel_mime_parser_preface (CamelMimeParser *parser);
+const char *camel_mime_parser_postface (CamelMimeParser *parser);
+
+/* return the from line content */
+const char *camel_mime_parser_from_line (CamelMimeParser *parser);
+
+/* add a processing filter for body contents */
+int camel_mime_parser_filter_add (CamelMimeParser *parser, CamelMimeFilter *filter);
+void camel_mime_parser_filter_remove (CamelMimeParser *parser, int id);
+
+/* these should be used with caution, because the state will not
+ track the seeked position */
+/* FIXME: something to bootstrap the state? */
+off_t camel_mime_parser_tell (CamelMimeParser *parser);
+off_t camel_mime_parser_seek (CamelMimeParser *parser, off_t offset, int whence);
+
+off_t camel_mime_parser_tell_start_headers (CamelMimeParser *parser);
+off_t camel_mime_parser_tell_start_from (CamelMimeParser *parser);
+off_t camel_mime_parser_tell_start_boundary(CamelMimeParser *parser);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ! _CAMEL_MIME_PARSER_H */
diff --git a/camel/camel-mime-utils.c b/camel/camel-mime-utils.c
new file mode 100644
index 0000000000..4702afbf94
--- /dev/null
+++ b/camel/camel-mime-utils.c
@@ -0,0 +1,4320 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* dont touch this file without my permission - Michael */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h> /* for MAXHOSTNAMELEN */
+#include <sys/stat.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <regex.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 1024
+#endif
+
+#include <glib.h>
+#include <gal/util/e-iconv.h>
+#include <e-util/e-time-utils.h>
+
+#include "camel-mime-utils.h"
+#include "camel-charset-map.h"
+#include "camel-service.h" /* for camel_gethostbyname() */
+#include "camel-utf8.h"
+
+#ifndef CLEAN_DATE
+#include "broken-date-parser.h"
+#endif
+
+#if 0
+int strdup_count = 0;
+int malloc_count = 0;
+int free_count = 0;
+
+#define g_strdup(x) (strdup_count++, g_strdup(x))
+#define g_malloc(x) (malloc_count++, g_malloc(x))
+#define g_free(x) (free_count++, g_free(x))
+#endif
+
+/* for all non-essential warnings ... */
+#define w(x)
+
+#define d(x)
+#define d2(x)
+
+#define CAMEL_UUENCODE_CHAR(c) ((c) ? (c) + ' ' : '`')
+#define CAMEL_UUDECODE_CHAR(c) (((c) - ' ') & 077)
+
+static char *base64_alphabet =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static unsigned char tohex[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+unsigned short camel_mime_special_table[256];
+static unsigned char camel_mime_base64_rank[256];
+
+/* Used by table initialisation code for special characters */
+#define CHARS_LWSP " \t\n\r"
+#define CHARS_TSPECIAL "()<>@,;:\\\"/[]?="
+#define CHARS_SPECIAL "()<>@,;:\\\".[]"
+#define CHARS_CSPECIAL "()\\\r" /* not in comments */
+#define CHARS_DSPECIAL "[]\\\r \t" /* not in domains */
+#define CHARS_ESPECIAL "()<>@,;:\"/[]?.=_" /* list of characters that must be encoded.
+ encoded word in text specials: rfc 2047 5(1)*/
+#define CHARS_PSPECIAL "!*+-/" /* list of additional characters that can be left unencoded.
+ encoded word in phrase specials: rfc 2047 5(3) */
+#define CHARS_ATTRCHAR "*\'% " /* extra non-included attribute-chars */
+
+static void
+header_remove_bits(unsigned short bit, unsigned char *vals)
+{
+ int i;
+
+ for (i=0;vals[i];i++)
+ camel_mime_special_table[vals[i]] &= ~ bit;
+}
+
+static void
+header_init_bits(unsigned short bit, unsigned short bitcopy, int remove, unsigned char *vals)
+{
+ int i;
+ int len = strlen(vals);
+
+ if (!remove) {
+ for (i=0;i<len;i++) {
+ camel_mime_special_table[vals[i]] |= bit;
+ }
+ if (bitcopy) {
+ for (i=0;i<256;i++) {
+ if (camel_mime_special_table[i] & bitcopy)
+ camel_mime_special_table[i] |= bit;
+ }
+ }
+ } else {
+ for (i=0;i<256;i++)
+ camel_mime_special_table[i] |= bit;
+ for (i=0;i<len;i++) {
+ camel_mime_special_table[vals[i]] &= ~bit;
+ }
+ if (bitcopy) {
+ for (i=0;i<256;i++) {
+ if (camel_mime_special_table[i] & bitcopy)
+ camel_mime_special_table[i] &= ~bit;
+ }
+ }
+ }
+}
+
+static void
+header_decode_init(void)
+{
+ int i;
+
+ for (i=0;i<256;i++) {
+ camel_mime_special_table[i] = 0;
+ if (i<32 || i==127)
+ camel_mime_special_table[i] |= CAMEL_MIME_IS_CTRL;
+ else if (i < 127)
+ camel_mime_special_table[i] |= CAMEL_MIME_IS_ATTRCHAR;
+ if ((i>=32 && i<=60) || (i>=62 && i<=126) || i==9)
+ camel_mime_special_table[i] |= (CAMEL_MIME_IS_QPSAFE|CAMEL_MIME_IS_ESAFE);
+ if ((i>='0' && i<='9') || (i>='a' && i<='z') || (i>='A' && i<= 'Z'))
+ camel_mime_special_table[i] |= CAMEL_MIME_IS_PSAFE;
+ }
+ camel_mime_special_table[' '] |= CAMEL_MIME_IS_SPACE;
+ header_init_bits(CAMEL_MIME_IS_LWSP, 0, 0, CHARS_LWSP);
+ header_init_bits(CAMEL_MIME_IS_TSPECIAL, CAMEL_MIME_IS_CTRL, 0, CHARS_TSPECIAL);
+ header_init_bits(CAMEL_MIME_IS_SPECIAL, 0, 0, CHARS_SPECIAL);
+ header_init_bits(CAMEL_MIME_IS_DSPECIAL, 0, FALSE, CHARS_DSPECIAL);
+ header_remove_bits(CAMEL_MIME_IS_ESAFE, CHARS_ESPECIAL);
+ header_remove_bits(CAMEL_MIME_IS_ATTRCHAR, CHARS_TSPECIAL CHARS_ATTRCHAR);
+ header_init_bits(CAMEL_MIME_IS_PSAFE, 0, 0, CHARS_PSPECIAL);
+}
+
+static void
+base64_init(void)
+{
+ int i;
+
+ memset(camel_mime_base64_rank, 0xff, sizeof(camel_mime_base64_rank));
+ for (i=0;i<64;i++) {
+ camel_mime_base64_rank[(unsigned int)base64_alphabet[i]] = i;
+ }
+ camel_mime_base64_rank['='] = 0;
+}
+
+/* call this when finished encoding everything, to
+ flush off the last little bit */
+size_t
+camel_base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
+{
+ int c1, c2;
+ unsigned char *outptr = out;
+
+ if (inlen>0)
+ outptr += camel_base64_encode_step(in, inlen, break_lines, outptr, state, save);
+
+ c1 = ((unsigned char *)save)[1];
+ c2 = ((unsigned char *)save)[2];
+
+ d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
+ (int)((char *)save)[0],
+ (int)((char *)save)[1],
+ (int)((char *)save)[2]));
+
+ switch (((char *)save)[0]) {
+ case 2:
+ outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
+ g_assert(outptr[2] != 0);
+ goto skip;
+ case 1:
+ outptr[2] = '=';
+ skip:
+ outptr[0] = base64_alphabet[ c1 >> 2 ];
+ outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
+ outptr[3] = '=';
+ outptr += 4;
+ break;
+ }
+ if (break_lines)
+ *outptr++ = '\n';
+
+ *save = 0;
+ *state = 0;
+
+ return outptr-out;
+}
+
+/*
+ performs an 'encode step', only encodes blocks of 3 characters to the
+ output at a time, saves left-over state in state and save (initialise to
+ 0 on first invocation).
+*/
+size_t
+camel_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
+{
+ register unsigned char *inptr, *outptr;
+
+ if (len<=0)
+ return 0;
+
+ inptr = in;
+ outptr = out;
+
+ d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0]));
+
+ if (len + ((char *)save)[0] > 2) {
+ unsigned char *inend = in+len-2;
+ register int c1, c2, c3;
+ register int already;
+
+ already = *state;
+
+ switch (((char *)save)[0]) {
+ case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
+ case 2: c1 = ((unsigned char *)save)[1];
+ c2 = ((unsigned char *)save)[2]; goto skip2;
+ }
+
+ /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
+ while (inptr < inend) {
+ c1 = *inptr++;
+ skip1:
+ c2 = *inptr++;
+ skip2:
+ c3 = *inptr++;
+ *outptr++ = base64_alphabet[ c1 >> 2 ];
+ *outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
+ *outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
+ *outptr++ = base64_alphabet[ c3 & 0x3f ];
+ /* this is a bit ugly ... */
+ if (break_lines && (++already)>=19) {
+ *outptr++='\n';
+ already = 0;
+ }
+ }
+
+ ((char *)save)[0] = 0;
+ len = 2-(inptr-inend);
+ *state = already;
+ }
+
+ d(printf("state = %d, len = %d\n",
+ (int)((char *)save)[0],
+ len));
+
+ if (len>0) {
+ register char *saveout;
+
+ /* points to the slot for the next char to save */
+ saveout = & (((char *)save)[1]) + ((char *)save)[0];
+
+ /* len can only be 0 1 or 2 */
+ switch(len) {
+ case 2: *saveout++ = *inptr++;
+ case 1: *saveout++ = *inptr++;
+ }
+ ((char *)save)[0]+=len;
+ }
+
+ d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
+ (int)((char *)save)[0],
+ (int)((char *)save)[1],
+ (int)((char *)save)[2]));
+
+ return outptr-out;
+}
+
+
+/**
+ * camel_base64_decode_step: decode a chunk of base64 encoded data
+ * @in: input stream
+ * @len: max length of data to decode
+ * @out: output stream
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been decoded
+ *
+ * Decodes a chunk of base64 encoded data
+ **/
+size_t
+camel_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
+{
+ register unsigned char *inptr, *outptr;
+ unsigned char *inend, c;
+ register unsigned int v;
+ int i;
+
+ inend = in+len;
+ outptr = out;
+
+ /* convert 4 base64 bytes to 3 normal bytes */
+ v=*save;
+ i=*state;
+ inptr = in;
+ while (inptr<inend) {
+ c = camel_mime_base64_rank[*inptr++];
+ if (c != 0xff) {
+ v = (v<<6) | c;
+ i++;
+ if (i==4) {
+ *outptr++ = v>>16;
+ *outptr++ = v>>8;
+ *outptr++ = v;
+ i=0;
+ }
+ }
+ }
+
+ *save = v;
+ *state = i;
+
+ /* quick scan back for '=' on the end somewhere */
+ /* fortunately we can drop 1 output char for each trailing = (upto 2) */
+ i=2;
+ while (inptr>in && i) {
+ inptr--;
+ if (camel_mime_base64_rank[*inptr] != 0xff) {
+ if (*inptr == '=' && outptr>out)
+ outptr--;
+ i--;
+ }
+ }
+
+ /* if i!= 0 then there is a truncation error! */
+ return outptr-out;
+}
+
+char *
+camel_base64_encode_simple (const char *data, size_t len)
+{
+ unsigned char *out;
+ int state = 0, outlen;
+ unsigned int save = 0;
+
+ out = g_malloc (len * 4 / 3 + 5);
+ outlen = camel_base64_encode_close ((unsigned char *)data, len, FALSE,
+ out, &state, &save);
+ out[outlen] = '\0';
+ return (char *)out;
+}
+
+size_t
+camel_base64_decode_simple (char *data, size_t len)
+{
+ int state = 0;
+ unsigned int save = 0;
+
+ return camel_base64_decode_step ((unsigned char *)data, len,
+ (unsigned char *)data, &state, &save);
+}
+
+/**
+ * camel_uuencode_close: uuencode a chunk of data
+ * @in: input stream
+ * @len: input stream length
+ * @out: output stream
+ * @uubuf: temporary buffer of 60 bytes
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been encoded
+ *
+ * Returns the number of bytes encoded. Call this when finished
+ * encoding data with camel_uuencode_step to flush off the last little
+ * bit.
+ **/
+size_t
+camel_uuencode_close (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
+{
+ register unsigned char *outptr, *bufptr;
+ register guint32 saved;
+ int uulen, uufill, i;
+
+ outptr = out;
+
+ if (len > 0)
+ outptr += camel_uuencode_step (in, len, out, uubuf, state, save);
+
+ uufill = 0;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+
+ bufptr = uubuf + ((uulen / 3) * 4);
+
+ if (i > 0) {
+ while (i < 3) {
+ saved <<= 8 | 0;
+ uufill++;
+ i++;
+ }
+
+ if (i == 3) {
+ /* convert 3 normal bytes into 4 uuencoded bytes */
+ unsigned char b0, b1, b2;
+
+ b0 = saved >> 16;
+ b1 = saved >> 8 & 0xff;
+ b2 = saved & 0xff;
+
+ *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f);
+
+ i = 0;
+ saved = 0;
+ uulen += 3;
+ }
+ }
+
+ if (uulen > 0) {
+ int cplen = ((uulen / 3) * 4);
+
+ *outptr++ = CAMEL_UUENCODE_CHAR ((uulen - uufill) & 0xff);
+ memcpy (outptr, uubuf, cplen);
+ outptr += cplen;
+ *outptr++ = '\n';
+ uulen = 0;
+ }
+
+ *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
+ *outptr++ = '\n';
+
+ *save = 0;
+ *state = 0;
+
+ return outptr - out;
+}
+
+
+/**
+ * camel_uuencode_step: uuencode a chunk of data
+ * @in: input stream
+ * @len: input stream length
+ * @out: output stream
+ * @uubuf: temporary buffer of 60 bytes
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been encoded
+ *
+ * Returns the number of bytes encoded. Performs an 'encode step',
+ * only encodes blocks of 45 characters to the output at a time, saves
+ * left-over state in @uubuf, @state and @save (initialize to 0 on first
+ * invocation).
+ **/
+size_t
+camel_uuencode_step (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
+{
+ register unsigned char *inptr, *outptr, *bufptr;
+ unsigned char *inend;
+ register guint32 saved;
+ int uulen, i;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+
+ inptr = in;
+ inend = in + len;
+
+ outptr = out;
+
+ bufptr = uubuf + ((uulen / 3) * 4);
+
+ while (inptr < inend) {
+ while (uulen < 45 && inptr < inend) {
+ while (i < 3 && inptr < inend) {
+ saved = (saved << 8) | *inptr++;
+ i++;
+ }
+
+ if (i == 3) {
+ /* convert 3 normal bytes into 4 uuencoded bytes */
+ unsigned char b0, b1, b2;
+
+ b0 = saved >> 16;
+ b1 = saved >> 8 & 0xff;
+ b2 = saved & 0xff;
+
+ *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f);
+
+ i = 0;
+ saved = 0;
+ uulen += 3;
+ }
+ }
+
+ if (uulen >= 45) {
+ *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
+ memcpy (outptr, uubuf, ((uulen / 3) * 4));
+ outptr += ((uulen / 3) * 4);
+ *outptr++ = '\n';
+ uulen = 0;
+ bufptr = uubuf;
+ }
+ }
+
+ *save = saved;
+ *state = ((uulen & 0xff) << 8) | (i & 0xff);
+
+ return outptr - out;
+}
+
+
+/**
+ * camel_uudecode_step: uudecode a chunk of data
+ * @in: input stream
+ * @inlen: max length of data to decode ( normally strlen(in) ??)
+ * @out: output stream
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been decoded
+ *
+ * Returns the number of bytes decoded. Performs a 'decode step' on
+ * a chunk of uuencoded data. Assumes the "begin <mode> <file name>"
+ * line has been stripped off.
+ **/
+size_t
+camel_uudecode_step (unsigned char *in, size_t len, unsigned char *out, int *state, guint32 *save)
+{
+ register unsigned char *inptr, *outptr;
+ unsigned char *inend, ch;
+ register guint32 saved;
+ gboolean last_was_eoln;
+ int uulen, i;
+
+ if (*state & CAMEL_UUDECODE_STATE_END)
+ return 0;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+ if (uulen == 0)
+ last_was_eoln = TRUE;
+ else
+ last_was_eoln = FALSE;
+
+ inend = in + len;
+ outptr = out;
+
+ inptr = in;
+ while (inptr < inend) {
+ if (*inptr == '\n' || last_was_eoln) {
+ if (last_was_eoln && *inptr != '\n') {
+ uulen = CAMEL_UUDECODE_CHAR (*inptr);
+ last_was_eoln = FALSE;
+ if (uulen == 0) {
+ *state |= CAMEL_UUDECODE_STATE_END;
+ break;
+ }
+ } else {
+ last_was_eoln = TRUE;
+ }
+
+ inptr++;
+ continue;
+ }
+
+ ch = *inptr++;
+
+ if (uulen > 0) {
+ /* save the byte */
+ saved = (saved << 8) | ch;
+ i++;
+ if (i == 4) {
+ /* convert 4 uuencoded bytes to 3 normal bytes */
+ unsigned char b0, b1, b2, b3;
+
+ b0 = saved >> 24;
+ b1 = saved >> 16 & 0xff;
+ b2 = saved >> 8 & 0xff;
+ b3 = saved & 0xff;
+
+ if (uulen >= 3) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
+ *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
+ *outptr++ = CAMEL_UUDECODE_CHAR (b2) << 6 | CAMEL_UUDECODE_CHAR (b3);
+ } else {
+ if (uulen >= 1) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
+ }
+ if (uulen >= 2) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
+ }
+ }
+
+ i = 0;
+ saved = 0;
+ uulen -= 3;
+ }
+ } else {
+ break;
+ }
+ }
+
+ *save = saved;
+ *state = (*state & CAMEL_UUDECODE_STATE_MASK) | ((uulen & 0xff) << 8) | (i & 0xff);
+
+ return outptr - out;
+}
+
+
+/* complete qp encoding */
+size_t
+camel_quoted_decode_close(unsigned char *in, size_t len, unsigned char *out, int *state, int *save)
+{
+ register unsigned char *outptr = out;
+ int last;
+
+ if (len>0)
+ outptr += camel_quoted_encode_step(in, len, outptr, state, save);
+
+ last = *state;
+ if (last != -1) {
+ /* space/tab must be encoded if it's the last character on
+ the line */
+ if (camel_mime_is_qpsafe(last) && last!=' ' && last!=9) {
+ *outptr++ = last;
+ } else {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last>>4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ }
+ }
+
+ *save = 0;
+ *state = -1;
+
+ return outptr-out;
+}
+
+/* perform qp encoding, initialise state to -1 and save to 0 on first invocation */
+size_t
+camel_quoted_encode_step (unsigned char *in, size_t len, unsigned char *out, int *statep, int *save)
+{
+ register guchar *inptr, *outptr, *inend;
+ unsigned char c;
+ register int sofar = *save; /* keeps track of how many chars on a line */
+ register int last = *statep; /* keeps track if last char to end was a space cr etc */
+
+ inptr = in;
+ inend = in + len;
+ outptr = out;
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c == '\r') {
+ if (last != -1) {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last >> 4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ sofar += 3;
+ }
+ last = c;
+ } else if (c == '\n') {
+ if (last != -1 && last != '\r') {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last >> 4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ }
+ *outptr++ = '\n';
+ sofar = 0;
+ last = -1;
+ } else {
+ if (last != -1) {
+ if (camel_mime_is_qpsafe(last)) {
+ *outptr++ = last;
+ sofar++;
+ } else {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last >> 4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ sofar += 3;
+ }
+ }
+
+ if (camel_mime_is_qpsafe(c)) {
+ if (sofar > 74) {
+ *outptr++ = '=';
+ *outptr++ = '\n';
+ sofar = 0;
+ }
+
+ /* delay output of space char */
+ if (c==' ' || c=='\t') {
+ last = c;
+ } else {
+ *outptr++ = c;
+ sofar++;
+ last = -1;
+ }
+ } else {
+ if (sofar > 72) {
+ *outptr++ = '=';
+ *outptr++ = '\n';
+ sofar = 3;
+ } else
+ sofar += 3;
+
+ *outptr++ = '=';
+ *outptr++ = tohex[(c >> 4) & 0xf];
+ *outptr++ = tohex[c & 0xf];
+ last = -1;
+ }
+ }
+ }
+ *save = sofar;
+ *statep = last;
+
+ return (outptr - out);
+}
+
+/*
+ FIXME: this does not strip trailing spaces from lines (as it should, rfc 2045, section 6.7)
+ Should it also canonicalise the end of line to CR LF??
+
+ Note: Trailing rubbish (at the end of input), like = or =x or =\r will be lost.
+*/
+
+size_t
+camel_quoted_decode_step(unsigned char *in, size_t len, unsigned char *out, int *savestate, int *saveme)
+{
+ register unsigned char *inptr, *outptr;
+ unsigned char *inend, c;
+ int state, save;
+
+ inend = in+len;
+ outptr = out;
+
+ d(printf("quoted-printable, decoding text '%.*s'\n", len, in));
+
+ state = *savestate;
+ save = *saveme;
+ inptr = in;
+ while (inptr<inend) {
+ switch (state) {
+ case 0:
+ while (inptr<inend) {
+ c = *inptr++;
+ if (c=='=') {
+ state = 1;
+ break;
+ }
+#ifdef CANONICALISE_EOL
+ /*else if (c=='\r') {
+ state = 3;
+ } else if (c=='\n') {
+ *outptr++ = '\r';
+ *outptr++ = c;
+ } */
+#endif
+ else {
+ *outptr++ = c;
+ }
+ }
+ break;
+ case 1:
+ c = *inptr++;
+ if (c=='\n') {
+ /* soft break ... unix end of line */
+ state = 0;
+ } else {
+ save = c;
+ state = 2;
+ }
+ break;
+ case 2:
+ c = *inptr++;
+ if (isxdigit(c) && isxdigit(save)) {
+ c = toupper(c);
+ save = toupper(save);
+ *outptr++ = (((save>='A'?save-'A'+10:save-'0')&0x0f) << 4)
+ | ((c>='A'?c-'A'+10:c-'0')&0x0f);
+ } else if (c=='\n' && save == '\r') {
+ /* soft break ... canonical end of line */
+ } else {
+ /* just output the data */
+ *outptr++ = '=';
+ *outptr++ = save;
+ *outptr++ = c;
+ }
+ state = 0;
+ break;
+#ifdef CANONICALISE_EOL
+ case 3:
+ /* convert \r -> to \r\n, leaves \r\n alone */
+ c = *inptr++;
+ if (c=='\n') {
+ *outptr++ = '\r';
+ *outptr++ = c;
+ } else {
+ *outptr++ = '\r';
+ *outptr++ = '\n';
+ *outptr++ = c;
+ }
+ state = 0;
+ break;
+#endif
+ }
+ }
+
+ *savestate = state;
+ *saveme = save;
+
+ return outptr-out;
+}
+
+/*
+ this is for the "Q" encoding of international words,
+ which is slightly different than plain quoted-printable (mainly by allowing 0x20 <> _)
+*/
+static size_t
+quoted_decode(const unsigned char *in, size_t len, unsigned char *out)
+{
+ register const unsigned char *inptr;
+ register unsigned char *outptr;
+ unsigned const char *inend;
+ unsigned char c, c1;
+ int ret = 0;
+
+ inend = in+len;
+ outptr = out;
+
+ d(printf("decoding text '%.*s'\n", len, in));
+
+ inptr = in;
+ while (inptr<inend) {
+ c = *inptr++;
+ if (c=='=') {
+ /* silently ignore truncated data? */
+ if (inend-in>=2) {
+ c = toupper(*inptr++);
+ c1 = toupper(*inptr++);
+ *outptr++ = (((c>='A'?c-'A'+10:c-'0')&0x0f) << 4)
+ | ((c1>='A'?c1-'A'+10:c1-'0')&0x0f);
+ } else {
+ ret = -1;
+ break;
+ }
+ } else if (c=='_') {
+ *outptr++ = 0x20;
+ } else if (c==' ' || c==0x09) {
+ /* FIXME: this is an error! ignore for now ... */
+ ret = -1;
+ break;
+ } else {
+ *outptr++ = c;
+ }
+ }
+ if (ret==0) {
+ return outptr-out;
+ }
+ return 0;
+}
+
+/* rfc2047 version of quoted-printable */
+/* safemask is the mask to apply to the camel_mime_special_table to determine what
+ characters can safely be included without encoding */
+static size_t
+quoted_encode (const unsigned char *in, size_t len, unsigned char *out, unsigned short safemask)
+{
+ register const unsigned char *inptr, *inend;
+ unsigned char *outptr;
+ unsigned char c;
+
+ inptr = in;
+ inend = in + len;
+ outptr = out;
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c==' ') {
+ *outptr++ = '_';
+ } else if (camel_mime_special_table[c] & safemask) {
+ *outptr++ = c;
+ } else {
+ *outptr++ = '=';
+ *outptr++ = tohex[(c >> 4) & 0xf];
+ *outptr++ = tohex[c & 0xf];
+ }
+ }
+
+ d(printf("encoding '%.*s' = '%.*s'\n", len, in, outptr-out, out));
+
+ return (outptr - out);
+}
+
+
+static void
+header_decode_lwsp(const char **in)
+{
+ const char *inptr = *in;
+ char c;
+
+ d2(printf("is ws: '%s'\n", *in));
+
+ while (camel_mime_is_lwsp(*inptr) || (*inptr =='(' && *inptr != '\0')) {
+ while (camel_mime_is_lwsp(*inptr) && inptr != '\0') {
+ d2(printf("(%c)", *inptr));
+ inptr++;
+ }
+ d2(printf("\n"));
+
+ /* check for comments */
+ if (*inptr == '(') {
+ int depth = 1;
+ inptr++;
+ while (depth && (c=*inptr) && *inptr != '\0') {
+ if (c=='\\' && inptr[1]) {
+ inptr++;
+ } else if (c=='(') {
+ depth++;
+ } else if (c==')') {
+ depth--;
+ }
+ inptr++;
+ }
+ }
+ }
+ *in = inptr;
+}
+
+/* decode rfc 2047 encoded string segment */
+static char *
+rfc2047_decode_word(const char *in, size_t len)
+{
+ const char *inptr = in+2;
+ const char *inend = in+len-2;
+ const char *inbuf;
+ const char *charset;
+ char *encname, *p;
+ int tmplen;
+ size_t ret;
+ char *decword = NULL;
+ char *decoded = NULL;
+ char *outbase = NULL;
+ char *outbuf;
+ size_t inlen, outlen;
+ gboolean retried = FALSE;
+ iconv_t ic;
+
+ d(printf("rfc2047: decoding '%.*s'\n", len, in));
+
+ /* quick check to see if this could possibly be a real encoded word */
+ if (len < 8 || !(in[0] == '=' && in[1] == '?' && in[len-1] == '=' && in[len-2] == '?')) {
+ d(printf("invalid\n"));
+ return NULL;
+ }
+
+ /* skip past the charset to the encoding type */
+ inptr = memchr (inptr, '?', inend-inptr);
+ if (inptr != NULL && inptr < inend + 2 && inptr[2] == '?') {
+ d(printf("found ?, encoding is '%c'\n", inptr[0]));
+ inptr++;
+ tmplen = inend-inptr-2;
+ decword = g_alloca (tmplen); /* this will always be more-than-enough room */
+ switch(toupper(inptr[0])) {
+ case 'Q':
+ inlen = quoted_decode(inptr+2, tmplen, decword);
+ break;
+ case 'B': {
+ int state = 0;
+ unsigned int save = 0;
+
+ inlen = camel_base64_decode_step((char *)inptr+2, tmplen, decword, &state, &save);
+ /* if state != 0 then error? */
+ break;
+ }
+ default:
+ /* uhhh, unknown encoding type - probably an invalid encoded word string */
+ return NULL;
+ }
+ d(printf("The encoded length = %d\n", inlen));
+ if (inlen > 0) {
+ /* yuck, all this snot is to setup iconv! */
+ tmplen = inptr - in - 3;
+ encname = g_alloca (tmplen + 1);
+ memcpy (encname, in + 2, tmplen);
+ encname[tmplen] = '\0';
+
+ /* rfc2231 updates rfc2047 encoded words...
+ * The ABNF given in RFC 2047 for encoded-words is:
+ * encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
+ * This specification changes this ABNF to:
+ * encoded-word := "=?" charset ["*" language] "?" encoding "?" encoded-text "?="
+ */
+
+ /* trim off the 'language' part if it's there... */
+ p = strchr (encname, '*');
+ if (p)
+ *p = '\0';
+
+ charset = e_iconv_charset_name (encname);
+
+ inbuf = decword;
+
+ outlen = inlen * 6 + 16;
+ outbase = g_alloca (outlen);
+ outbuf = outbase;
+
+ retry:
+ ic = e_iconv_open ("UTF-8", charset);
+ if (ic != (iconv_t) -1) {
+ ret = e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ e_iconv (ic, NULL, 0, &outbuf, &outlen);
+ *outbuf = 0;
+ decoded = g_strdup (outbase);
+ }
+ e_iconv_close (ic);
+ } else {
+ w(g_warning ("Cannot decode charset, header display may be corrupt: %s: %s",
+ charset, strerror (errno)));
+
+ if (!retried) {
+ charset = e_iconv_locale_charset ();
+ if (!charset)
+ charset = "iso-8859-1";
+
+ retried = TRUE;
+ goto retry;
+ }
+
+ /* we return the encoded word here because we've got to return valid utf8 */
+ decoded = g_strndup (in, inlen);
+ }
+ }
+ }
+
+ d(printf("decoded '%s'\n", decoded));
+
+ return decoded;
+}
+
+/* ok, a lot of mailers are BROKEN, and send iso-latin1 encoded
+ headers, when they should just be sticking to US-ASCII
+ according to the rfc's. Anyway, since the conversion to utf-8
+ is trivial, just do it here without iconv */
+static GString *
+append_latin1 (GString *out, const char *in, size_t len)
+{
+ unsigned int c;
+
+ while (len) {
+ c = (unsigned int)*in++;
+ len--;
+ if (c & 0x80) {
+ out = g_string_append_c (out, 0xc0 | ((c >> 6) & 0x3)); /* 110000xx */
+ out = g_string_append_c (out, 0x80 | (c & 0x3f)); /* 10xxxxxx */
+ } else {
+ out = g_string_append_c (out, c);
+ }
+ }
+ return out;
+}
+
+static int
+append_8bit (GString *out, const char *inbuf, size_t inlen, const char *charset)
+{
+ char *outbase, *outbuf;
+ size_t outlen;
+ iconv_t ic;
+
+ ic = e_iconv_open ("UTF-8", charset);
+ if (ic == (iconv_t) -1)
+ return FALSE;
+
+ outlen = inlen * 6 + 16;
+ outbuf = outbase = g_malloc(outlen);
+
+ if (e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen) == (size_t) -1) {
+ w(g_warning("Conversion to '%s' failed: %s", charset, strerror (errno)));
+ g_free(outbase);
+ e_iconv_close (ic);
+ return FALSE;
+ }
+
+ e_iconv (ic, NULL, NULL, &outbuf, &outlen);
+
+ *outbuf = 0;
+ g_string_append(out, outbase);
+ g_free(outbase);
+ e_iconv_close (ic);
+
+ return TRUE;
+
+}
+
+static GString *
+append_quoted_pair (GString *str, const char *in, gssize inlen)
+{
+ register const char *inptr = in;
+ const char *inend = in + inlen;
+ char c;
+
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c == '\\' && inptr < inend)
+ g_string_append_c (str, *inptr++);
+ else
+ g_string_append_c (str, c);
+ }
+
+ return str;
+}
+
+/* decodes a simple text, rfc822 + rfc2047 */
+static char *
+header_decode_text (const char *in, size_t inlen, int ctext, const char *default_charset)
+{
+ GString *out;
+ const char *inptr, *inend, *start, *chunk, *locale_charset;
+ GString *(* append) (GString *, const char *, gssize);
+ char *dword = NULL;
+ guint32 mask;
+
+ locale_charset = e_iconv_locale_charset ();
+
+ if (ctext) {
+ mask = (CAMEL_MIME_IS_SPECIAL | CAMEL_MIME_IS_SPACE | CAMEL_MIME_IS_CTRL);
+ append = append_quoted_pair;
+ } else {
+ mask = (CAMEL_MIME_IS_LWSP);
+ append = g_string_append_len;
+ }
+
+ out = g_string_new ("");
+ inptr = in;
+ inend = inptr + inlen;
+ chunk = NULL;
+
+ while (inptr < inend) {
+ start = inptr;
+ while (inptr < inend && camel_mime_is_type (*inptr, mask))
+ inptr++;
+
+ if (inptr == inend) {
+ append (out, start, inptr - start);
+ break;
+ } else if (dword == NULL) {
+ append (out, start, inptr - start);
+ } else {
+ chunk = start;
+ }
+
+ start = inptr;
+ while (inptr < inend && !camel_mime_is_type (*inptr, mask))
+ inptr++;
+
+ dword = rfc2047_decode_word(start, inptr-start);
+ if (dword) {
+ g_string_append(out, dword);
+ g_free(dword);
+ } else {
+ if (!chunk)
+ chunk = start;
+
+ if ((default_charset == NULL || !append_8bit (out, chunk, inptr-chunk, default_charset))
+ && (locale_charset == NULL || !append_8bit(out, chunk, inptr-chunk, locale_charset)))
+ append_latin1(out, chunk, inptr-chunk);
+ }
+
+ chunk = NULL;
+ }
+
+ dword = out->str;
+ g_string_free (out, FALSE);
+
+ return dword;
+}
+
+char *
+camel_header_decode_string (const char *in, const char *default_charset)
+{
+ if (in == NULL)
+ return NULL;
+ return header_decode_text (in, strlen (in), FALSE, default_charset);
+}
+
+char *
+camel_header_format_ctext (const char *in, const char *default_charset)
+{
+ if (in == NULL)
+ return NULL;
+ return header_decode_text (in, strlen (in), TRUE, default_charset);
+}
+
+/* how long a sequence of pre-encoded words should be less than, to attempt to
+ fit into a properly folded word. Only a guide. */
+#define CAMEL_FOLD_PREENCODED (24)
+
+/* FIXME: needs a way to cache iconv opens for different charsets? */
+static void
+rfc2047_encode_word(GString *outstring, const char *in, size_t len, const char *type, unsigned short safemask)
+{
+ iconv_t ic = (iconv_t) -1;
+ char *buffer, *out, *ascii;
+ size_t inlen, outlen, enclen, bufflen;
+ const char *inptr, *p;
+ int first = 1;
+
+ d(printf("Converting [%d] '%.*s' to %s\n", len, len, in, type));
+
+ /* convert utf8->encoding */
+ bufflen = len * 6 + 16;
+ buffer = g_alloca (bufflen);
+ inlen = len;
+ inptr = in;
+
+ ascii = g_alloca (bufflen);
+
+ if (strcasecmp (type, "UTF-8") != 0)
+ ic = e_iconv_open (type, "UTF-8");
+
+ while (inlen) {
+ size_t convlen, proclen;
+ int i;
+
+ /* break up words into smaller bits, what we really want is encoded + overhead < 75,
+ but we'll just guess what that means in terms of input chars, and assume its good enough */
+
+ out = buffer;
+ outlen = bufflen;
+
+ if (ic == (iconv_t) -1) {
+ /* native encoding case, the easy one (?) */
+ /* we work out how much we can convert, and still be in length */
+ /* proclen will be the result of input characters that we can convert, to the nearest
+ (approximated) valid utf8 char */
+ convlen = 0;
+ proclen = 0;
+ p = inptr;
+ i = 0;
+ while (p < (in+len) && convlen < (75 - strlen("=?utf-8?q\?\?="))) {
+ unsigned char c = *p++;
+
+ if (c >= 0xc0)
+ proclen = i;
+ i++;
+ if (c < 0x80)
+ proclen = i;
+ if (camel_mime_special_table[c] & safemask)
+ convlen += 1;
+ else
+ convlen += 3;
+ }
+ /* well, we probably have broken utf8, just copy it anyway what the heck */
+ if (proclen == 0) {
+ w(g_warning("Appear to have truncated utf8 sequence"));
+ proclen = inlen;
+ }
+ memcpy(out, inptr, proclen);
+ inptr += proclen;
+ inlen -= proclen;
+ out += proclen;
+ } else {
+ /* well we could do similar, but we can't (without undue effort), we'll just break it up into
+ hopefully-small-enough chunks, and leave it at that */
+ convlen = MIN(inlen, CAMEL_FOLD_PREENCODED);
+ p = inptr;
+ if (e_iconv (ic, &inptr, &convlen, &out, &outlen) == (size_t) -1 && errno != EINVAL) {
+ w(g_warning("Conversion problem: conversion truncated: %s", strerror (errno)));
+ /* blah, we include it anyway, better than infinite loop ... */
+ inptr = p + convlen;
+ } else {
+ /* make sure we flush out any shift state */
+ e_iconv (ic, NULL, 0, &out, &outlen);
+ }
+ inlen -= (inptr - p);
+ }
+
+ enclen = out-buffer;
+
+ if (enclen) {
+ /* create token */
+ out = ascii;
+ if (first)
+ first = 0;
+ else
+ *out++ = ' ';
+ out += sprintf (out, "=?%s?Q?", type);
+ out += quoted_encode (buffer, enclen, out, safemask);
+ sprintf (out, "?=");
+
+ d(printf("converted part = %s\n", ascii));
+
+ g_string_append (outstring, ascii);
+ }
+ }
+
+ if (ic != (iconv_t) -1)
+ e_iconv_close (ic);
+}
+
+
+/* TODO: Should this worry about quotes?? */
+char *
+camel_header_encode_string (const unsigned char *in)
+{
+ const unsigned char *inptr = in, *start, *word;
+ gboolean last_was_encoded = FALSE;
+ gboolean last_was_space = FALSE;
+ int encoding;
+ GString *out;
+ char *outstr;
+
+ g_return_val_if_fail (g_utf8_validate (in, -1, NULL), NULL);
+
+ if (in == NULL)
+ return NULL;
+
+ /* do a quick us-ascii check (the common case?) */
+ while (*inptr) {
+ if (*inptr > 127)
+ break;
+ inptr++;
+ }
+ if (*inptr == '\0')
+ return g_strdup (in);
+
+ /* This gets each word out of the input, and checks to see what charset
+ can be used to encode it. */
+ /* TODO: Work out when to merge subsequent words, or across word-parts */
+ out = g_string_new ("");
+ inptr = in;
+ encoding = 0;
+ word = NULL;
+ start = inptr;
+ while (inptr && *inptr) {
+ gunichar c;
+ const char *newinptr;
+
+ newinptr = g_utf8_next_char (inptr);
+ c = g_utf8_get_char (inptr);
+ if (newinptr == NULL || !g_unichar_validate (c)) {
+ w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s",
+ (inptr-in), inptr[0], in));
+ inptr++;
+ continue;
+ }
+
+ if (c < 256 && camel_mime_is_lwsp (c) && !last_was_space) {
+ /* we've reached the end of a 'word' */
+ if (word && !(last_was_encoded && encoding)) {
+ /* output lwsp between non-encoded words */
+ g_string_append_len (out, start, word - start);
+ start = word;
+ }
+
+ switch (encoding) {
+ case 0:
+ g_string_append_len (out, start, inptr - start);
+ last_was_encoded = FALSE;
+ break;
+ case 1:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
+ last_was_encoded = TRUE;
+ break;
+ case 2:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start,
+ camel_charset_best (start, inptr - start), CAMEL_MIME_IS_ESAFE);
+ last_was_encoded = TRUE;
+ break;
+ }
+
+ last_was_space = TRUE;
+ start = inptr;
+ word = NULL;
+ encoding = 0;
+ } else if (c > 127 && c < 256) {
+ encoding = MAX (encoding, 1);
+ last_was_space = FALSE;
+ } else if (c >= 256) {
+ encoding = MAX (encoding, 2);
+ last_was_space = FALSE;
+ } else if (!camel_mime_is_lwsp (c)) {
+ last_was_space = FALSE;
+ }
+
+ if (!(c < 256 && camel_mime_is_lwsp (c)) && !word)
+ word = inptr;
+
+ inptr = newinptr;
+ }
+
+ if (inptr - start) {
+ if (word && !(last_was_encoded && encoding)) {
+ g_string_append_len (out, start, word - start);
+ start = word;
+ }
+
+ switch (encoding) {
+ case 0:
+ g_string_append_len (out, start, inptr - start);
+ break;
+ case 1:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
+ break;
+ case 2:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start,
+ camel_charset_best (start, inptr - start - 1), CAMEL_MIME_IS_ESAFE);
+ break;
+ }
+ }
+
+ outstr = out->str;
+ g_string_free (out, FALSE);
+
+ return outstr;
+}
+
+/* apply quoted-string rules to a string */
+static void
+quote_word(GString *out, gboolean do_quotes, const char *start, size_t len)
+{
+ int i, c;
+
+ /* TODO: What about folding on long lines? */
+ if (do_quotes)
+ g_string_append_c(out, '"');
+ for (i=0;i<len;i++) {
+ c = *start++;
+ if (c == '\"' || c=='\\' || c=='\r')
+ g_string_append_c(out, '\\');
+ g_string_append_c(out, c);
+ }
+ if (do_quotes)
+ g_string_append_c(out, '"');
+}
+
+/* incrementing possibility for the word type */
+enum _phrase_word_t {
+ WORD_ATOM,
+ WORD_QSTRING,
+ WORD_2047
+};
+
+struct _phrase_word {
+ const unsigned char *start, *end;
+ enum _phrase_word_t type;
+ int encoding;
+};
+
+static gboolean
+word_types_compatable (enum _phrase_word_t type1, enum _phrase_word_t type2)
+{
+ switch (type1) {
+ case WORD_ATOM:
+ return type2 == WORD_QSTRING;
+ case WORD_QSTRING:
+ return type2 != WORD_2047;
+ case WORD_2047:
+ return type2 == WORD_2047;
+ default:
+ return FALSE;
+ }
+}
+
+/* split the input into words with info about each word
+ * merge common word types clean up */
+static GList *
+header_encode_phrase_get_words (const unsigned char *in)
+{
+ const unsigned char *inptr = in, *start, *last;
+ struct _phrase_word *word;
+ enum _phrase_word_t type;
+ int encoding, count = 0;
+ GList *words = NULL;
+
+ /* break the input into words */
+ type = WORD_ATOM;
+ last = inptr;
+ start = inptr;
+ encoding = 0;
+ while (inptr && *inptr) {
+ gunichar c;
+ const char *newinptr;
+
+ newinptr = g_utf8_next_char (inptr);
+ c = g_utf8_get_char (inptr);
+
+ if (!g_unichar_validate (c)) {
+ w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s",
+ (inptr - in), inptr[0], in));
+ inptr++;
+ continue;
+ }
+
+ inptr = newinptr;
+ if (g_unichar_isspace (c)) {
+ if (count > 0) {
+ word = g_new0 (struct _phrase_word, 1);
+ word->start = start;
+ word->end = last;
+ word->type = type;
+ word->encoding = encoding;
+ words = g_list_append (words, word);
+ count = 0;
+ }
+
+ start = inptr;
+ type = WORD_ATOM;
+ encoding = 0;
+ } else {
+ count++;
+ if (c < 128) {
+ if (!camel_mime_is_atom (c))
+ type = MAX (type, WORD_QSTRING);
+ } else if (c > 127 && c < 256) {
+ type = WORD_2047;
+ encoding = MAX (encoding, 1);
+ } else if (c >= 256) {
+ type = WORD_2047;
+ encoding = MAX (encoding, 2);
+ }
+ }
+
+ last = inptr;
+ }
+
+ if (count > 0) {
+ word = g_new0 (struct _phrase_word, 1);
+ word->start = start;
+ word->end = last;
+ word->type = type;
+ word->encoding = encoding;
+ words = g_list_append (words, word);
+ }
+
+ return words;
+}
+
+#define MERGED_WORD_LT_FOLDLEN(wordlen, type) ((type) == WORD_2047 ? (wordlen) < CAMEL_FOLD_PREENCODED : (wordlen) < (CAMEL_FOLD_SIZE - 8))
+
+static gboolean
+header_encode_phrase_merge_words (GList **wordsp)
+{
+ GList *wordl, *nextl, *words = *wordsp;
+ struct _phrase_word *word, *next;
+ gboolean merged = FALSE;
+
+ /* scan the list, checking for words of similar types that can be merged */
+ wordl = words;
+ while (wordl) {
+ word = wordl->data;
+ nextl = g_list_next (wordl);
+
+ while (nextl) {
+ next = nextl->data;
+ /* merge nodes of the same type AND we are not creating too long a string */
+ if (word_types_compatable (word->type, next->type)) {
+ if (MERGED_WORD_LT_FOLDLEN (next->end - word->start, MAX (word->type, next->type))) {
+ /* the resulting word type is the MAX of the 2 types */
+ word->type = MAX(word->type, next->type);
+
+ word->end = next->end;
+ words = g_list_remove_link (words, nextl);
+ g_list_free_1 (nextl);
+ g_free (next);
+
+ nextl = g_list_next (wordl);
+
+ merged = TRUE;
+ } else {
+ /* if it is going to be too long, make sure we include the
+ separating whitespace */
+ word->end = next->start;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ wordl = g_list_next (wordl);
+ }
+
+ *wordsp = words;
+
+ return merged;
+}
+
+/* encodes a phrase sequence (different quoting/encoding rules to strings) */
+char *
+camel_header_encode_phrase (const unsigned char *in)
+{
+ struct _phrase_word *word = NULL, *last_word = NULL;
+ GList *words, *wordl;
+ GString *out;
+ char *outstr;
+
+ if (in == NULL)
+ return NULL;
+
+ words = header_encode_phrase_get_words (in);
+ if (!words)
+ return NULL;
+
+ while (header_encode_phrase_merge_words (&words))
+ ;
+
+ out = g_string_new ("");
+
+ /* output words now with spaces between them */
+ wordl = words;
+ while (wordl) {
+ const char *start;
+ size_t len;
+
+ word = wordl->data;
+
+ /* append correct number of spaces between words */
+ if (last_word && !(last_word->type == WORD_2047 && word->type == WORD_2047)) {
+ /* one or both of the words are not encoded so we write the spaces out untouched */
+ len = word->start - last_word->end;
+ out = g_string_append_len (out, last_word->end, len);
+ }
+
+ switch (word->type) {
+ case WORD_ATOM:
+ out = g_string_append_len (out, word->start, word->end - word->start);
+ break;
+ case WORD_QSTRING:
+ quote_word (out, TRUE, word->start, word->end - word->start);
+ break;
+ case WORD_2047:
+ if (last_word && last_word->type == WORD_2047) {
+ /* include the whitespace chars between these 2 words in the
+ resulting rfc2047 encoded word. */
+ len = word->end - last_word->end;
+ start = last_word->end;
+
+ /* encoded words need to be separated by linear whitespace */
+ g_string_append_c (out, ' ');
+ } else {
+ len = word->end - word->start;
+ start = word->start;
+ }
+
+ if (word->encoding == 1)
+ rfc2047_encode_word (out, start, len, "ISO-8859-1", CAMEL_MIME_IS_PSAFE);
+ else
+ rfc2047_encode_word (out, start, len,
+ camel_charset_best (start, len), CAMEL_MIME_IS_PSAFE);
+ break;
+ }
+
+ g_free (last_word);
+ wordl = g_list_next (wordl);
+
+ last_word = word;
+ }
+
+ /* and we no longer need the list */
+ g_free (word);
+ g_list_free (words);
+
+ outstr = out->str;
+ g_string_free (out, FALSE);
+
+ return outstr;
+}
+
+
+/* these are all internal parser functions */
+
+static char *
+decode_token (const char **in)
+{
+ const char *inptr = *in;
+ const char *start;
+
+ header_decode_lwsp (&inptr);
+ start = inptr;
+ while (camel_mime_is_ttoken (*inptr))
+ inptr++;
+ if (inptr > start) {
+ *in = inptr;
+ return g_strndup (start, inptr - start);
+ } else {
+ return NULL;
+ }
+}
+
+char *
+camel_header_token_decode(const char *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return decode_token(&in);
+}
+
+/*
+ <"> * ( <any char except <"> \, cr / \ <any char> ) <">
+*/
+static char *
+header_decode_quoted_string(const char **in)
+{
+ const char *inptr = *in;
+ char *out = NULL, *outptr;
+ size_t outlen;
+ int c;
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == '"') {
+ const char *intmp;
+ int skip = 0;
+
+ /* first, calc length */
+ inptr++;
+ intmp = inptr;
+ while ( (c = *intmp++) && c!= '"') {
+ if (c=='\\' && *intmp) {
+ intmp++;
+ skip++;
+ }
+ }
+ outlen = intmp-inptr-skip;
+ out = outptr = g_malloc(outlen+1);
+ while ( (c = *inptr++) && c!= '"') {
+ if (c=='\\' && *inptr) {
+ c = *inptr++;
+ }
+ *outptr++ = c;
+ }
+ *outptr = '\0';
+ }
+ *in = inptr;
+ return out;
+}
+
+static char *
+header_decode_atom(const char **in)
+{
+ const char *inptr = *in, *start;
+
+ header_decode_lwsp(&inptr);
+ start = inptr;
+ while (camel_mime_is_atom(*inptr))
+ inptr++;
+ *in = inptr;
+ if (inptr > start)
+ return g_strndup(start, inptr-start);
+ else
+ return NULL;
+}
+
+static char *
+header_decode_word (const char **in)
+{
+ const char *inptr = *in;
+
+ header_decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ *in = inptr;
+ return header_decode_quoted_string (in);
+ } else {
+ *in = inptr;
+ return header_decode_atom (in);
+ }
+}
+
+static char *
+header_decode_value(const char **in)
+{
+ const char *inptr = *in;
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == '"') {
+ d(printf("decoding quoted string\n"));
+ return header_decode_quoted_string(in);
+ } else if (camel_mime_is_ttoken(*inptr)) {
+ d(printf("decoding token\n"));
+ /* this may not have the right specials for all params? */
+ return decode_token(in);
+ }
+ return NULL;
+}
+
+/* should this return -1 for no int? */
+int
+camel_header_decode_int(const char **in)
+{
+ const char *inptr = *in;
+ int c, v=0;
+
+ header_decode_lwsp(&inptr);
+ while ( (c=*inptr++ & 0xff)
+ && isdigit(c) ) {
+ v = v*10+(c-'0');
+ }
+ *in = inptr-1;
+ return v;
+}
+
+#define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
+
+static char *
+hex_decode (const char *in, size_t len)
+{
+ const unsigned char *inend = in + len;
+ unsigned char *inptr, *outptr;
+ char *outbuf;
+
+ outptr = outbuf = g_malloc (len + 1);
+
+ inptr = (unsigned char *) in;
+ while (inptr < inend) {
+ if (*inptr == '%') {
+ if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
+ *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
+ inptr += 3;
+ } else
+ *outptr++ = *inptr++;
+ } else
+ *outptr++ = *inptr++;
+ }
+
+ *outptr = '\0';
+
+ return outbuf;
+}
+
+/* Tries to convert @in @from charset @to charset. Any failure, we get no data out rather than partial conversion */
+static char *
+header_convert(const char *to, const char *from, const char *in, size_t inlen)
+{
+ iconv_t ic;
+ size_t outlen, ret;
+ char *outbuf, *outbase, *result = NULL;
+
+ ic = e_iconv_open(to, from);
+ if (ic == (iconv_t) -1)
+ return NULL;
+
+ outlen = inlen * 6 + 16;
+ outbuf = outbase = g_malloc(outlen);
+
+ ret = e_iconv(ic, &in, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ e_iconv(ic, NULL, 0, &outbuf, &outlen);
+ *outbuf = '\0';
+ result = g_strdup(outbase);
+ }
+ e_iconv_close(ic);
+ g_free(outbase);
+
+ return result;
+}
+
+/* an rfc2184 encoded string looks something like:
+ * us-ascii'en'This%20is%20even%20more%20
+ */
+
+static char *
+rfc2184_decode (const char *in, size_t len)
+{
+ const char *inptr = in;
+ const char *inend = in + len;
+ const char *charset;
+ char *decoded, *decword, *encoding;
+
+ inptr = memchr (inptr, '\'', len);
+ if (!inptr)
+ return NULL;
+
+ encoding = g_alloca(inptr-in+1);
+ memcpy(encoding, in, inptr-in);
+ encoding[inptr-in] = 0;
+ charset = e_iconv_charset_name (encoding);
+
+ inptr = memchr (inptr + 1, '\'', inend - inptr - 1);
+ if (!inptr)
+ return NULL;
+ inptr++;
+ if (inptr >= inend)
+ return NULL;
+
+ decword = hex_decode (inptr, inend - inptr);
+ decoded = header_convert("UTF-8", charset, decword, strlen(decword));
+ g_free(decword);
+
+ return decoded;
+}
+
+char *
+camel_header_param (struct _camel_header_param *p, const char *name)
+{
+ while (p && g_ascii_strcasecmp (p->name, name) != 0)
+ p = p->next;
+ if (p)
+ return p->value;
+ return NULL;
+}
+
+struct _camel_header_param *
+camel_header_set_param (struct _camel_header_param **l, const char *name, const char *value)
+{
+ struct _camel_header_param *p = (struct _camel_header_param *)l, *pn;
+
+ if (name == NULL)
+ return NULL;
+
+ while (p->next) {
+ pn = p->next;
+ if (!g_ascii_strcasecmp (pn->name, name)) {
+ g_free (pn->value);
+ if (value) {
+ pn->value = g_strdup (value);
+ return pn;
+ } else {
+ p->next = pn->next;
+ g_free (pn->name);
+ g_free (pn);
+ return NULL;
+ }
+ }
+ p = pn;
+ }
+
+ if (value == NULL)
+ return NULL;
+
+ pn = g_malloc (sizeof (*pn));
+ pn->next = 0;
+ pn->name = g_strdup (name);
+ pn->value = g_strdup (value);
+ p->next = pn;
+
+ return pn;
+}
+
+const char *
+camel_content_type_param (CamelContentType *t, const char *name)
+{
+ if (t==NULL)
+ return NULL;
+ return camel_header_param (t->params, name);
+}
+
+void
+camel_content_type_set_param (CamelContentType *t, const char *name, const char *value)
+{
+ camel_header_set_param (&t->params, name, value);
+}
+
+/**
+ * camel_content_type_is:
+ * @ct: A content type specifier, or #NULL.
+ * @type: A type to check against.
+ * @subtype: A subtype to check against, or "*" to match any subtype.
+ *
+ * Returns #TRUE if the content type @ct is of type @type/@subtype.
+ * The subtype of "*" will match any subtype. If @ct is #NULL, then
+ * it will match the type "text/plain".
+ *
+ * Return value: #TRUE or #FALSE depending on the matching of the type.
+ **/
+int
+camel_content_type_is(CamelContentType *ct, const char *type, const char *subtype)
+{
+ /* no type == text/plain or text/"*" */
+ if (ct==NULL || (ct->type == NULL && ct->subtype == NULL)) {
+ return (!strcasecmp(type, "text")
+ && (!g_ascii_strcasecmp(subtype, "plain")
+ || !strcasecmp(subtype, "*")));
+ }
+
+ return (ct->type != NULL
+ && (!g_ascii_strcasecmp(ct->type, type)
+ && ((ct->subtype != NULL
+ && !g_ascii_strcasecmp(ct->subtype, subtype))
+ || !strcasecmp("*", subtype))));
+}
+
+void
+camel_header_param_list_free(struct _camel_header_param *p)
+{
+ struct _camel_header_param *n;
+
+ while (p) {
+ n = p->next;
+ g_free(p->name);
+ g_free(p->value);
+ g_free(p);
+ p = n;
+ }
+}
+
+CamelContentType *
+camel_content_type_new(const char *type, const char *subtype)
+{
+ CamelContentType *t = g_malloc(sizeof(*t));
+
+ t->type = g_strdup(type);
+ t->subtype = g_strdup(subtype);
+ t->params = NULL;
+ t->refcount = 1;
+ return t;
+}
+
+void
+camel_content_type_ref(CamelContentType *ct)
+{
+ if (ct)
+ ct->refcount++;
+}
+
+
+void
+camel_content_type_unref(CamelContentType *ct)
+{
+ if (ct) {
+ if (ct->refcount <= 1) {
+ camel_header_param_list_free(ct->params);
+ g_free(ct->type);
+ g_free(ct->subtype);
+ g_free(ct);
+ } else {
+ ct->refcount--;
+ }
+ }
+}
+
+/* for decoding email addresses, canonically */
+static char *
+header_decode_domain(const char **in)
+{
+ const char *inptr = *in, *start;
+ int go = TRUE;
+ char *ret;
+ GString *domain = g_string_new("");
+
+ /* domain ref | domain literal */
+ header_decode_lwsp(&inptr);
+ while (go) {
+ if (*inptr == '[') { /* domain literal */
+ domain = g_string_append_c(domain, '[');
+ inptr++;
+ header_decode_lwsp(&inptr);
+ start = inptr;
+ while (camel_mime_is_dtext(*inptr)) {
+ domain = g_string_append_c(domain, *inptr);
+ inptr++;
+ }
+ if (*inptr == ']') {
+ domain = g_string_append_c(domain, ']');
+ inptr++;
+ } else {
+ w(g_warning("closing ']' not found in domain: %s", *in));
+ }
+ } else {
+ char *a = header_decode_atom(&inptr);
+ if (a) {
+ domain = g_string_append(domain, a);
+ g_free(a);
+ } else {
+ w(g_warning("missing atom from domain-ref"));
+ break;
+ }
+ }
+ header_decode_lwsp(&inptr);
+ if (*inptr == '.') { /* next sub-domain? */
+ domain = g_string_append_c(domain, '.');
+ inptr++;
+ header_decode_lwsp(&inptr);
+ } else
+ go = FALSE;
+ }
+
+ *in = inptr;
+
+ ret = domain->str;
+ g_string_free(domain, FALSE);
+ return ret;
+}
+
+static char *
+header_decode_addrspec(const char **in)
+{
+ const char *inptr = *in;
+ char *word;
+ GString *addr = g_string_new("");
+
+ header_decode_lwsp(&inptr);
+
+ /* addr-spec */
+ word = header_decode_word (&inptr);
+ if (word) {
+ addr = g_string_append(addr, word);
+ header_decode_lwsp(&inptr);
+ g_free(word);
+ while (*inptr == '.' && word) {
+ inptr++;
+ addr = g_string_append_c(addr, '.');
+ word = header_decode_word (&inptr);
+ if (word) {
+ addr = g_string_append(addr, word);
+ header_decode_lwsp(&inptr);
+ g_free(word);
+ } else {
+ w(g_warning("Invalid address spec: %s", *in));
+ }
+ }
+ if (*inptr == '@') {
+ inptr++;
+ addr = g_string_append_c(addr, '@');
+ word = header_decode_domain(&inptr);
+ if (word) {
+ addr = g_string_append(addr, word);
+ g_free(word);
+ } else {
+ w(g_warning("Invalid address, missing domain: %s", *in));
+ }
+ } else {
+ w(g_warning("Invalid addr-spec, missing @: %s", *in));
+ }
+ } else {
+ w(g_warning("invalid addr-spec, no local part"));
+ }
+
+ /* FIXME: return null on error? */
+
+ *in = inptr;
+ word = addr->str;
+ g_string_free(addr, FALSE);
+ return word;
+}
+
+/*
+ address:
+ word *('.' word) @ domain |
+ *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain |
+
+ 1*word ':' [ word ... etc (mailbox, as above) ] ';'
+ */
+
+/* mailbox:
+ word *( '.' word ) '@' domain
+ *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain
+ */
+
+static struct _camel_header_address *
+header_decode_mailbox(const char **in, const char *charset)
+{
+ const char *inptr = *in;
+ char *pre;
+ int closeme = FALSE;
+ GString *addr;
+ GString *name = NULL;
+ struct _camel_header_address *address = NULL;
+ const char *comment = NULL;
+
+ addr = g_string_new("");
+
+ /* for each address */
+ pre = header_decode_word (&inptr);
+ header_decode_lwsp(&inptr);
+ if (!(*inptr == '.' || *inptr == '@' || *inptr==',' || *inptr=='\0')) {
+ /* ',' and '\0' required incase it is a simple address, no @ domain part (buggy writer) */
+ name = g_string_new ("");
+ while (pre) {
+ char *text, *last;
+
+ /* perform internationalised decoding, and append */
+ text = camel_header_decode_string (pre, charset);
+ g_string_append (name, text);
+ last = pre;
+ g_free(text);
+
+ pre = header_decode_word (&inptr);
+ if (pre) {
+ size_t l = strlen (last);
+ size_t p = strlen (pre);
+
+ /* dont append ' ' between sucsessive encoded words */
+ if ((l>6 && last[l-2] == '?' && last[l-1] == '=')
+ && (p>6 && pre[0] == '=' && pre[1] == '?')) {
+ /* dont append ' ' */
+ } else {
+ name = g_string_append_c(name, ' ');
+ }
+ } else {
+ /* Fix for stupidly-broken-mailers that like to put '.''s in names unquoted */
+ /* see bug #8147 */
+ while (!pre && *inptr && *inptr != '<') {
+ w(g_warning("Working around stupid mailer bug #5: unescaped characters in names"));
+ name = g_string_append_c(name, *inptr++);
+ pre = header_decode_word (&inptr);
+ }
+ }
+ g_free(last);
+ }
+ header_decode_lwsp(&inptr);
+ if (*inptr == '<') {
+ closeme = TRUE;
+ try_address_again:
+ inptr++;
+ header_decode_lwsp(&inptr);
+ if (*inptr == '@') {
+ while (*inptr == '@') {
+ inptr++;
+ header_decode_domain(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ',') {
+ inptr++;
+ header_decode_lwsp(&inptr);
+ }
+ }
+ if (*inptr == ':') {
+ inptr++;
+ } else {
+ w(g_warning("broken route-address, missing ':': %s", *in));
+ }
+ }
+ pre = header_decode_word (&inptr);
+ header_decode_lwsp(&inptr);
+ } else {
+ w(g_warning("broken address? %s", *in));
+ }
+ }
+
+ if (pre) {
+ addr = g_string_append(addr, pre);
+ } else {
+ w(g_warning("No local-part for email address: %s", *in));
+ }
+
+ /* should be at word '.' localpart */
+ while (*inptr == '.' && pre) {
+ inptr++;
+ g_free(pre);
+ pre = header_decode_word (&inptr);
+ addr = g_string_append_c(addr, '.');
+ if (pre)
+ addr = g_string_append(addr, pre);
+ comment = inptr;
+ header_decode_lwsp(&inptr);
+ }
+ g_free(pre);
+
+ /* now at '@' domain part */
+ if (*inptr == '@') {
+ char *dom;
+
+ inptr++;
+ addr = g_string_append_c(addr, '@');
+ comment = inptr;
+ dom = header_decode_domain(&inptr);
+ addr = g_string_append(addr, dom);
+ g_free(dom);
+ } else if (*inptr != '>' || !closeme) {
+ /* If we get a <, the address was probably a name part, lets try again shall we? */
+ /* Another fix for seriously-broken-mailers */
+ if (*inptr && *inptr != ',') {
+ char *text;
+
+ w(g_warning("We didn't get an '@' where we expected in '%s', trying again", *in));
+ w(g_warning("Name is '%s', Addr is '%s' we're at '%s'\n", name?name->str:"<UNSET>", addr->str, inptr));
+
+ /* need to keep *inptr, as try_address_again will drop the current character */
+ if (*inptr == '<')
+ closeme = TRUE;
+ else
+ g_string_append_c(addr, *inptr);
+
+ /* check for address is encoded word ... */
+ text = camel_header_decode_string(addr->str, charset);
+ if (name == NULL) {
+ name = addr;
+ addr = g_string_new("");
+ if (text) {
+ g_string_truncate(name, 0);
+ g_string_append(name, text);
+ }
+ } else {
+ g_string_append(name, text?text:addr->str);
+ g_string_truncate(addr, 0);
+ }
+ g_free(text);
+
+ /* or maybe that we've added up a bunch of broken bits to make an encoded word */
+ text = rfc2047_decode_word(name->str, name->len);
+ if (text) {
+ g_string_truncate(name, 0);
+ g_string_append(name, text);
+ g_free(text);
+ }
+
+ goto try_address_again;
+ }
+ w(g_warning("invalid address, no '@' domain part at %c: %s", *inptr, *in));
+ }
+
+ if (closeme) {
+ header_decode_lwsp(&inptr);
+ if (*inptr == '>') {
+ inptr++;
+ } else {
+ w(g_warning("invalid route address, no closing '>': %s", *in));
+ }
+ } else if (name == NULL && comment != NULL && inptr>comment) { /* check for comment after address */
+ char *text, *tmp;
+ const char *comstart, *comend;
+
+ /* this is a bit messy, we go from the last known position, because
+ decode_domain/etc skip over any comments on the way */
+ /* FIXME: This wont detect comments inside the domain itself,
+ but nobody seems to use that feature anyway ... */
+
+ d(printf("checking for comment from '%s'\n", comment));
+
+ comstart = strchr(comment, '(');
+ if (comstart) {
+ comstart++;
+ header_decode_lwsp(&inptr);
+ comend = inptr-1;
+ while (comend > comstart && comend[0] != ')')
+ comend--;
+
+ if (comend > comstart) {
+ d(printf(" looking at subset '%.*s'\n", comend-comstart, comstart));
+ tmp = g_strndup (comstart, comend-comstart);
+ text = camel_header_decode_string (tmp, charset);
+ name = g_string_new (text);
+ g_free (tmp);
+ g_free (text);
+ }
+ }
+ }
+
+ *in = inptr;
+
+ if (addr->len > 0) {
+ if (!g_utf8_validate (addr->str, addr->len, NULL)) {
+ /* workaround for invalid addr-specs containing 8bit chars (see bug #42170 for details) */
+ const char *locale_charset;
+ GString *out;
+
+ locale_charset = e_iconv_locale_charset ();
+
+ out = g_string_new ("");
+
+ if ((charset == NULL || !append_8bit (out, addr->str, addr->len, charset))
+ && (locale_charset == NULL || !append_8bit (out, addr->str, addr->len, locale_charset)))
+ append_latin1 (out, addr->str, addr->len);
+
+ g_string_free (addr, TRUE);
+ addr = out;
+ }
+
+ address = camel_header_address_new_name(name ? name->str : "", addr->str);
+ }
+
+ d(printf("got mailbox: %s\n", addr->str));
+
+ g_string_free(addr, TRUE);
+ if (name)
+ g_string_free(name, TRUE);
+
+ return address;
+}
+
+static struct _camel_header_address *
+header_decode_address(const char **in, const char *charset)
+{
+ const char *inptr = *in;
+ char *pre;
+ GString *group = g_string_new("");
+ struct _camel_header_address *addr = NULL, *member;
+
+ /* pre-scan, trying to work out format, discard results */
+ header_decode_lwsp(&inptr);
+ while ((pre = header_decode_word (&inptr))) {
+ group = g_string_append(group, pre);
+ group = g_string_append(group, " ");
+ g_free(pre);
+ }
+ header_decode_lwsp(&inptr);
+ if (*inptr == ':') {
+ d(printf("group detected: %s\n", group->str));
+ addr = camel_header_address_new_group(group->str);
+ /* that was a group spec, scan mailbox's */
+ inptr++;
+ /* FIXME: check rfc 2047 encodings of words, here or above in the loop */
+ header_decode_lwsp(&inptr);
+ if (*inptr != ';') {
+ int go = TRUE;
+ do {
+ member = header_decode_mailbox(&inptr, charset);
+ if (member)
+ camel_header_address_add_member(addr, member);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ',')
+ inptr++;
+ else
+ go = FALSE;
+ } while (go);
+ if (*inptr == ';') {
+ inptr++;
+ } else {
+ w(g_warning("Invalid group spec, missing closing ';': %s", *in));
+ }
+ } else {
+ inptr++;
+ }
+ *in = inptr;
+ } else {
+ addr = header_decode_mailbox(in, charset);
+ }
+
+ g_string_free(group, TRUE);
+
+ return addr;
+}
+
+static char *
+header_msgid_decode_internal(const char **in)
+{
+ const char *inptr = *in;
+ char *msgid = NULL;
+
+ d(printf("decoding Message-ID: '%s'\n", *in));
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == '<') {
+ inptr++;
+ header_decode_lwsp(&inptr);
+ msgid = header_decode_addrspec(&inptr);
+ if (msgid) {
+ header_decode_lwsp(&inptr);
+ if (*inptr == '>') {
+ inptr++;
+ } else {
+ w(g_warning("Missing closing '>' on message id: %s", *in));
+ }
+ } else {
+ w(g_warning("Cannot find message id in: %s", *in));
+ }
+ } else {
+ w(g_warning("missing opening '<' on message id: %s", *in));
+ }
+ *in = inptr;
+
+ return msgid;
+}
+
+char *
+camel_header_msgid_decode(const char *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_msgid_decode_internal(&in);
+}
+
+char *
+camel_header_contentid_decode (const char *in)
+{
+ const char *inptr = in;
+ gboolean at = FALSE;
+ GString *addr;
+ char *buf;
+
+ d(printf("decoding Content-ID: '%s'\n", in));
+
+ header_decode_lwsp (&inptr);
+
+ /* some lame mailers quote the Content-Id */
+ if (*inptr == '"')
+ inptr++;
+
+ /* make sure the content-id is not "" which can happen if we get a
+ * content-id such as <.@> (which Eudora likes to use...) */
+ if ((buf = camel_header_msgid_decode (inptr)) != NULL && *buf)
+ return buf;
+
+ g_free (buf);
+
+ /* ugh, not a valid msg-id - try to get something useful out of it then? */
+ inptr = in;
+ header_decode_lwsp (&inptr);
+ if (*inptr == '<') {
+ inptr++;
+ header_decode_lwsp (&inptr);
+ }
+
+ /* Eudora has been known to use <.@> as a content-id */
+ if (!(buf = header_decode_word (&inptr)) && !strchr (".@", *inptr))
+ return NULL;
+
+ addr = g_string_new ("");
+ header_decode_lwsp (&inptr);
+ while (buf != NULL || *inptr == '.' || (*inptr == '@' && !at)) {
+ if (buf != NULL) {
+ g_string_append (addr, buf);
+ g_free (buf);
+ buf = NULL;
+ }
+
+ if (!at) {
+ if (*inptr == '.') {
+ g_string_append_c (addr, *inptr++);
+ buf = header_decode_word (&inptr);
+ } else if (*inptr == '@') {
+ g_string_append_c (addr, *inptr++);
+ buf = header_decode_word (&inptr);
+ at = TRUE;
+ }
+ } else if (strchr (".[]", *inptr)) {
+ g_string_append_c (addr, *inptr++);
+ buf = header_decode_atom (&inptr);
+ }
+
+ header_decode_lwsp (&inptr);
+ }
+
+ buf = addr->str;
+ g_string_free (addr, FALSE);
+
+ return buf;
+}
+
+void
+camel_header_references_list_append_asis(struct _camel_header_references **list, char *ref)
+{
+ struct _camel_header_references *w = (struct _camel_header_references *)list, *n;
+ while (w->next)
+ w = w->next;
+ n = g_malloc(sizeof(*n));
+ n->id = ref;
+ n->next = 0;
+ w->next = n;
+}
+
+int
+camel_header_references_list_size(struct _camel_header_references **list)
+{
+ int count = 0;
+ struct _camel_header_references *w = *list;
+ while (w) {
+ count++;
+ w = w->next;
+ }
+ return count;
+}
+
+void
+camel_header_references_list_clear(struct _camel_header_references **list)
+{
+ struct _camel_header_references *w = *list, *n;
+ while (w) {
+ n = w->next;
+ g_free(w->id);
+ g_free(w);
+ w = n;
+ }
+ *list = NULL;
+}
+
+static void
+header_references_decode_single (const char **in, struct _camel_header_references **head)
+{
+ struct _camel_header_references *ref;
+ const char *inptr = *in;
+ char *id, *word;
+
+ while (*inptr) {
+ header_decode_lwsp (&inptr);
+ if (*inptr == '<') {
+ id = header_msgid_decode_internal (&inptr);
+ if (id) {
+ ref = g_malloc (sizeof (struct _camel_header_references));
+ ref->next = *head;
+ ref->id = id;
+ *head = ref;
+ break;
+ }
+ } else {
+ word = header_decode_word (&inptr);
+ if (word)
+ g_free (word);
+ else if (*inptr != '\0')
+ inptr++; /* Stupid mailer tricks */
+ }
+ }
+
+ *in = inptr;
+}
+
+/* TODO: why is this needed? Can't the other interface also work? */
+struct _camel_header_references *
+camel_header_references_inreplyto_decode (const char *in)
+{
+ struct _camel_header_references *ref = NULL;
+
+ if (in == NULL || in[0] == '\0')
+ return NULL;
+
+ header_references_decode_single (&in, &ref);
+
+ return ref;
+}
+
+/* generate a list of references, from most recent up */
+struct _camel_header_references *
+camel_header_references_decode (const char *in)
+{
+ struct _camel_header_references *refs = NULL;
+
+ if (in == NULL || in[0] == '\0')
+ return NULL;
+
+ while (*in)
+ header_references_decode_single (&in, &refs);
+
+ return refs;
+}
+
+struct _camel_header_references *
+camel_header_references_dup(const struct _camel_header_references *list)
+{
+ struct _camel_header_references *new = NULL, *tmp;
+
+ while (list) {
+ tmp = g_new(struct _camel_header_references, 1);
+ tmp->next = new;
+ tmp->id = g_strdup(list->id);
+ new = tmp;
+ list = list->next;
+ }
+ return new;
+}
+
+struct _camel_header_address *
+camel_header_mailbox_decode(const char *in, const char *charset)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_mailbox(&in, charset);
+}
+
+struct _camel_header_address *
+camel_header_address_decode(const char *in, const char *charset)
+{
+ const char *inptr = in, *last;
+ struct _camel_header_address *list = NULL, *addr;
+
+ d(printf("decoding To: '%s'\n", in));
+
+ if (in == NULL)
+ return NULL;
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == 0)
+ return NULL;
+
+ do {
+ last = inptr;
+ addr = header_decode_address(&inptr, charset);
+ if (addr)
+ camel_header_address_list_append(&list, addr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ',')
+ inptr++;
+ else
+ break;
+ } while (inptr != last);
+
+ if (*inptr) {
+ w(g_warning("Invalid input detected at %c (%d): %s\n or at: %s", *inptr, inptr-in, in, inptr));
+ }
+
+ if (inptr == last) {
+ w(g_warning("detected invalid input loop at : %s", last));
+ }
+
+ return list;
+}
+
+struct _camel_header_newsgroup *
+camel_header_newsgroups_decode(const char *in)
+{
+ const char *inptr = in;
+ register char c;
+ struct _camel_header_newsgroup *head, *last, *ng;
+ const char *start;
+
+ head = NULL;
+ last = (struct _camel_header_newsgroup *)&head;
+
+ do {
+ header_decode_lwsp(&inptr);
+ start = inptr;
+ while ((c = *inptr++) && !camel_mime_is_lwsp(c) && c != ',')
+ ;
+ if (start != inptr-1) {
+ ng = g_malloc(sizeof(*ng));
+ ng->newsgroup = g_strndup(start, inptr-start-1);
+ ng->next = NULL;
+ last->next = ng;
+ last = ng;
+ }
+ } while (c);
+
+ return head;
+}
+
+void
+camel_header_newsgroups_free(struct _camel_header_newsgroup *ng)
+{
+ while (ng) {
+ struct _camel_header_newsgroup *nng = ng->next;
+
+ g_free(ng->newsgroup);
+ g_free(ng);
+ ng = nng;
+ }
+}
+
+/* this must be kept in sync with the header */
+static const char *encodings[] = {
+ "",
+ "7bit",
+ "8bit",
+ "base64",
+ "quoted-printable",
+ "binary",
+ "x-uuencode",
+};
+
+const char *
+camel_transfer_encoding_to_string (CamelTransferEncoding encoding)
+{
+ if (encoding >= sizeof (encodings) / sizeof (encodings[0]))
+ encoding = 0;
+
+ return encodings[encoding];
+}
+
+CamelTransferEncoding
+camel_transfer_encoding_from_string (const char *string)
+{
+ int i;
+
+ if (string != NULL) {
+ for (i = 0; i < sizeof (encodings) / sizeof (encodings[0]); i++)
+ if (!g_ascii_strcasecmp (string, encodings[i]))
+ return i;
+ }
+
+ return CAMEL_TRANSFER_ENCODING_DEFAULT;
+}
+
+void
+camel_header_mime_decode(const char *in, int *maj, int *min)
+{
+ const char *inptr = in;
+ int major=-1, minor=-1;
+
+ d(printf("decoding MIME-Version: '%s'\n", in));
+
+ if (in != NULL) {
+ header_decode_lwsp(&inptr);
+ if (isdigit(*inptr)) {
+ major = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == '.') {
+ inptr++;
+ header_decode_lwsp(&inptr);
+ if (isdigit(*inptr))
+ minor = camel_header_decode_int(&inptr);
+ }
+ }
+ }
+
+ if (maj)
+ *maj = major;
+ if (min)
+ *min = minor;
+
+ d(printf("major = %d, minor = %d\n", major, minor));
+}
+
+struct _rfc2184_param {
+ struct _camel_header_param param;
+ int index;
+};
+
+static int
+rfc2184_param_cmp(const void *ap, const void *bp)
+{
+ const struct _rfc2184_param *a = *(void **)ap;
+ const struct _rfc2184_param *b = *(void **)bp;
+ int res;
+
+ res = strcmp(a->param.name, b->param.name);
+ if (res == 0) {
+ if (a->index > b->index)
+ res = 1;
+ else if (a->index < b->index)
+ res = -1;
+ }
+
+ return res;
+}
+
+/* NB: Steals name and value */
+static struct _camel_header_param *
+header_append_param(struct _camel_header_param *last, char *name, char *value)
+{
+ struct _camel_header_param *node;
+
+ /* This handles -
+ 8 bit data in parameters, illegal, tries to convert using locale, or just safens it up.
+ rfc2047 ecoded parameters, illegal, decodes them anyway. Some Outlook & Mozilla do this?
+ */
+ node = g_malloc(sizeof(*node));
+ last->next = node;
+ node->next = NULL;
+ node->name = name;
+ if (strncmp(value, "=?", 2) == 0
+ && (node->value = header_decode_text(value, strlen(value), FALSE, NULL))) {
+ g_free(value);
+ } else if (!g_utf8_validate(value, -1, NULL)) {
+ const char * charset = e_iconv_locale_charset();
+
+ if ((node->value = header_convert("UTF-8", charset?charset:"ISO-8859-1", value, strlen(value)))) {
+ g_free(value);
+ } else {
+ node->value = value;
+ for (;*value;value++)
+ if (!isascii((unsigned char)*value))
+ *value = '_';
+ }
+ } else
+ node->value = value;
+
+ return node;
+}
+
+static struct _camel_header_param *
+header_decode_param_list (const char **in)
+{
+ struct _camel_header_param *head = NULL, *last = (struct _camel_header_param *)&head;
+ GPtrArray *split = NULL;
+ const char *inptr = *in;
+ struct _rfc2184_param *work;
+ char *tmp;
+
+ /* Dump parameters into the output list, in the order found. RFC 2184 split parameters are kept in an array */
+ header_decode_lwsp(&inptr);
+ while (*inptr == ';') {
+ char *name;
+ char *value = NULL;
+
+ inptr++;
+ name = decode_token(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == '=') {
+ inptr++;
+ value = header_decode_value(&inptr);
+ }
+
+ if (name && value) {
+ char *index = strchr(name, '*');
+
+ if (index) {
+ if (index[1] == 0) {
+ /* VAL*="foo", decode immediately and append */
+ *index = 0;
+ tmp = rfc2184_decode(value, strlen(value));
+ if (tmp) {
+ g_free(value);
+ value = tmp;
+ }
+ last = header_append_param(last, name, value);
+ } else {
+ /* VAL*1="foo", save for later */
+ *index++ = 0;
+ work = g_malloc(sizeof(*work));
+ work->param.name = name;
+ work->param.value = value;
+ work->index = atoi(index);
+ if (split == NULL)
+ split = g_ptr_array_new();
+ g_ptr_array_add(split, work);
+ }
+ } else {
+ last = header_append_param(last, name, value);
+ }
+ } else {
+ g_free(name);
+ g_free(value);
+ }
+
+ header_decode_lwsp(&inptr);
+ }
+
+ /* Rejoin any RFC 2184 split parameters in the proper order */
+ /* Parameters with the same index will be concatenated in undefined order */
+ if (split) {
+ GString *value = g_string_new("");
+ struct _rfc2184_param *first;
+ int i;
+
+ qsort(split->pdata, split->len, sizeof(split->pdata[0]), rfc2184_param_cmp);
+ first = split->pdata[0];
+ for (i=0;i<split->len;i++) {
+ work = split->pdata[i];
+ if (split->len-1 == i)
+ g_string_append(value, work->param.value);
+ if (split->len-1 == i || strcmp(work->param.name, first->param.name) != 0) {
+ tmp = rfc2184_decode(value->str, value->len);
+ if (tmp == NULL)
+ tmp = g_strdup(value->str);
+
+ last = header_append_param(last, g_strdup(first->param.name), tmp);
+ g_string_truncate(value, 0);
+ first = work;
+ }
+ if (split->len-1 != i)
+ g_string_append(value, work->param.value);
+ }
+ g_string_free(value, TRUE);
+ for (i=0;i<split->len;i++) {
+ work = split->pdata[i];
+ g_free(work->param.name);
+ g_free(work->param.value);
+ g_free(work);
+ }
+ g_ptr_array_free(split, TRUE);
+ }
+
+ *in = inptr;
+
+ return head;
+}
+
+struct _camel_header_param *
+camel_header_param_list_decode(const char *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_param_list(&in);
+}
+
+static char *
+header_encode_param (const unsigned char *in, gboolean *encoded)
+{
+ const unsigned char *inptr = in;
+ unsigned char *outbuf = NULL;
+ const char *charset;
+ int encoding;
+ GString *out;
+ guint32 c;
+
+ *encoded = FALSE;
+
+ g_return_val_if_fail (in != NULL, NULL);
+
+ /* do a quick us-ascii check (the common case?) */
+ while (*inptr) {
+ if (*inptr > 127)
+ break;
+ inptr++;
+ }
+
+ if (*inptr == '\0')
+ return g_strdup (in);
+
+ inptr = in;
+ encoding = 0;
+ while ( encoding !=2 && (c = camel_utf8_getc(&inptr)) ) {
+ if (c > 127 && c < 256)
+ encoding = MAX (encoding, 1);
+ else if (c >= 256)
+ encoding = MAX (encoding, 2);
+ }
+
+ if (encoding == 2)
+ charset = camel_charset_best(in, strlen(in));
+ else
+ charset = "iso-8859-1";
+
+ if (strcasecmp(charset, "UTF-8") != 0
+ && (outbuf = header_convert(charset, "UTF-8", in, strlen(in)))) {
+ inptr = outbuf;
+ } else {
+ charset = "UTF-8";
+ inptr = in;
+ }
+
+ /* FIXME: set the 'language' as well, assuming we can get that info...? */
+ out = g_string_new (charset);
+ g_string_append(out, "''");
+
+ while ( (c = *inptr++) ) {
+ if (camel_mime_is_attrchar(c))
+ g_string_append_c (out, c);
+ else
+ g_string_append_printf (out, "%%%c%c", tohex[(c >> 4) & 0xf], tohex[c & 0xf]);
+ }
+ g_free (outbuf);
+
+ outbuf = out->str;
+ g_string_free (out, FALSE);
+ *encoded = TRUE;
+
+ return outbuf;
+}
+
+void
+camel_header_param_list_format_append (GString *out, struct _camel_header_param *p)
+{
+ int used = out->len;
+
+ while (p) {
+ gboolean encoded = FALSE;
+ gboolean quote = FALSE;
+ int here = out->len;
+ size_t nlen, vlen;
+ char *value;
+
+ if (!p->value) {
+ p = p->next;
+ continue;
+ }
+
+ value = header_encode_param (p->value, &encoded);
+ if (!value) {
+ w(g_warning ("appending parameter %s=%s violates rfc2184", p->name, p->value));
+ value = g_strdup (p->value);
+ }
+
+ if (!encoded) {
+ char *ch;
+
+ for (ch = value; *ch; ch++) {
+ if (camel_mime_is_tspecial (*ch) || camel_mime_is_lwsp (*ch))
+ break;
+ }
+
+ quote = ch && *ch;
+ }
+
+ nlen = strlen (p->name);
+ vlen = strlen (value);
+
+ if (used + nlen + vlen > CAMEL_FOLD_SIZE - 8) {
+ out = g_string_append (out, ";\n\t");
+ here = out->len;
+ used = 0;
+ } else
+ out = g_string_append (out, "; ");
+
+ if (nlen + vlen > CAMEL_FOLD_SIZE - 8) {
+ /* we need to do special rfc2184 parameter wrapping */
+ int maxlen = CAMEL_FOLD_SIZE - (nlen + 8);
+ char *inptr, *inend;
+ int i = 0;
+
+ inptr = value;
+ inend = value + vlen;
+
+ while (inptr < inend) {
+ char *ptr = inptr + MIN (inend - inptr, maxlen);
+
+ if (encoded && ptr < inend) {
+ /* be careful not to break an encoded char (ie %20) */
+ char *q = ptr;
+ int j = 2;
+
+ for ( ; j > 0 && q > inptr && *q != '%'; j--, q--);
+ if (*q == '%')
+ ptr = q;
+ }
+
+ if (i != 0) {
+ g_string_append (out, ";\n\t");
+ here = out->len;
+ used = 0;
+ }
+
+ g_string_append_printf (out, "%s*%d%s=", p->name, i++, encoded ? "*" : "");
+ if (encoded || !quote)
+ g_string_append_len (out, inptr, ptr - inptr);
+ else
+ quote_word (out, TRUE, inptr, ptr - inptr);
+
+ d(printf ("wrote: %s\n", out->str + here));
+
+ used += (out->len - here);
+
+ inptr = ptr;
+ }
+ } else {
+ g_string_append_printf (out, "%s%s=", p->name, encoded ? "*" : "");
+
+ if (encoded || !quote)
+ g_string_append (out, value);
+ else
+ quote_word (out, TRUE, value, vlen);
+
+ used += (out->len - here);
+ }
+
+ g_free (value);
+
+ p = p->next;
+ }
+}
+
+char *
+camel_header_param_list_format(struct _camel_header_param *p)
+{
+ GString *out = g_string_new("");
+ char *ret;
+
+ camel_header_param_list_format_append(out, p);
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+CamelContentType *
+camel_content_type_decode(const char *in)
+{
+ const char *inptr = in;
+ char *type, *subtype = NULL;
+ CamelContentType *t = NULL;
+
+ if (in==NULL)
+ return NULL;
+
+ type = decode_token(&inptr);
+ header_decode_lwsp(&inptr);
+ if (type) {
+ if (*inptr == '/') {
+ inptr++;
+ subtype = decode_token(&inptr);
+ }
+ if (subtype == NULL && (!strcasecmp(type, "text"))) {
+ w(g_warning("text type with no subtype, resorting to text/plain: %s", in));
+ subtype = g_strdup("plain");
+ }
+ if (subtype == NULL) {
+ w(g_warning("MIME type with no subtype: %s", in));
+ }
+
+ t = camel_content_type_new(type, subtype);
+ t->params = header_decode_param_list(&inptr);
+ g_free(type);
+ g_free(subtype);
+ } else {
+ g_free(type);
+ d(printf("cannot find MIME type in header (2) '%s'", in));
+ }
+ return t;
+}
+
+void
+camel_content_type_dump(CamelContentType *ct)
+{
+ struct _camel_header_param *p;
+
+ printf("Content-Type: ");
+ if (ct==NULL) {
+ printf("<NULL>\n");
+ return;
+ }
+ printf("%s / %s", ct->type, ct->subtype);
+ p = ct->params;
+ if (p) {
+ while (p) {
+ printf(";\n\t%s=\"%s\"", p->name, p->value);
+ p = p->next;
+ }
+ }
+ printf("\n");
+}
+
+char *
+camel_content_type_format (CamelContentType *ct)
+{
+ GString *out;
+ char *ret;
+
+ if (ct == NULL)
+ return NULL;
+
+ out = g_string_new ("");
+ if (ct->type == NULL) {
+ g_string_append_printf (out, "text/plain");
+ w(g_warning ("Content-Type with no main type"));
+ } else if (ct->subtype == NULL) {
+ w(g_warning ("Content-Type with no sub type: %s", ct->type));
+ if (!g_ascii_strcasecmp (ct->type, "multipart"))
+ g_string_append_printf (out, "%s/mixed", ct->type);
+ else
+ g_string_append_printf (out, "%s", ct->type);
+ } else {
+ g_string_append_printf (out, "%s/%s", ct->type, ct->subtype);
+ }
+ camel_header_param_list_format_append (out, ct->params);
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+char *
+camel_content_type_simple (CamelContentType *ct)
+{
+ if (ct->type == NULL) {
+ w(g_warning ("Content-Type with no main type"));
+ return g_strdup ("text/plain");
+ } else if (ct->subtype == NULL) {
+ w(g_warning ("Content-Type with no sub type: %s", ct->type));
+ if (!g_ascii_strcasecmp (ct->type, "multipart"))
+ return g_strdup_printf ("%s/mixed", ct->type);
+ else
+ return g_strdup (ct->type);
+ } else
+ return g_strdup_printf ("%s/%s", ct->type, ct->subtype);
+}
+
+char *
+camel_content_transfer_encoding_decode (const char *in)
+{
+ if (in)
+ return decode_token (&in);
+
+ return NULL;
+}
+
+CamelContentDisposition *
+camel_content_disposition_decode(const char *in)
+{
+ CamelContentDisposition *d = NULL;
+ const char *inptr = in;
+
+ if (in == NULL)
+ return NULL;
+
+ d = g_malloc(sizeof(*d));
+ d->refcount = 1;
+ d->disposition = decode_token(&inptr);
+ if (d->disposition == NULL)
+ w(g_warning("Empty disposition type"));
+ d->params = header_decode_param_list(&inptr);
+ return d;
+}
+
+void
+camel_content_disposition_ref(CamelContentDisposition *d)
+{
+ if (d)
+ d->refcount++;
+}
+
+void
+camel_content_disposition_unref(CamelContentDisposition *d)
+{
+ if (d) {
+ if (d->refcount<=1) {
+ camel_header_param_list_free(d->params);
+ g_free(d->disposition);
+ g_free(d);
+ } else {
+ d->refcount--;
+ }
+ }
+}
+
+char *
+camel_content_disposition_format(CamelContentDisposition *d)
+{
+ GString *out;
+ char *ret;
+
+ if (d==NULL)
+ return NULL;
+
+ out = g_string_new("");
+ if (d->disposition)
+ out = g_string_append(out, d->disposition);
+ else
+ out = g_string_append(out, "attachment");
+ camel_header_param_list_format_append(out, d->params);
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+/* hrm, is there a library for this shit? */
+static struct {
+ char *name;
+ int offset;
+} tz_offsets [] = {
+ { "UT", 0 },
+ { "GMT", 0 },
+ { "EST", -500 }, /* these are all US timezones. bloody yanks */
+ { "EDT", -400 },
+ { "CST", -600 },
+ { "CDT", -500 },
+ { "MST", -700 },
+ { "MDT", -600 },
+ { "PST", -800 },
+ { "PDT", -700 },
+ { "Z", 0 },
+ { "A", -100 },
+ { "M", -1200 },
+ { "N", 100 },
+ { "Y", 1200 },
+};
+
+static char *tz_months [] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static char *tz_days [] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+char *
+camel_header_format_date(time_t time, int offset)
+{
+ struct tm tm;
+
+ d(printf("offset = %d\n", offset));
+
+ d(printf("converting date %s", ctime(&time)));
+
+ time += ((offset / 100) * (60*60)) + (offset % 100)*60;
+
+ d(printf("converting date %s", ctime(&time)));
+
+ gmtime_r (&time, &tm);
+
+ return g_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %+05d",
+ tz_days[tm.tm_wday],
+ tm.tm_mday, tz_months[tm.tm_mon],
+ tm.tm_year + 1900,
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ offset);
+}
+
+/* convert a date to time_t representation */
+/* this is an awful mess oh well */
+time_t
+camel_header_decode_date(const char *in, int *saveoffset)
+{
+ const char *inptr = in;
+ char *monthname;
+ gboolean foundmonth;
+ int year, offset = 0;
+ struct tm tm;
+ int i;
+ time_t t;
+
+ if (in == NULL) {
+ if (saveoffset)
+ *saveoffset = 0;
+ return 0;
+ }
+
+ d(printf ("\ndecoding date '%s'\n", inptr));
+
+ memset (&tm, 0, sizeof(tm));
+
+ header_decode_lwsp (&inptr);
+ if (!isdigit (*inptr)) {
+ char *day = decode_token (&inptr);
+ /* we dont really care about the day, it's only for display */
+ if (day) {
+ d(printf ("got day: %s\n", day));
+ g_free (day);
+ header_decode_lwsp (&inptr);
+ if (*inptr == ',') {
+ inptr++;
+ } else {
+#ifndef CLEAN_DATE
+ return parse_broken_date (in, saveoffset);
+#else
+ if (saveoffset)
+ *saveoffset = 0;
+ return 0;
+#endif /* ! CLEAN_DATE */
+ }
+ }
+ }
+ tm.tm_mday = camel_header_decode_int(&inptr);
+#ifndef CLEAN_DATE
+ if (tm.tm_mday == 0) {
+ return parse_broken_date (in, saveoffset);
+ }
+#endif /* ! CLEAN_DATE */
+
+ monthname = decode_token(&inptr);
+ foundmonth = FALSE;
+ if (monthname) {
+ for (i=0;i<sizeof(tz_months)/sizeof(tz_months[0]);i++) {
+ if (!g_ascii_strcasecmp(tz_months[i], monthname)) {
+ tm.tm_mon = i;
+ foundmonth = TRUE;
+ break;
+ }
+ }
+ g_free(monthname);
+ }
+#ifndef CLEAN_DATE
+ if (!foundmonth) {
+ return parse_broken_date (in, saveoffset);
+ }
+#endif /* ! CLEAN_DATE */
+
+ year = camel_header_decode_int(&inptr);
+ if (year < 69) {
+ tm.tm_year = 100 + year;
+ } else if (year < 100) {
+ tm.tm_year = year;
+ } else if (year >= 100 && year < 1900) {
+ tm.tm_year = year;
+ } else {
+ tm.tm_year = year - 1900;
+ }
+ /* get the time ... yurck */
+ tm.tm_hour = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ':')
+ inptr++;
+ tm.tm_min = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ':')
+ inptr++;
+ tm.tm_sec = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == '+'
+ || *inptr == '-') {
+ offset = (*inptr++)=='-'?-1:1;
+ offset = offset * camel_header_decode_int(&inptr);
+ d(printf("abs signed offset = %d\n", offset));
+ if (offset < -1200 || offset > 1400)
+ offset = 0;
+ } else if (isdigit(*inptr)) {
+ offset = camel_header_decode_int(&inptr);
+ d(printf("abs offset = %d\n", offset));
+ if (offset < -1200 || offset > 1400)
+ offset = 0;
+ } else {
+ char *tz = decode_token(&inptr);
+
+ if (tz) {
+ for (i=0;i<sizeof(tz_offsets)/sizeof(tz_offsets[0]);i++) {
+ if (!g_ascii_strcasecmp(tz_offsets[i].name, tz)) {
+ offset = tz_offsets[i].offset;
+ break;
+ }
+ }
+ g_free(tz);
+ }
+ /* some broken mailers seem to put in things like GMT+1030 instead of just +1030 */
+ header_decode_lwsp(&inptr);
+ if (*inptr == '+' || *inptr == '-') {
+ int sign = (*inptr++)=='-'?-1:1;
+ offset = offset + (camel_header_decode_int(&inptr)*sign);
+ }
+ d(printf("named offset = %d\n", offset));
+ }
+
+ t = e_mktime_utc(&tm);
+
+ /* t is now GMT of the time we want, but not offset by the timezone ... */
+
+ d(printf(" gmt normalized? = %s\n", ctime(&t)));
+
+ /* this should convert the time to the GMT equiv time */
+ t -= ( (offset/100) * 60*60) + (offset % 100)*60;
+
+ d(printf(" gmt normalized for timezone? = %s\n", ctime(&t)));
+
+ d({
+ char *tmp;
+ tmp = camel_header_format_date(t, offset);
+ printf(" encoded again: %s\n", tmp);
+ g_free(tmp);
+ });
+
+ if (saveoffset)
+ *saveoffset = offset;
+
+ return t;
+}
+
+char *
+camel_header_location_decode(const char *in)
+{
+ int quote = 0;
+ GString *out = g_string_new("");
+ char c, *res;
+
+ /* Sigh. RFC2557 says:
+ * content-location = "Content-Location:" [CFWS] URI [CFWS]
+ * where URI is restricted to the syntax for URLs as
+ * defined in Uniform Resource Locators [URL] until
+ * IETF specifies other kinds of URIs.
+ *
+ * But Netscape puts quotes around the URI when sending web
+ * pages.
+ *
+ * Which is required as defined in rfc2017 [3.1]. Although
+ * outlook doesn't do this.
+ *
+ * Since we get headers already unfolded, we need just drop
+ * all whitespace. URL's cannot contain whitespace or quoted
+ * characters, even when included in quotes.
+ */
+
+ header_decode_lwsp(&in);
+ if (*in == '"') {
+ in++;
+ quote = 1;
+ }
+
+ while ( (c = *in++) ) {
+ if (quote && c=='"')
+ break;
+ if (!camel_mime_is_lwsp(c))
+ g_string_append_c(out, c);
+ }
+
+ res = g_strdup(out->str);
+ g_string_free(out, TRUE);
+
+ return res;
+}
+
+/* extra rfc checks */
+#define CHECKS
+
+#ifdef CHECKS
+static void
+check_header(struct _camel_header_raw *h)
+{
+ unsigned char *p;
+
+ p = h->value;
+ while (p && *p) {
+ if (!isascii(*p)) {
+ w(g_warning("Appending header violates rfc: %s: %s", h->name, h->value));
+ return;
+ }
+ p++;
+ }
+}
+#endif
+
+void
+camel_header_raw_append_parse(struct _camel_header_raw **list, const char *header, int offset)
+{
+ register const char *in;
+ size_t fieldlen;
+ char *name;
+
+ in = header;
+ while (camel_mime_is_fieldname(*in) || *in==':')
+ in++;
+ fieldlen = in-header-1;
+ while (camel_mime_is_lwsp(*in))
+ in++;
+ if (fieldlen == 0 || header[fieldlen] != ':') {
+ printf("Invalid header line: '%s'\n", header);
+ return;
+ }
+ name = g_alloca (fieldlen + 1);
+ memcpy(name, header, fieldlen);
+ name[fieldlen] = 0;
+
+ camel_header_raw_append(list, name, in, offset);
+}
+
+void
+camel_header_raw_append(struct _camel_header_raw **list, const char *name, const char *value, int offset)
+{
+ struct _camel_header_raw *l, *n;
+
+ d(printf("Header: %s: %s\n", name, value));
+
+ n = g_malloc(sizeof(*n));
+ n->next = NULL;
+ n->name = g_strdup(name);
+ n->value = g_strdup(value);
+ n->offset = offset;
+#ifdef CHECKS
+ check_header(n);
+#endif
+ l = (struct _camel_header_raw *)list;
+ while (l->next) {
+ l = l->next;
+ }
+ l->next = n;
+
+ /* debug */
+#if 0
+ if (!strcasecmp(name, "To")) {
+ printf("- Decoding To\n");
+ camel_header_to_decode(value);
+ } else if (!strcasecmp(name, "Content-type")) {
+ printf("- Decoding content-type\n");
+ camel_content_type_dump(camel_content_type_decode(value));
+ } else if (!strcasecmp(name, "MIME-Version")) {
+ printf("- Decoding mime version\n");
+ camel_header_mime_decode(value);
+ }
+#endif
+}
+
+static struct _camel_header_raw *
+header_raw_find_node(struct _camel_header_raw **list, const char *name)
+{
+ struct _camel_header_raw *l;
+
+ l = *list;
+ while (l) {
+ if (!g_ascii_strcasecmp(l->name, name))
+ break;
+ l = l->next;
+ }
+ return l;
+}
+
+const char *
+camel_header_raw_find(struct _camel_header_raw **list, const char *name, int *offset)
+{
+ struct _camel_header_raw *l;
+
+ l = header_raw_find_node(list, name);
+ if (l) {
+ if (offset)
+ *offset = l->offset;
+ return l->value;
+ } else
+ return NULL;
+}
+
+const char *
+camel_header_raw_find_next(struct _camel_header_raw **list, const char *name, int *offset, const char *last)
+{
+ struct _camel_header_raw *l;
+
+ if (last == NULL || name == NULL)
+ return NULL;
+
+ l = *list;
+ while (l && l->value != last)
+ l = l->next;
+ return camel_header_raw_find(&l, name, offset);
+}
+
+static void
+header_raw_free(struct _camel_header_raw *l)
+{
+ g_free(l->name);
+ g_free(l->value);
+ g_free(l);
+}
+
+void
+camel_header_raw_remove(struct _camel_header_raw **list, const char *name)
+{
+ struct _camel_header_raw *l, *p;
+
+ /* the next pointer is at the head of the structure, so this is safe */
+ p = (struct _camel_header_raw *)list;
+ l = *list;
+ while (l) {
+ if (!g_ascii_strcasecmp(l->name, name)) {
+ p->next = l->next;
+ header_raw_free(l);
+ l = p->next;
+ } else {
+ p = l;
+ l = l->next;
+ }
+ }
+}
+
+void
+camel_header_raw_replace(struct _camel_header_raw **list, const char *name, const char *value, int offset)
+{
+ camel_header_raw_remove(list, name);
+ camel_header_raw_append(list, name, value, offset);
+}
+
+void
+camel_header_raw_clear(struct _camel_header_raw **list)
+{
+ struct _camel_header_raw *l, *n;
+ l = *list;
+ while (l) {
+ n = l->next;
+ header_raw_free(l);
+ l = n;
+ }
+ *list = NULL;
+}
+
+char *
+camel_header_msgid_generate (void)
+{
+ static pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;
+#define COUNT_LOCK() pthread_mutex_lock (&count_lock)
+#define COUNT_UNLOCK() pthread_mutex_unlock (&count_lock)
+ char host[MAXHOSTNAMELEN];
+ char *name;
+ static int count = 0;
+ char *msgid;
+ int retval;
+ struct addrinfo *ai = NULL, hints = { 0 };
+
+ retval = gethostname (host, sizeof (host));
+ if (retval == 0 && *host) {
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(host, NULL, &hints, NULL);
+ if (ai && ai->ai_canonname)
+ name = ai->ai_canonname;
+ else
+ name = host;
+ } else
+ name = "localhost.localdomain";
+
+ COUNT_LOCK ();
+ msgid = g_strdup_printf ("%d.%d.%d.camel@%s", (int) time (NULL), getpid (), count++, name);
+ COUNT_UNLOCK ();
+
+ if (ai)
+ camel_freeaddrinfo(ai);
+
+ return msgid;
+}
+
+
+static struct {
+ char *name;
+ char *pattern;
+ regex_t regex;
+} mail_list_magic[] = {
+ /* List-Post: <mailto:gnome-hackers@gnome.org> */
+ /* List-Post: <mailto:gnome-hackers> */
+ { "List-Post", "[ \t]*<mailto:([^@>]+)@?([^ \n\t\r>]*)" },
+ /* List-Id: GNOME stuff <gnome-hackers.gnome.org> */
+ /* List-Id: <gnome-hackers.gnome.org> */
+ /* List-Id: <gnome-hackers> */
+ /* This old one wasn't very useful: { "List-Id", " *([^<]+)" },*/
+ { "List-Id", "[^<]*<([^\\.>]+)\\.?([^ \n\t\r>]*)" },
+ /* Mailing-List: list gnome-hackers@gnome.org; contact gnome-hackers-owner@gnome.org */
+ { "Mailing-List", "[ \t]*list ([^@]+)@?([^ \n\t\r>;]*)" },
+ /* Originator: gnome-hackers@gnome.org */
+ { "Originator", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* X-Mailing-List: <gnome-hackers@gnome.org> arcive/latest/100 */
+ /* X-Mailing-List: gnome-hackers@gnome.org */
+ /* X-Mailing-List: gnome-hackers */
+ /* X-Mailing-List: <gnome-hackers> */
+ { "X-Mailing-List", "[ \t]*<?([^@>]+)@?([^ \n\t\r>]*)" },
+ /* X-Loop: gnome-hackers@gnome.org */
+ { "X-Loop", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* X-List: gnome-hackers */
+ /* X-List: gnome-hackers@gnome.org */
+ { "X-List", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* Sender: owner-gnome-hackers@gnome.org */
+ /* Sender: owner-gnome-hacekrs */
+ { "Sender", "[ \t]*owner-([^@]+)@?([^ @\n\t\r>]*)" },
+ /* Sender: gnome-hackers-owner@gnome.org */
+ /* Sender: gnome-hackers-owner */
+ { "Sender", "[ \t]*([^@]+)-owner@?([^ @\n\t\r>]*)" },
+ /* Delivered-To: mailing list gnome-hackers@gnome.org */
+ /* Delivered-To: mailing list gnome-hackers */
+ { "Delivered-To", "[ \t]*mailing list ([^@]+)@?([^ \n\t\r>]*)" },
+ /* Sender: owner-gnome-hackers@gnome.org */
+ /* Sender: <owner-gnome-hackers@gnome.org> */
+ /* Sender: owner-gnome-hackers */
+ /* Sender: <owner-gnome-hackers> */
+ { "Return-Path", "[ \t]*<?owner-([^@>]+)@?([^ \n\t\r>]*)" },
+ /* X-BeenThere: gnome-hackers@gnome.org */
+ /* X-BeenThere: gnome-hackers */
+ { "X-BeenThere", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* List-Unsubscribe: <mailto:gnome-hackers-unsubscribe@gnome.org> */
+ { "List-Unsubscribe", "<mailto:(.+)-unsubscribe@([^ \n\t\r>]*)" },
+};
+
+char *
+camel_header_raw_check_mailing_list(struct _camel_header_raw **list)
+{
+ const char *v;
+ regmatch_t match[3];
+ int i, j;
+
+ for (i = 0; i < sizeof (mail_list_magic) / sizeof (mail_list_magic[0]); i++) {
+ v = camel_header_raw_find (list, mail_list_magic[i].name, NULL);
+ for (j=0;j<3;j++) {
+ match[j].rm_so = -1;
+ match[j].rm_eo = -1;
+ }
+ if (v != NULL && regexec (&mail_list_magic[i].regex, v, 3, match, 0) == 0 && match[1].rm_so != -1) {
+ char *list;
+ int len1, len2;
+
+ len1 = match[1].rm_eo - match[1].rm_so;
+ len2 = match[2].rm_eo - match[2].rm_so;
+
+ list = g_malloc(len1+len2+2);
+ memcpy(list, v + match[1].rm_so, len1);
+ if (len2) {
+ list[len1] = '@';
+ memcpy(list+len1+1, v+match[2].rm_so, len2);
+ list[len1+len2+1]=0;
+ } else {
+ list[len1] = 0;
+ }
+
+ return list;
+ }
+ }
+
+ return NULL;
+}
+
+/* ok, here's the address stuff, what a mess ... */
+struct _camel_header_address *
+camel_header_address_new (void)
+{
+ struct _camel_header_address *h;
+ h = g_malloc0(sizeof(*h));
+ h->type = CAMEL_HEADER_ADDRESS_NONE;
+ h->refcount = 1;
+ return h;
+}
+
+struct _camel_header_address *
+camel_header_address_new_name(const char *name, const char *addr)
+{
+ struct _camel_header_address *h;
+ h = camel_header_address_new();
+ h->type = CAMEL_HEADER_ADDRESS_NAME;
+ h->name = g_strdup(name);
+ h->v.addr = g_strdup(addr);
+ return h;
+}
+
+struct _camel_header_address *
+camel_header_address_new_group (const char *name)
+{
+ struct _camel_header_address *h;
+
+ h = camel_header_address_new();
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ h->name = g_strdup(name);
+ return h;
+}
+
+void
+camel_header_address_ref(struct _camel_header_address *h)
+{
+ if (h)
+ h->refcount++;
+}
+
+void
+camel_header_address_unref(struct _camel_header_address *h)
+{
+ if (h) {
+ if (h->refcount <= 1) {
+ if (h->type == CAMEL_HEADER_ADDRESS_GROUP) {
+ camel_header_address_list_clear(&h->v.members);
+ } else if (h->type == CAMEL_HEADER_ADDRESS_NAME) {
+ g_free(h->v.addr);
+ }
+ g_free(h->name);
+ g_free(h);
+ } else {
+ h->refcount--;
+ }
+ }
+}
+
+void
+camel_header_address_set_name(struct _camel_header_address *h, const char *name)
+{
+ if (h) {
+ g_free(h->name);
+ h->name = g_strdup(name);
+ }
+}
+
+void
+camel_header_address_set_addr(struct _camel_header_address *h, const char *addr)
+{
+ if (h) {
+ if (h->type == CAMEL_HEADER_ADDRESS_NAME
+ || h->type == CAMEL_HEADER_ADDRESS_NONE) {
+ h->type = CAMEL_HEADER_ADDRESS_NAME;
+ g_free(h->v.addr);
+ h->v.addr = g_strdup(addr);
+ } else {
+ g_warning("Trying to set the address on a group");
+ }
+ }
+}
+
+void
+camel_header_address_set_members(struct _camel_header_address *h, struct _camel_header_address *group)
+{
+ if (h) {
+ if (h->type == CAMEL_HEADER_ADDRESS_GROUP
+ || h->type == CAMEL_HEADER_ADDRESS_NONE) {
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ camel_header_address_list_clear(&h->v.members);
+ /* should this ref them? */
+ h->v.members = group;
+ } else {
+ g_warning("Trying to set the members on a name, not group");
+ }
+ }
+}
+
+void
+camel_header_address_add_member(struct _camel_header_address *h, struct _camel_header_address *member)
+{
+ if (h) {
+ if (h->type == CAMEL_HEADER_ADDRESS_GROUP
+ || h->type == CAMEL_HEADER_ADDRESS_NONE) {
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ camel_header_address_list_append(&h->v.members, member);
+ }
+ }
+}
+
+void
+camel_header_address_list_append_list(struct _camel_header_address **l, struct _camel_header_address **h)
+{
+ if (l) {
+ struct _camel_header_address *n = (struct _camel_header_address *)l;
+
+ while (n->next)
+ n = n->next;
+ n->next = *h;
+ }
+}
+
+
+void
+camel_header_address_list_append(struct _camel_header_address **l, struct _camel_header_address *h)
+{
+ if (h) {
+ camel_header_address_list_append_list(l, &h);
+ h->next = NULL;
+ }
+}
+
+void
+camel_header_address_list_clear(struct _camel_header_address **l)
+{
+ struct _camel_header_address *a, *n;
+ a = *l;
+ while (a) {
+ n = a->next;
+ camel_header_address_unref(a);
+ a = n;
+ }
+ *l = NULL;
+}
+
+/* if encode is true, then the result is suitable for mailing, otherwise
+ the result is suitable for display only (and may not even be re-parsable) */
+static void
+header_address_list_encode_append (GString *out, int encode, struct _camel_header_address *a)
+{
+ char *text;
+
+ while (a) {
+ switch (a->type) {
+ case CAMEL_HEADER_ADDRESS_NAME:
+ if (encode)
+ text = camel_header_encode_phrase (a->name);
+ else
+ text = a->name;
+ if (text && *text)
+ g_string_append_printf (out, "%s <%s>", text, a->v.addr);
+ else
+ g_string_append (out, a->v.addr);
+ if (encode)
+ g_free (text);
+ break;
+ case CAMEL_HEADER_ADDRESS_GROUP:
+ if (encode)
+ text = camel_header_encode_phrase (a->name);
+ else
+ text = a->name;
+ g_string_append_printf (out, "%s: ", text);
+ header_address_list_encode_append (out, encode, a->v.members);
+ g_string_append_printf (out, ";");
+ if (encode)
+ g_free (text);
+ break;
+ default:
+ g_warning ("Invalid address type");
+ break;
+ }
+ a = a->next;
+ if (a)
+ g_string_append (out, ", ");
+ }
+}
+
+char *
+camel_header_address_list_encode (struct _camel_header_address *a)
+{
+ GString *out;
+ char *ret;
+
+ if (a == NULL)
+ return NULL;
+
+ out = g_string_new ("");
+ header_address_list_encode_append (out, TRUE, a);
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+char *
+camel_header_address_list_format (struct _camel_header_address *a)
+{
+ GString *out;
+ char *ret;
+
+ if (a == NULL)
+ return NULL;
+
+ out = g_string_new ("");
+
+ header_address_list_encode_append (out, FALSE, a);
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+char *
+camel_header_address_fold (const char *in, size_t headerlen)
+{
+ size_t len, outlen;
+ const char *inptr = in, *space, *p, *n;
+ GString *out;
+ char *ret;
+ int i, needunfold = FALSE;
+
+ if (in == NULL)
+ return NULL;
+
+ /* first, check to see if we even need to fold */
+ len = headerlen + 2;
+ p = in;
+ while (*p) {
+ n = strchr (p, '\n');
+ if (n == NULL) {
+ len += strlen (p);
+ break;
+ }
+
+ needunfold = TRUE;
+ len += n-p;
+
+ if (len >= CAMEL_FOLD_SIZE)
+ break;
+ len = 0;
+ p = n + 1;
+ }
+ if (len < CAMEL_FOLD_SIZE)
+ return g_strdup (in);
+
+ /* we need to fold, so first unfold (if we need to), then process */
+ if (needunfold)
+ inptr = in = camel_header_unfold (in);
+
+ out = g_string_new ("");
+ outlen = headerlen + 2;
+ while (*inptr) {
+ space = strchr (inptr, ' ');
+ if (space) {
+ len = space - inptr + 1;
+ } else {
+ len = strlen (inptr);
+ }
+
+ d(printf("next word '%.*s'\n", len, inptr));
+
+ if (outlen + len > CAMEL_FOLD_SIZE) {
+ d(printf("outlen = %d wordlen = %d\n", outlen, len));
+ /* strip trailing space */
+ if (out->len > 0 && out->str[out->len-1] == ' ')
+ g_string_truncate (out, out->len-1);
+ g_string_append (out, "\n\t");
+ outlen = 1;
+ }
+
+ outlen += len;
+ for (i = 0; i < len; i++) {
+ g_string_append_c (out, inptr[i]);
+ }
+
+ inptr += len;
+ }
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ if (needunfold)
+ g_free ((char *)in);
+
+ return ret;
+}
+
+/* simple header folding */
+/* will work even if the header is already folded */
+char *
+camel_header_fold(const char *in, size_t headerlen)
+{
+ size_t len, outlen, i;
+ const char *inptr = in, *space, *p, *n;
+ GString *out;
+ char *ret;
+ int needunfold = FALSE;
+
+ if (in == NULL)
+ return NULL;
+
+ /* first, check to see if we even need to fold */
+ len = headerlen + 2;
+ p = in;
+ while (*p) {
+ n = strchr(p, '\n');
+ if (n == NULL) {
+ len += strlen (p);
+ break;
+ }
+
+ needunfold = TRUE;
+ len += n-p;
+
+ if (len >= CAMEL_FOLD_SIZE)
+ break;
+ len = 0;
+ p = n + 1;
+ }
+ if (len < CAMEL_FOLD_SIZE)
+ return g_strdup(in);
+
+ /* we need to fold, so first unfold (if we need to), then process */
+ if (needunfold)
+ inptr = in = camel_header_unfold(in);
+
+ out = g_string_new("");
+ outlen = headerlen+2;
+ while (*inptr) {
+ space = strchr(inptr, ' ');
+ if (space) {
+ len = space-inptr+1;
+ } else {
+ len = strlen(inptr);
+ }
+ d(printf("next word '%.*s'\n", len, inptr));
+ if (outlen + len > CAMEL_FOLD_SIZE) {
+ d(printf("outlen = %d wordlen = %d\n", outlen, len));
+ /* strip trailing space */
+ if (out->len > 0 && out->str[out->len-1] == ' ')
+ g_string_truncate(out, out->len-1);
+ g_string_append(out, "\n\t");
+ outlen = 1;
+ /* check for very long words, just cut them up */
+ while (outlen+len > CAMEL_FOLD_MAX_SIZE) {
+ for (i=0;i<CAMEL_FOLD_MAX_SIZE-outlen;i++)
+ g_string_append_c(out, inptr[i]);
+ inptr += CAMEL_FOLD_MAX_SIZE-outlen;
+ len -= CAMEL_FOLD_MAX_SIZE-outlen;
+ g_string_append(out, "\n\t");
+ outlen = 1;
+ }
+ }
+ outlen += len;
+ for (i=0;i<len;i++) {
+ g_string_append_c(out, inptr[i]);
+ }
+ inptr += len;
+ }
+ ret = out->str;
+ g_string_free(out, FALSE);
+
+ if (needunfold)
+ g_free((char *)in);
+
+ return ret;
+}
+
+char *
+camel_header_unfold(const char *in)
+{
+ char *out = g_malloc(strlen(in)+1);
+ const char *inptr = in;
+ char c, *o = out;
+
+ o = out;
+ while ((c = *inptr++)) {
+ if (c == '\n') {
+ if (camel_mime_is_lwsp(*inptr)) {
+ do {
+ inptr++;
+ } while (camel_mime_is_lwsp(*inptr));
+ *o++ = ' ';
+ } else {
+ *o++ = c;
+ }
+ } else {
+ *o++ = c;
+ }
+ }
+ *o = 0;
+
+ return out;
+}
+
+void
+camel_mime_utils_init(void)
+{
+ int i, errcode, regex_compilation_failed=0;
+
+ /* Init tables */
+ header_decode_init();
+ base64_init();
+
+ /* precompile regex's for speed at runtime */
+ for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++) {
+ errcode = regcomp(&mail_list_magic[i].regex, mail_list_magic[i].pattern, REG_EXTENDED|REG_ICASE);
+ if (errcode != 0) {
+ char *errstr;
+ size_t len;
+
+ len = regerror(errcode, &mail_list_magic[i].regex, NULL, 0);
+ errstr = g_malloc0(len + 1);
+ regerror(errcode, &mail_list_magic[i].regex, errstr, len);
+
+ g_warning("Internal error, compiling regex failed: %s: %s", mail_list_magic[i].pattern, errstr);
+ g_free(errstr);
+ regex_compilation_failed++;
+ }
+ }
+
+ g_assert(regex_compilation_failed == 0);
+}
+
+
+void
+camel_mime_utils_shutdown (void)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++)
+ regfree (&mail_list_magic[i].regex);
+}
diff --git a/camel/camel-movemail.c b/camel/camel-movemail.c
new file mode 100644
index 0000000000..9684bf25e6
--- /dev/null
+++ b/camel/camel-movemail.c
@@ -0,0 +1,540 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-movemail.c: mbox copying function */
+
+/*
+ * Author:
+ * Dan Winship <danw@ximian.com>
+ *
+ * Copyright 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+
+#include "camel-movemail.h"
+#include "camel-exception.h"
+
+#include "camel-mime-parser.h"
+#include "camel-mime-filter.h"
+#include "camel-mime-filter-from.h"
+
+#include "camel-lock-client.h"
+
+#define d(x)
+
+#ifdef MOVEMAIL_PATH
+#include <sys/wait.h>
+
+static void movemail_external (const char *source, const char *dest,
+ CamelException *ex);
+#endif
+
+#ifdef HAVE_BROKEN_SPOOL
+static int camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter);
+static int camel_movemail_solaris (int oldsfd, int dfd, CamelException *ex);
+#else
+/* these could probably be exposed as a utility? (but only mbox needs it) */
+static int camel_movemail_copy_file(int sfd, int dfd, CamelException *ex);
+#endif
+
+#if 0
+static int camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes);
+#endif
+
+/**
+ * camel_movemail: Copy an mbox file from a shared spool directory to a
+ * new folder in a Camel store
+ * @source: source file
+ * @dest: destination file
+ * @ex: a CamelException
+ *
+ * This copies an mbox file from a shared directory with multiple
+ * readers and writers into a private (presumably Camel-controlled)
+ * directory. Dot locking is used on the source file (but not the
+ * destination).
+ *
+ * Return Value: Returns -1 on error.
+ **/
+int
+camel_movemail(const char *source, const char *dest, CamelException *ex)
+{
+ int lockid = -1;
+ int res = -1;
+ int sfd, dfd;
+ struct stat st;
+
+ /* Stat and then open the spool file. If it doesn't exist or
+ * is empty, the user has no mail. (There's technically a race
+ * condition here in that an MDA might have just now locked it
+ * to deliver a message, but we don't care. In that case,
+ * assuming it's unlocked is equivalent to pretending we were
+ * called a fraction earlier.)
+ */
+ if (stat (source, &st) == -1) {
+ if (errno != ENOENT) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not check mail file %s: %s"),
+ source, g_strerror (errno));
+ }
+ return -1;
+ }
+
+ if (st.st_size == 0)
+ return 0;
+
+ /* open files */
+ sfd = open (source, O_RDWR);
+ if (sfd == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not open mail file %s: %s"),
+ source, g_strerror (errno));
+ return -1;
+ }
+
+ dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
+ if (dfd == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not open temporary mail "
+ "file %s: %s"), dest,
+ g_strerror (errno));
+ close (sfd);
+ return -1;
+ }
+
+ /* lock our source mailbox */
+ lockid = camel_lock_helper_lock(source, ex);
+ if (lockid == -1) {
+ close(sfd);
+ close(dfd);
+ return -1;
+ }
+
+#ifdef HAVE_BROKEN_SPOOL
+ res = camel_movemail_solaris(sfd, dfd, ex);
+#else
+ res = camel_movemail_copy_file(sfd, dfd, ex);
+#endif
+
+ /* If no errors occurred copying the data, and we successfully
+ * close the destination file, then truncate the source file.
+ */
+ if (res != -1) {
+ if (close (dfd) == 0) {
+ ftruncate (sfd, 0);
+ } else {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to store mail in temp file %s: %s"),
+ dest, g_strerror (errno));
+ res = -1;
+ }
+ } else
+ close (dfd);
+ close (sfd);
+
+ camel_lock_helper_unlock(lockid);
+
+ return res;
+}
+
+#ifdef MOVEMAIL_PATH
+static void
+movemail_external (const char *source, const char *dest, CamelException *ex)
+{
+ sigset_t mask, omask;
+ pid_t pid;
+ int fd[2], len = 0, nread, status;
+ char buf[BUFSIZ], *output = NULL;
+
+ /* Block SIGCHLD so the app can't mess us up. */
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGCHLD);
+ sigprocmask (SIG_BLOCK, &mask, &omask);
+
+ if (pipe (fd) == -1) {
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not create pipe: %s"),
+ g_strerror (errno));
+ return;
+ }
+
+ pid = fork ();
+ switch (pid) {
+ case -1:
+ close (fd[0]);
+ close (fd[1]);
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not fork: %s"),
+ g_strerror (errno));
+ return;
+
+ case 0:
+ /* Child */
+ close (fd[0]);
+ close (STDIN_FILENO);
+ dup2 (fd[1], STDOUT_FILENO);
+ dup2 (fd[1], STDERR_FILENO);
+
+ execl (MOVEMAIL_PATH, MOVEMAIL_PATH, source, dest, NULL);
+ _exit (255);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Parent */
+ close (fd[1]);
+
+ /* Read movemail's output. */
+ while ((nread = read (fd[0], buf, sizeof (buf))) > 0) {
+ output = g_realloc (output, len + nread + 1);
+ memcpy (output + len, buf, nread);
+ len += nread;
+ output[len] = '\0';
+ }
+ close (fd[0]);
+
+ /* Now get the exit status. */
+ while (waitpid (pid, &status, 0) == -1 && errno == EINTR)
+ ;
+ sigprocmask (SIG_SETMASK, &omask, NULL);
+
+ if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Movemail program failed: %s"),
+ output ? output : _("(Unknown error)"));
+ }
+ g_free (output);
+}
+#endif
+
+#ifndef HAVE_BROKEN_SPOOL
+static int
+camel_movemail_copy_file(int sfd, int dfd, CamelException *ex)
+{
+ int nread, nwrote;
+ char buf[4096];
+
+ while (1) {
+ int written = 0;
+
+ nread = read (sfd, buf, sizeof (buf));
+ if (nread == 0)
+ break;
+ else if (nread == -1) {
+ if (errno == EINTR)
+ continue;
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error reading mail file: %s"),
+ g_strerror (errno));
+ return -1;
+ }
+
+ while (nread) {
+ nwrote = write (dfd, buf + written, nread);
+ if (nwrote == -1) {
+ if (errno == EINTR)
+ continue; /* continues inner loop */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error writing mail temp file: %s"),
+ g_strerror (errno));
+ return -1;
+ }
+ written += nwrote;
+ nread -= nwrote;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+#if 0
+static int
+camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes)
+{
+ char buffer[4096];
+ int written = 0;
+
+ d(printf("writing %d bytes ... ", bytes));
+
+ if (lseek(fromfd, start, SEEK_SET) != start)
+ return -1;
+
+ while (bytes>0) {
+ int toread, towrite;
+
+ toread = bytes;
+ if (bytes>4096)
+ toread = 4096;
+ else
+ toread = bytes;
+ do {
+ towrite = read(fromfd, buffer, toread);
+ } while (towrite == -1 && errno == EINTR);
+
+ if (towrite == -1)
+ return -1;
+
+ /* check for 'end of file' */
+ if (towrite == 0) {
+ d(printf("end of file?\n"));
+ break;
+ }
+
+ do {
+ toread = write(tofd, buffer, towrite);
+ } while (toread == -1 && errno == EINTR);
+
+ if (toread == -1)
+ return -1;
+
+ written += toread;
+ bytes -= toread;
+ }
+
+ d(printf("written %d bytes\n", written));
+
+ return written;
+}
+#endif
+
+#define PRE_SIZE (32)
+
+#ifdef HAVE_BROKEN_SPOOL
+static int
+camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter)
+{
+ char buffer[4096+PRE_SIZE];
+ int written = 0;
+ char *filterbuffer;
+ int filterlen, filterpre;
+
+ d(printf("writing %d bytes ... ", bytes));
+
+ camel_mime_filter_reset(filter);
+
+ if (lseek(fromfd, start, SEEK_SET) != start)
+ return -1;
+
+ while (bytes>0) {
+ int toread, towrite;
+
+ toread = bytes;
+ if (bytes>4096)
+ toread = 4096;
+ else
+ toread = bytes;
+ do {
+ towrite = read(fromfd, buffer+PRE_SIZE, toread);
+ } while (towrite == -1 && errno == EINTR);
+
+ if (towrite == -1)
+ return -1;
+
+ d(printf("read %d unfiltered bytes\n", towrite));
+
+ /* check for 'end of file' */
+ if (towrite == 0) {
+ d(printf("end of file?\n"));
+ camel_mime_filter_complete(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
+ &filterbuffer, &filterlen, &filterpre);
+ towrite = filterlen;
+ if (towrite == 0)
+ break;
+ } else {
+ camel_mime_filter_filter(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
+ &filterbuffer, &filterlen, &filterpre);
+ towrite = filterlen;
+ }
+
+ d(printf("writing %d filtered bytes\n", towrite));
+
+ do {
+ toread = write(tofd, filterbuffer, towrite);
+ } while (toread == -1 && errno == EINTR);
+
+ if (toread == -1)
+ return -1;
+
+ written += toread;
+ bytes -= toread;
+ }
+
+ d(printf("written %d bytes\n", written));
+
+ return written;
+}
+
+/* write the headers back out again, but not he Content-Length header, because we dont
+ want to maintain it! */
+static int
+solaris_header_write(int fd, struct _camel_header_raw *header)
+{
+ struct iovec iv[4];
+ int outlen = 0, len;
+
+ iv[1].iov_base = ":";
+ iv[1].iov_len = 1;
+ iv[3].iov_base = "\n";
+ iv[3].iov_len = 1;
+
+ while (header) {
+ if (strcasecmp(header->name, "Content-Length")) {
+ iv[0].iov_base = header->name;
+ iv[0].iov_len = strlen(header->name);
+ iv[2].iov_base = header->value;
+ iv[2].iov_len = strlen(header->value);
+
+ do {
+ len = writev(fd, iv, 4);
+ } while (len == -1 && errno == EINTR);
+
+ if (len == -1)
+ return -1;
+ outlen += len;
+ }
+ header = header->next;
+ }
+
+ do {
+ len = write(fd, "\n", 1);
+ } while (len == -1 && errno == EINTR);
+
+ if (len == -1)
+ return -1;
+
+ outlen += 1;
+
+ d(printf("Wrote %d bytes of headers\n", outlen));
+
+ return outlen;
+}
+
+/* Well, since Solaris is a tad broken wrt its 'mbox' folder format,
+ we must convert it to a real mbox format. Thankfully this is
+ mostly pretty easy */
+static int
+camel_movemail_solaris (int oldsfd, int dfd, CamelException *ex)
+{
+ CamelMimeParser *mp;
+ char *buffer;
+ int len;
+ int sfd;
+ CamelMimeFilterFrom *ffrom;
+ int ret = 1;
+ char *from = NULL;
+
+ /* need to dup as the mime parser will close on finish */
+ sfd = dup(oldsfd);
+ if (sfd == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error copying mail temp file: %s"),
+ g_strerror (errno));
+ return -1;
+ }
+
+ mp = camel_mime_parser_new();
+ camel_mime_parser_scan_from(mp, TRUE);
+ camel_mime_parser_init_with_fd(mp, sfd);
+
+ ffrom = camel_mime_filter_from_new();
+
+ while (camel_mime_parser_step(mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_FROM) {
+ g_assert(camel_mime_parser_from_line(mp));
+ from = g_strdup(camel_mime_parser_from_line(mp));
+ if (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_FROM_END) {
+ const char *cl;
+ int length;
+ int start, body;
+ off_t newpos;
+
+ ret = 0;
+
+ start = camel_mime_parser_tell_start_from(mp);
+ body = camel_mime_parser_tell(mp);
+
+ if (write(dfd, from, strlen(from)) != strlen(from))
+ goto fail;
+
+ /* write out headers, but NOT content-length header */
+ if (solaris_header_write(dfd, camel_mime_parser_headers_raw(mp)) == -1)
+ goto fail;
+
+ cl = camel_mime_parser_header(mp, "content-length", NULL);
+ if (cl == NULL) {
+ g_warning("Required Content-Length header is missing from solaris mail box @ %d", (int)camel_mime_parser_tell(mp));
+ camel_mime_parser_drop_step(mp);
+ camel_mime_parser_drop_step(mp);
+ camel_mime_parser_step(mp, &buffer, &len);
+ camel_mime_parser_unstep(mp);
+ length = camel_mime_parser_tell_start_from(mp) - body;
+ newpos = -1;
+ } else {
+ length = atoi(cl);
+ camel_mime_parser_drop_step(mp);
+ camel_mime_parser_drop_step(mp);
+ newpos = length+body;
+ }
+ /* copy body->length converting From lines */
+ if (camel_movemail_copy_filter(sfd, dfd, body, length, (CamelMimeFilter *)ffrom) == -1)
+ goto fail;
+ if (newpos != -1)
+ camel_mime_parser_seek(mp, newpos, SEEK_SET);
+ } else {
+ g_error("Inalid parser state: %d", camel_mime_parser_state(mp));
+ }
+ g_free(from);
+ }
+
+ camel_object_unref((CamelObject *)mp);
+ camel_object_unref((CamelObject *)ffrom);
+
+ return ret;
+
+fail:
+ g_free(from);
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error copying mail temp file: %s"),
+ g_strerror (errno));
+
+
+ camel_object_unref((CamelObject *)mp);
+ camel_object_unref((CamelObject *)ffrom);
+
+ return -1;
+}
+#endif /* HAVE_BROKEN_SPOOL */
+
diff --git a/camel/camel-multipart-signed.c b/camel/camel-multipart-signed.c
new file mode 100644
index 0000000000..1a5092ba27
--- /dev/null
+++ b/camel/camel-multipart-signed.c
@@ -0,0 +1,794 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * camel-multipart.c : Abstract class for a multipart
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+
+#include <stdio.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <errno.h>
+
+#include "camel-mime-part.h"
+#include "camel-mime-message.h"
+#include "camel-mime-parser.h"
+#include "camel-stream-mem.h"
+#include "camel-multipart-signed.h"
+#include "camel-mime-part.h"
+#include "camel-exception.h"
+#include "md5-utils.h"
+
+#include "camel-stream-filter.h"
+#include "camel-seekable-substream.h"
+#include "camel-mime-filter-crlf.h"
+#include "camel-mime-filter-canon.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))
+ #include <stdio.h>;*/
+
+static void signed_add_part (CamelMultipart *multipart, CamelMimePart *part);
+static void signed_add_part_at (CamelMultipart *multipart, CamelMimePart *part, guint index);
+static void signed_remove_part (CamelMultipart *multipart, CamelMimePart *part);
+static CamelMimePart *signed_remove_part_at (CamelMultipart *multipart, guint index);
+static CamelMimePart *signed_get_part (CamelMultipart *multipart, guint index);
+static guint signed_get_number (CamelMultipart *multipart);
+
+static ssize_t write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream);
+static void set_mime_type_field (CamelDataWrapper *data_wrapper, CamelContentType *mime_type);
+static int construct_from_stream (CamelDataWrapper *data_wrapper, CamelStream *stream);
+static int signed_construct_from_parser (CamelMultipart *multipart, struct _CamelMimeParser *mp);
+
+static CamelMultipartClass *parent_class = NULL;
+
+/* Returns the class for a CamelMultipartSigned */
+#define CMP_CLASS(so) CAMEL_MULTIPART_SIGNED_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+/* Returns the class for a CamelDataWrapper */
+#define CDW_CLASS(so) CAMEL_DATA_WRAPPER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static void
+camel_multipart_signed_class_init (CamelMultipartSignedClass *camel_multipart_signed_class)
+{
+ CamelDataWrapperClass *camel_data_wrapper_class = CAMEL_DATA_WRAPPER_CLASS(camel_multipart_signed_class);
+ CamelMultipartClass *mpclass = (CamelMultipartClass *)camel_multipart_signed_class;
+
+ parent_class = (CamelMultipartClass *)camel_multipart_get_type();
+
+ /* virtual method overload */
+ camel_data_wrapper_class->construct_from_stream = construct_from_stream;
+ camel_data_wrapper_class->write_to_stream = write_to_stream;
+ camel_data_wrapper_class->decode_to_stream = write_to_stream;
+ camel_data_wrapper_class->set_mime_type_field = set_mime_type_field;
+
+ mpclass->add_part = signed_add_part;
+ mpclass->add_part_at = signed_add_part_at;
+ mpclass->remove_part = signed_remove_part;
+ mpclass->remove_part_at = signed_remove_part_at;
+ mpclass->get_part = signed_get_part;
+ mpclass->get_number = signed_get_number;
+ mpclass->construct_from_parser = signed_construct_from_parser;
+
+/*
+ mpclass->get_boundary = signed_get_boundary;
+ mpclass->set_boundary = signed_set_boundary;
+*/
+}
+
+static void
+camel_multipart_signed_init (gpointer object, gpointer klass)
+{
+ CamelMultipartSigned *multipart = (CamelMultipartSigned *)object;
+
+ camel_data_wrapper_set_mime_type(CAMEL_DATA_WRAPPER(multipart), "multipart/signed");
+ multipart->start1 = -1;
+}
+
+static void
+camel_multipart_signed_finalize (CamelObject *object)
+{
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)object;
+
+ g_free(mps->protocol);
+ g_free(mps->micalg);
+ if (mps->signature)
+ camel_object_unref((CamelObject *)mps->signature);
+ if (mps->content)
+ camel_object_unref((CamelObject *)mps->content);
+ if (mps->contentraw)
+ camel_object_unref((CamelObject *)mps->contentraw);
+}
+
+CamelType
+camel_multipart_signed_get_type (void)
+{
+ static CamelType camel_multipart_signed_type = CAMEL_INVALID_TYPE;
+
+ if (camel_multipart_signed_type == CAMEL_INVALID_TYPE) {
+ camel_multipart_signed_type = camel_type_register (camel_multipart_get_type (), "CamelMultipartSigned",
+ sizeof (CamelMultipartSigned),
+ sizeof (CamelMultipartSignedClass),
+ (CamelObjectClassInitFunc) camel_multipart_signed_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_multipart_signed_init,
+ (CamelObjectFinalizeFunc) camel_multipart_signed_finalize);
+ }
+
+ return camel_multipart_signed_type;
+}
+
+/**
+ * camel_multipart_signed_new:
+ *
+ * Create a new CamelMultipartSigned object.
+ *
+ * A MultipartSigned should be used to store and create parts of
+ * type "multipart/signed". This is because multipart/signed is
+ * entirely broken-by-design (tm) and uses completely
+ * different semantics to other mutlipart types. It must be treated
+ * as opaque data by any transport. See rfc 3156 for details.
+ *
+ * There are 3 ways to create the part:
+ * Use construct_from_stream. If this is used, then you must
+ * set the mime_type appropriately to match the data uses, so
+ * that the multiple parts my be extracted.
+ *
+ * Use construct_from_parser. The parser MUST be in the CAMEL_MIME_PARSER_STATE_HEADER
+ * state, and the current content_type MUST be "multipart/signed" with
+ * the appropriate boundary and it SHOULD include the appropriate protocol
+ * and hash specifiers.
+ *
+ * Use sign_part. A signature part will automatically be created
+ * and the whole part may be written using write_to_stream to
+ * create a 'transport-safe' version (as safe as can be expected with
+ * such a broken specification).
+ *
+ * Return value: a new CamelMultipartSigned
+ **/
+CamelMultipartSigned *
+camel_multipart_signed_new (void)
+{
+ CamelMultipartSigned *multipart;
+
+ multipart = (CamelMultipartSigned *)camel_object_new(CAMEL_MULTIPART_SIGNED_TYPE);
+
+ return multipart;
+}
+
+static int
+skip_content(CamelMimeParser *cmp)
+{
+ char *buf;
+ size_t len;
+ int state;
+
+ switch (camel_mime_parser_state(cmp)) {
+ case CAMEL_MIME_PARSER_STATE_HEADER:
+ /* body part */
+ while (camel_mime_parser_step(cmp, &buf, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
+ /* NOOP */ ;
+ break;
+ case CAMEL_MIME_PARSER_STATE_MESSAGE:
+ /* message body part */
+ (void)camel_mime_parser_step(cmp, &buf, &len);
+ skip_content(cmp);
+
+ /* clean up followon state if any, see camel-mime-message.c */
+ state = camel_mime_parser_step(cmp, &buf, &len);
+ switch (state) {
+ case CAMEL_MIME_PARSER_STATE_EOF:
+ case CAMEL_MIME_PARSER_STATE_FROM_END: /* these doesn't belong to us */
+ camel_mime_parser_unstep(cmp);
+ case CAMEL_MIME_PARSER_STATE_MESSAGE_END:
+ break;
+ default:
+ g_error ("Bad parser state: Expecing MESSAGE_END or EOF or EOM, got: %d", camel_mime_parser_state (cmp));
+ camel_mime_parser_unstep(cmp);
+ return -1;
+ }
+ break;
+ case CAMEL_MIME_PARSER_STATE_MULTIPART:
+ /* embedded multipart */
+ while ((state = camel_mime_parser_step(cmp, &buf, &len)) != CAMEL_MIME_PARSER_STATE_MULTIPART_END)
+ skip_content(cmp);
+ break;
+ default:
+ g_warning("Invalid state encountered???: %d", camel_mime_parser_state(cmp));
+ }
+
+ return 0;
+}
+
+static int
+parse_content(CamelMultipartSigned *mps)
+{
+ CamelMimeParser *cmp;
+ CamelMultipart *mp = (CamelMultipart *)mps;
+ CamelStreamMem *mem;
+ const char *boundary;
+ char *buf;
+ size_t len;
+ off_t head = -1, tail = -1;
+ int state;
+
+ boundary = camel_multipart_get_boundary(mp);
+ if (boundary == NULL) {
+ g_warning("Trying to get multipart/signed content without setting boundary first");
+ return -1;
+ }
+
+ mem = (CamelStreamMem *)((CamelDataWrapper *)mps)->stream;
+ if (mem == NULL) {
+ g_warning("Trying to parse multipart/signed without constructing first");
+ return -1;
+ }
+
+ /* This is all seriously complex.
+ This is so we can parse all cases properly, without altering the content.
+ All we are doing is finding part offsets. */
+
+ camel_stream_reset((CamelStream *)mem);
+ cmp = camel_mime_parser_new();
+ camel_mime_parser_init_with_stream(cmp, (CamelStream *)mem);
+ camel_mime_parser_push_state(cmp, CAMEL_MIME_PARSER_STATE_MULTIPART, boundary);
+
+ mps->start1 = -1;
+ mps->end1 = -1;
+ mps->start2 = -1;
+ mps->end2 = -1;
+
+ while ((state = camel_mime_parser_step(cmp, &buf, &len)) != CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
+ if (mps->start1 == -1) {
+ head = camel_mime_parser_tell_start_boundary(cmp);
+ mps->start1 = camel_mime_parser_tell_start_headers(cmp);
+ } else if (mps->start2 == -1) {
+ mps->start2 = camel_mime_parser_tell_start_headers(cmp);
+ mps->end1 = camel_mime_parser_tell_start_boundary(cmp);
+ if (mps->end1 > mps->start1 && mem->buffer->data[mps->end1-1] == '\n')
+ mps->end1--;
+ if (mps->end1 > mps->start1 && mem->buffer->data[mps->end1-1] == '\r')
+ mps->end1--;
+ } else {
+ g_warning("multipart/signed has more than 2 parts, remaining parts ignored");
+ break;
+ }
+
+ if (skip_content(cmp) == -1)
+ break;
+ }
+
+ if (state == CAMEL_MIME_PARSER_STATE_MULTIPART_END) {
+ mps->end2 = camel_mime_parser_tell_start_boundary(cmp);
+ tail = camel_mime_parser_tell(cmp);
+
+ camel_multipart_set_preface(mp, camel_mime_parser_preface(cmp));
+ camel_multipart_set_postface(mp, camel_mime_parser_postface(cmp));
+ }
+
+ camel_object_unref(cmp);
+
+ if (mps->end2 == -1 || mps->start2 == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* we snoop the mime type to get boundary and hash info */
+static void
+set_mime_type_field(CamelDataWrapper *data_wrapper, CamelContentType *mime_type)
+{
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)data_wrapper;
+
+ ((CamelDataWrapperClass *)parent_class)->set_mime_type_field(data_wrapper, mime_type);
+ if (mime_type) {
+ const char *micalg, *protocol;
+
+ protocol = camel_content_type_param(mime_type, "protocol");
+ g_free(mps->protocol);
+ mps->protocol = g_strdup(protocol);
+
+ micalg = camel_content_type_param(mime_type, "micalg");
+ g_free(mps->micalg);
+ mps->micalg = g_strdup(micalg);
+ }
+}
+
+static void
+signed_add_part(CamelMultipart *multipart, CamelMimePart *part)
+{
+ g_warning("Cannot add parts to a signed part using add_part");
+}
+
+static void
+signed_add_part_at(CamelMultipart *multipart, CamelMimePart *part, guint index)
+{
+ g_warning("Cannot add parts to a signed part using add_part_at");
+}
+
+static void
+signed_remove_part(CamelMultipart *multipart, CamelMimePart *part)
+{
+ g_warning("Cannot remove parts from a signed part using remove_part");
+}
+
+static CamelMimePart *
+signed_remove_part_at (CamelMultipart *multipart, guint index)
+{
+ g_warning("Cannot remove parts from a signed part using remove_part");
+ return NULL;
+}
+
+static CamelMimePart *
+signed_get_part(CamelMultipart *multipart, guint index)
+{
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)multipart;
+ CamelDataWrapper *dw = (CamelDataWrapper *)multipart;
+ CamelStream *stream;
+
+ switch (index) {
+ case CAMEL_MULTIPART_SIGNED_CONTENT:
+ if (mps->content)
+ return mps->content;
+ if (mps->contentraw) {
+ stream = mps->contentraw;
+ camel_object_ref((CamelObject *)stream);
+ } else if (mps->start1 == -1
+ && parse_content(mps) == -1
+ && (stream = ((CamelDataWrapper *)mps)->stream) == NULL) {
+ g_warning("Trying to get content on an invalid multipart/signed");
+ return NULL;
+ } else if (dw->stream == NULL) {
+ return NULL;
+ } else if (mps->start1 == -1) {
+ stream = dw->stream;
+ camel_object_ref(stream);
+ } else {
+ stream = camel_seekable_substream_new((CamelSeekableStream *)dw->stream, mps->start1, mps->end1);
+ }
+ camel_stream_reset(stream);
+ mps->content = camel_mime_part_new();
+ camel_data_wrapper_construct_from_stream((CamelDataWrapper *)mps->content, stream);
+ camel_object_unref(stream);
+ return mps->content;
+ case CAMEL_MULTIPART_SIGNED_SIGNATURE:
+ if (mps->signature)
+ return mps->signature;
+ if (mps->start1 == -1
+ && parse_content(mps) == -1) {
+ g_warning("Trying to get signature on invalid multipart/signed");
+ return NULL;
+ } else if (dw->stream == NULL) {
+ return NULL;
+ }
+ stream = camel_seekable_substream_new((CamelSeekableStream *)dw->stream, mps->start2, mps->end2);
+ camel_stream_reset(stream);
+ mps->signature = camel_mime_part_new();
+ camel_data_wrapper_construct_from_stream((CamelDataWrapper *)mps->signature, stream);
+ camel_object_unref((CamelObject *)stream);
+ return mps->signature;
+ default:
+ g_warning("trying to get object out of bounds for multipart");
+ }
+
+ return NULL;
+}
+
+static guint
+signed_get_number(CamelMultipart *multipart)
+{
+ CamelDataWrapper *dw = (CamelDataWrapper *)multipart;
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)multipart;
+
+ /* check what we have, so we return something reasonable */
+
+ if ((mps->content || mps->contentraw) && mps->signature)
+ return 2;
+
+ if (mps->start1 == -1 && parse_content(mps) == -1) {
+ if (dw->stream == NULL)
+ return 0;
+ else
+ return 1;
+ } else {
+ return 2;
+ }
+}
+
+static void
+set_stream(CamelMultipartSigned *mps, CamelStream *mem)
+{
+ CamelDataWrapper *dw = (CamelDataWrapper *)mps;
+
+ if (dw->stream)
+ camel_object_unref((CamelObject *)dw->stream);
+ dw->stream = (CamelStream *)mem;
+
+ mps->start1 = -1;
+ if (mps->content) {
+ camel_object_unref((CamelObject *)mps->content);
+ mps->content = NULL;
+ }
+ if (mps->contentraw) {
+ camel_object_unref((CamelObject *)mps->contentraw);
+ mps->contentraw = NULL;
+ }
+ if (mps->signature) {
+ camel_object_unref((CamelObject *)mps->signature);
+ mps->signature = NULL;
+ }
+}
+
+static int
+construct_from_stream(CamelDataWrapper *data_wrapper, CamelStream *stream)
+{
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)data_wrapper;
+ CamelStream *mem = camel_stream_mem_new();
+
+ if (camel_stream_write_to_stream(stream, mem) == -1)
+ return -1;
+
+ set_stream(mps, mem);
+
+ return 0;
+}
+
+static int
+signed_construct_from_parser(CamelMultipart *multipart, struct _CamelMimeParser *mp)
+{
+ int err;
+ CamelContentType *content_type;
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)multipart;
+ char *buf;
+ size_t len;
+ CamelStream *mem;
+
+ /* we *must not* be in multipart state, otherwise the mime parser will
+ parse the headers which is a no no @#$@# stupid multipart/signed spec */
+ g_assert(camel_mime_parser_state(mp) == CAMEL_MIME_PARSER_STATE_HEADER);
+
+ /* All we do is copy it to a memstream */
+ content_type = camel_mime_parser_content_type(mp);
+ camel_multipart_set_boundary(multipart, camel_content_type_param(content_type, "boundary"));
+
+ mem = camel_stream_mem_new();
+ while (camel_mime_parser_step(mp, &buf, &len) != CAMEL_MIME_PARSER_STATE_BODY_END)
+ camel_stream_write(mem, buf, len);
+
+ set_stream(mps, mem);
+
+ err = camel_mime_parser_errno(mp);
+ if (err != 0) {
+ errno = err;
+ return -1;
+ } else
+ return 0;
+}
+
+static ssize_t
+write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream)
+{
+ CamelMultipartSigned *mps = (CamelMultipartSigned *)data_wrapper;
+ CamelMultipart *mp = (CamelMultipart *)mps;
+ const char *boundary;
+ ssize_t total = 0;
+ ssize_t count;
+
+ /* we have 3 basic cases:
+ 1. constructed, we write out the data wrapper stream we got
+ 2. signed content, we create and write out a new stream
+ 3. invalid
+ */
+
+ /* 1 */
+ /* FIXME: locking? */
+ if (data_wrapper->stream) {
+ camel_stream_reset(data_wrapper->stream);
+ return camel_stream_write_to_stream(data_wrapper->stream, stream);
+ }
+
+ /* 3 */
+ if (mps->signature == NULL || mps->contentraw == NULL)
+ return -1;
+
+ /* 2 */
+ boundary = camel_multipart_get_boundary(mp);
+ if (mp->preface) {
+ count = camel_stream_write_string(stream, mp->preface);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ /* first boundary */
+ count = camel_stream_printf(stream, "\n--%s\n", boundary);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* output content part */
+ camel_stream_reset(mps->contentraw);
+ count = camel_stream_write_to_stream(mps->contentraw, stream);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* boundary */
+ count = camel_stream_printf(stream, "\n--%s\n", boundary);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* signature */
+ count = camel_data_wrapper_write_to_stream((CamelDataWrapper *)mps->signature, stream);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* write the terminating boudary delimiter */
+ count = camel_stream_printf(stream, "\n--%s--\n", boundary);
+ if (count == -1)
+ return -1;
+ total += count;
+
+ /* and finally the postface */
+ if (mp->postface) {
+ count = camel_stream_write_string(stream, mp->postface);
+ if (count == -1)
+ return -1;
+ total += count;
+ }
+
+ return total;
+}
+
+/* See rfc3156, section 2 and others */
+/* We do this simply: Anything not base64 must be qp
+ This is so that we can safely translate any occurance of "From "
+ into the quoted-printable escaped version safely. */
+static void
+prepare_sign(CamelMimePart *mime_part)
+{
+ CamelDataWrapper *wrapper;
+ CamelTransferEncoding encoding;
+ int parts, i;
+
+ wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+ if (!wrapper)
+ return;
+
+ if (CAMEL_IS_MULTIPART (wrapper)) {
+ parts = camel_multipart_get_number((CamelMultipart *)wrapper);
+ for (i = 0; i < parts; i++)
+ prepare_sign(camel_multipart_get_part((CamelMultipart *)wrapper, i));
+ } else if (CAMEL_IS_MIME_MESSAGE (wrapper)) {
+ prepare_sign((CamelMimePart *)wrapper);
+ } else {
+ encoding = camel_mime_part_get_encoding(mime_part);
+
+ if (encoding != CAMEL_TRANSFER_ENCODING_BASE64
+ && encoding != CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE) {
+ camel_mime_part_set_encoding(mime_part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
+ }
+ }
+}
+
+/**
+ * camel_multipart_signed_sign:
+ * @mps:
+ * @context: The CipherContext to use for signing.
+ * @content: CamelMimePart content you wish to sign/transport.
+ * @userid: The id of the signing key to use.
+ * @hash: The algorithm to use.
+ * @ex:
+ *
+ * Sign the part @content, and attach it as the first part
+ * (CAMEL_MULTIPART_SIGNED_CONTENT) of the multipart @mps. A
+ * signature object will be created and setup as the second part
+ * (CAMEL_MULTIPART_SIGNED_SIGNATURE) of the object. Once a part has
+ * been successfully signed the mutlipart is ready for transmission.
+ *
+ * This method should be used to create multipart/signed objects
+ * which are properly canoncalised before signing, etc.
+ *
+ * Return value: -1 on error, setting @ex appropriately. On error
+ * neither the content or signature parts will be setup.
+ **/
+int
+camel_multipart_signed_sign(CamelMultipartSigned *mps, CamelCipherContext *context, CamelMimePart *content, const char *userid, CamelCipherHash hash, CamelException *ex)
+{
+ abort();
+#if 0
+ CamelMimeFilter *canon_filter;
+ CamelStream *mem;
+ CamelStreamFilter *filter;
+ CamelContentType *mime_type;
+ CamelMimePart *sigpart;
+
+ /* this needs to be set */
+ g_return_val_if_fail(context->sign_protocol != NULL, -1);
+
+ prepare_sign(content);
+
+ mem = camel_stream_mem_new();
+ filter = camel_stream_filter_new_with_stream(mem);
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+ canon_filter = camel_mime_filter_canon_new(CAMEL_MIME_FILTER_CANON_STRIP|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_FROM);
+ camel_stream_filter_add(filter, (CamelMimeFilter *)canon_filter);
+ camel_object_unref((CamelObject *)canon_filter);
+
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)content, (CamelStream *)filter);
+ camel_stream_flush((CamelStream *)filter);
+ camel_object_unref((CamelObject *)filter);
+ camel_stream_reset(mem);
+
+#if 0
+ printf("-- Signing:\n");
+ fwrite(((CamelStreamMem *)mem)->buffer->data, ((CamelStreamMem *)mem)->buffer->len, 1, stdout);
+ printf("-- end\n");
+#endif
+
+ sigpart = camel_mime_part_new();
+
+ if (camel_cipher_sign(context, userid, hash, mem, sigpart, ex) == -1) {
+ camel_object_unref(mem);
+ camel_object_unref(sigpart);
+ return -1;
+ }
+
+ /* setup our mime type and boundary */
+ mime_type = camel_content_type_new("multipart", "signed");
+ camel_content_type_set_param(mime_type, "micalg", camel_cipher_hash_to_id(context, hash));
+ camel_content_type_set_param(mime_type, "protocol", context->sign_protocol);
+ camel_data_wrapper_set_mime_type_field(CAMEL_DATA_WRAPPER (mps), mime_type);
+ camel_content_type_unref(mime_type);
+ camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
+
+ /* just keep the whole raw content. We dont *really* need to do this because
+ we know how we just proccessed it, but, well, better to be safe than sorry */
+ mps->signature = sigpart;
+ mps->contentraw = mem;
+ camel_stream_reset(mem);
+
+ /* clear the data-wrapper stream - tells write_to_stream to use the right object */
+ if (((CamelDataWrapper *)mps)->stream) {
+ camel_object_unref((CamelObject *) ((CamelDataWrapper *)mps)->stream);
+ ((CamelDataWrapper *)mps)->stream = NULL;
+ }
+#endif
+ return 0;
+}
+
+CamelStream *
+camel_multipart_signed_get_content_stream(CamelMultipartSigned *mps, CamelException *ex)
+{
+ CamelStream *constream;
+
+ /* we need to be able to verify stuff we just signed as well as stuff we loaded from a stream/parser */
+
+ if (mps->contentraw) {
+ constream = mps->contentraw;
+ camel_object_ref((CamelObject *)constream);
+ } else {
+ CamelStream *sub;
+ CamelMimeFilter *canon_filter;
+
+ if (mps->start1 == -1 && parse_content(mps) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("parse error"));
+ return NULL;
+ }
+
+ /* first, prepare our parts */
+ sub = camel_seekable_substream_new((CamelSeekableStream *)((CamelDataWrapper *)mps)->stream, mps->start1, mps->end1);
+ constream = (CamelStream *)camel_stream_filter_new_with_stream(sub);
+ camel_object_unref((CamelObject *)sub);
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+ canon_filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
+ camel_stream_filter_add((CamelStreamFilter *)constream, (CamelMimeFilter *)canon_filter);
+ camel_object_unref((CamelObject *)canon_filter);
+ }
+
+ return constream;
+}
+
+/**
+ * camel_multipart_signed_verify:
+ * @mps:
+ * @context:
+ * @ex:
+ *
+ * Verify a signed object. This may be used to verify newly signed
+ * objects as well as those created from external streams or parsers.
+ *
+ * Return value: A validity value, or NULL on error, setting @ex
+ * appropriately.
+ **/
+CamelCipherValidity *
+camel_multipart_signed_verify(CamelMultipartSigned *mps, CamelCipherContext *context, CamelException *ex)
+{
+ abort();
+
+ return NULL;
+#if 0
+ CamelCipherValidity *valid;
+ CamelMimePart *sigpart;
+ CamelStream *constream;
+
+ /* we need to be able to verify stuff we just signed as well as stuff we loaded from a stream/parser */
+
+ if (mps->contentraw) {
+ constream = mps->contentraw;
+ camel_object_ref((CamelObject *)constream);
+ } else {
+ CamelStream *sub;
+ CamelMimeFilter *canon_filter;
+
+ if (mps->start1 == -1 && parse_content(mps) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("parse error"));
+ return NULL;
+ }
+
+ /* first, prepare our parts */
+ sub = camel_seekable_substream_new((CamelSeekableStream *)((CamelDataWrapper *)mps)->stream, mps->start1, mps->end1);
+ constream = (CamelStream *)camel_stream_filter_new_with_stream(sub);
+ camel_object_unref((CamelObject *)sub);
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+ canon_filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF);
+ camel_stream_filter_add((CamelStreamFilter *)constream, (CamelMimeFilter *)canon_filter);
+ camel_object_unref((CamelObject *)canon_filter);
+ }
+
+ /* we do this as a normal mime part so we can have it handle transfer encoding etc */
+ sigpart = camel_multipart_get_part((CamelMultipart *)mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
+
+ /* do the magic, the caller must supply the right context for this kind of object */
+ valid = camel_cipher_verify(context, camel_cipher_id_to_hash(context, mps->micalg), constream, sigpart, ex);
+
+#if 0
+ {
+ CamelStream *sout = camel_stream_fs_new_with_fd(dup(0));
+
+ camel_stream_printf(sout, "-- Verifying:\n");
+ camel_stream_reset(constream);
+ camel_stream_write_to_stream(constream, sout);
+ camel_stream_printf(sout, "-- end\n");
+ camel_object_unref((CamelObject *)sout);
+ }
+#endif
+
+ camel_object_unref(constream);
+
+ return valid;
+#endif
+}
+
+
diff --git a/camel/camel-operation.c b/camel/camel-operation.c
new file mode 100644
index 0000000000..406d576597
--- /dev/null
+++ b/camel/camel-operation.c
@@ -0,0 +1,730 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Michael Zucchi <NotZed@ximian.com>
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+#ifdef HAVE_NSS
+#include <nspr.h>
+#endif
+
+#include "camel-operation.h"
+#include "e-util/e-msgport.h"
+
+#define d(x)
+
+/* ********************************************************************** */
+
+struct _status_stack {
+ guint32 flags;
+ char *msg;
+ int pc; /* last pc reported */
+ unsigned int stamp; /* last stamp reported */
+};
+
+struct _CamelOperation {
+ struct _CamelOperation *next;
+ struct _CamelOperation *prev;
+
+ pthread_t id; /* id of running thread */
+ guint32 flags; /* cancelled ? */
+ int blocked; /* cancellation blocked depth */
+ int refcount;
+
+ CamelOperationStatusFunc status;
+ void *status_data;
+ unsigned int status_update;
+
+ /* stack of status messages (struct _status_stack *) */
+ GSList *status_stack;
+ struct _status_stack *lastreport;
+
+ EMsgPort *cancel_port;
+ int cancel_fd;
+#ifdef HAVE_NSS
+ PRFileDesc *cancel_prfd;
+#endif
+};
+
+#define CAMEL_OPERATION_CANCELLED (1<<0)
+#define CAMEL_OPERATION_TRANSIENT (1<<1)
+
+/* Delay before a transient operation has any effect on the status */
+#define CAMEL_OPERATION_TRANSIENT_DELAY (5)
+
+static pthread_mutex_t operation_lock = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK() pthread_mutex_lock(&operation_lock)
+#define UNLOCK() pthread_mutex_unlock(&operation_lock)
+
+
+static unsigned int stamp (void);
+static EDList operation_list = E_DLIST_INITIALISER(operation_list);
+static pthread_key_t operation_key;
+
+typedef struct _CamelOperationMsg {
+ EMsg msg;
+} CamelOperationMsg ;
+
+/**
+ * camel_operation_init:
+ * @void:
+ *
+ * Init internal variables. Only call this once.
+ **/
+void
+camel_operation_init(void)
+{
+ pthread_key_create(&operation_key, NULL);
+}
+
+/**
+ * camel_operation_shutdown:
+ *
+ * Cleans up internal variables.
+ **/
+void
+camel_operation_shutdown (void)
+{
+ pthread_key_delete (operation_key);
+}
+
+/**
+ * camel_operation_new:
+ * @status: Callback for receiving status messages. This will always
+ * be called with an internal lock held.
+ * @status_data: User data.
+ *
+ * Create a new camel operation handle. Camel operation handles can
+ * be used in a multithreaded application (or a single operation
+ * handle can be used in a non threaded appliation) to cancel running
+ * operations and to obtain notification messages of the internal
+ * status of messages.
+ *
+ * Return value: A new operation handle.
+ **/
+CamelOperation *
+camel_operation_new (CamelOperationStatusFunc status, void *status_data)
+{
+ CamelOperation *cc;
+
+ cc = g_malloc0(sizeof(*cc));
+
+ cc->flags = 0;
+ cc->blocked = 0;
+ cc->refcount = 1;
+ cc->status = status;
+ cc->status_data = status_data;
+ cc->cancel_port = e_msgport_new();
+ cc->cancel_fd = -1;
+
+ LOCK();
+ e_dlist_addtail(&operation_list, (EDListNode *)cc);
+ UNLOCK();
+
+ return cc;
+}
+
+/**
+ * camel_operation_mute:
+ * @cc:
+ *
+ * mutes a camel operation permanently. from this point on you will never
+ * receive operation updates, even if more are sent.
+ **/
+void
+camel_operation_mute(CamelOperation *cc)
+{
+ LOCK();
+ cc->status = NULL;
+ cc->status_data = NULL;
+ UNLOCK();
+}
+
+/**
+ * camel_operation_registered:
+ *
+ * Returns the registered operation, or %NULL if none registered.
+ **/
+CamelOperation *
+camel_operation_registered (void)
+{
+ CamelOperation *cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc)
+ camel_operation_ref(cc);
+
+ return cc;
+}
+
+/**
+ * camel_operation_ref:
+ * @cc: operation context
+ *
+ * Add a reference to the CamelOperation @cc.
+ **/
+void
+camel_operation_ref (CamelOperation *cc)
+{
+ g_assert(cc->refcount > 0);
+
+ LOCK();
+ cc->refcount++;
+ UNLOCK();
+}
+
+/**
+ * camel_operation_unref:
+ * @cc: operation context
+ *
+ * Unref and potentially free @cc.
+ **/
+void
+camel_operation_unref (CamelOperation *cc)
+{
+ GSList *n;
+
+ g_assert(cc->refcount > 0);
+
+ LOCK();
+ if (cc->refcount == 1) {
+ CamelOperationMsg *msg;
+
+ e_dlist_remove((EDListNode *)cc);
+
+ while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
+ g_free(msg);
+
+ e_msgport_destroy(cc->cancel_port);
+
+ n = cc->status_stack;
+ while (n) {
+ g_warning("Camel operation status stack non empty: %s", (char *)n->data);
+ g_free(n->data);
+ n = n->next;
+ }
+ g_slist_free(cc->status_stack);
+
+ g_free(cc);
+ } else {
+ cc->refcount--;
+ }
+ UNLOCK();
+}
+
+/**
+ * camel_operation_cancel_block:
+ * @cc: operation context
+ *
+ * Block cancellation for this operation. If @cc is NULL, then the
+ * current thread is blocked.
+ **/
+void
+camel_operation_cancel_block (CamelOperation *cc)
+{
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc) {
+ LOCK();
+ cc->blocked++;
+ UNLOCK();
+ }
+}
+
+/**
+ * camel_operation_cancel_unblock:
+ * @cc: operation context
+ *
+ * Unblock cancellation, when the unblock count reaches the block
+ * count, then this operation can be cancelled. If @cc is NULL, then
+ * the current thread is unblocked.
+ **/
+void
+camel_operation_cancel_unblock (CamelOperation *cc)
+{
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc) {
+ LOCK();
+ cc->blocked--;
+ UNLOCK();
+ }
+}
+
+/**
+ * camel_operation_cancel:
+ * @cc: operation context
+ *
+ * Cancel a given operation. If @cc is NULL then all outstanding
+ * operations are cancelled.
+ **/
+void
+camel_operation_cancel (CamelOperation *cc)
+{
+ CamelOperationMsg *msg;
+
+ LOCK();
+
+ if (cc == NULL) {
+ CamelOperation *cn;
+
+ cc = (CamelOperation *)operation_list.head;
+ cn = cc->next;
+ while (cn) {
+ cc->flags |= CAMEL_OPERATION_CANCELLED;
+ msg = g_malloc0(sizeof(*msg));
+ e_msgport_put(cc->cancel_port, (EMsg *)msg);
+ cc = cn;
+ cn = cn->next;
+ }
+ } else if ((cc->flags & CAMEL_OPERATION_CANCELLED) == 0) {
+ d(printf("cancelling thread %d\n", cc->id));
+
+ cc->flags |= CAMEL_OPERATION_CANCELLED;
+ msg = g_malloc0(sizeof(*msg));
+ e_msgport_put(cc->cancel_port, (EMsg *)msg);
+ }
+
+ UNLOCK();
+}
+
+/**
+ * camel_operation_uncancel:
+ * @cc: operation context
+ *
+ * Uncancel a cancelled operation. If @cc is NULL then the current
+ * operation is uncancelled.
+ *
+ * This is useful, if e.g. you need to do some cleaning up where a
+ * cancellation lying around in the same thread will abort any
+ * processing.
+ **/
+void
+camel_operation_uncancel(CamelOperation *cc)
+{
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc) {
+ CamelOperationMsg *msg;
+
+ LOCK();
+ while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)))
+ g_free(msg);
+
+ cc->flags &= ~CAMEL_OPERATION_CANCELLED;
+ UNLOCK();
+ }
+}
+
+/**
+ * camel_operation_register:
+ * @cc: operation context
+ *
+ * Register a thread or the main thread for cancellation through @cc.
+ * If @cc is NULL, then a new cancellation is created for this thread.
+ *
+ * All calls to operation_register() should save their value and call
+ * operation_register again with that, to automatically stack
+ * registrations.
+ *
+ * Return Value: Returns the previously registered operatoin.
+ *
+ **/
+CamelOperation *
+camel_operation_register (CamelOperation *cc)
+{
+ CamelOperation *oldcc = pthread_getspecific(operation_key);
+
+ pthread_setspecific(operation_key, cc);
+
+ return oldcc;
+}
+
+/**
+ * camel_operation_unregister:
+ * @cc: operation context
+ *
+ * Unregister the current thread for all cancellations.
+ **/
+void
+camel_operation_unregister (CamelOperation *cc)
+{
+ pthread_setspecific(operation_key, NULL);
+}
+
+/**
+ * camel_operation_cancel_check:
+ * @cc: operation context
+ *
+ * Check if cancellation has been applied to @cc. If @cc is NULL,
+ * then the CamelOperation registered for the current thread is used.
+ *
+ * Return value: TRUE if the operation has been cancelled.
+ **/
+gboolean
+camel_operation_cancel_check (CamelOperation *cc)
+{
+ CamelOperationMsg *msg;
+ int cancelled;
+
+ d(printf("checking for cancel in thread %d\n", pthread_self()));
+
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ LOCK();
+
+ if (cc == NULL || cc->blocked > 0) {
+ d(printf("ahah! cancellation is blocked\n"));
+ cancelled = FALSE;
+ } else if (cc->flags & CAMEL_OPERATION_CANCELLED) {
+ d(printf("previously cancelled\n"));
+ cancelled = TRUE;
+ } else if ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port))) {
+ d(printf("Got cancellation message\n"));
+ do {
+ g_free(msg);
+ } while ((msg = (CamelOperationMsg *)e_msgport_get(cc->cancel_port)));
+ cc->flags |= CAMEL_OPERATION_CANCELLED;
+ cancelled = TRUE;
+ } else
+ cancelled = FALSE;
+
+ UNLOCK();
+
+ return cancelled;
+}
+
+/**
+ * camel_operation_cancel_fd:
+ * @cc: operation context
+ *
+ * Retrieve a file descriptor that can be waited on (select, or poll)
+ * for read, to asynchronously detect cancellation.
+ *
+ * Return value: The fd, or -1 if cancellation is not available
+ * (blocked, or has not been registered for this thread).
+ **/
+int
+camel_operation_cancel_fd (CamelOperation *cc)
+{
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc == NULL || cc->blocked)
+ return -1;
+
+ LOCK();
+
+ if (cc->cancel_fd == -1)
+ cc->cancel_fd = e_msgport_fd(cc->cancel_port);
+
+ UNLOCK();
+
+ return cc->cancel_fd;
+}
+
+#ifdef HAVE_NSS
+/**
+ * camel_operation_cancel_prfd:
+ * @cc: operation context
+ *
+ * Retrieve a file descriptor that can be waited on (select, or poll)
+ * for read, to asynchronously detect cancellation.
+ *
+ * Return value: The fd, or NULL if cancellation is not available
+ * (blocked, or has not been registered for this thread).
+ **/
+PRFileDesc *
+camel_operation_cancel_prfd (CamelOperation *cc)
+{
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc == NULL || cc->blocked)
+ return NULL;
+
+ LOCK();
+
+ if (cc->cancel_prfd == NULL)
+ cc->cancel_prfd = e_msgport_prfd(cc->cancel_port);
+
+ UNLOCK();
+
+ return cc->cancel_prfd;
+}
+#endif /* HAVE_NSS */
+
+/**
+ * camel_operation_start:
+ * @cc: operation context
+ * @what: action being performed (printf-style format string)
+ * @Varargs: varargs
+ *
+ * Report the start of an operation. All start operations should have
+ * similar end operations.
+ **/
+void
+camel_operation_start (CamelOperation *cc, char *what, ...)
+{
+ va_list ap;
+ char *msg;
+ struct _status_stack *s;
+
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc == NULL)
+ return;
+
+ LOCK();
+
+ if (cc->status == NULL) {
+ UNLOCK();
+ return;
+ }
+
+ va_start(ap, what);
+ msg = g_strdup_vprintf(what, ap);
+ va_end(ap);
+ cc->status_update = 0;
+ s = g_malloc0(sizeof(*s));
+ s->msg = msg;
+ s->flags = 0;
+ cc->lastreport = s;
+ cc->status_stack = g_slist_prepend(cc->status_stack, s);
+
+ UNLOCK();
+
+ cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);
+
+ d(printf("start '%s'\n", msg, pc));
+}
+
+/**
+ * camel_operation_start_transient:
+ * @cc: operation context
+ * @what: printf-style format string describing the action being performed
+ * @Varargs: varargs
+ *
+ * Start a transient event. We only update this to the display if it
+ * takes very long to process, and if we do, we then go back to the
+ * previous state when finished.
+ **/
+void
+camel_operation_start_transient (CamelOperation *cc, char *what, ...)
+{
+ va_list ap;
+ char *msg;
+ struct _status_stack *s;
+
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc == NULL || cc->status == NULL)
+ return;
+
+ LOCK();
+
+ va_start(ap, what);
+ msg = g_strdup_vprintf(what, ap);
+ va_end(ap);
+ cc->status_update = 0;
+ s = g_malloc0(sizeof(*s));
+ s->msg = msg;
+ s->flags = CAMEL_OPERATION_TRANSIENT;
+ s->stamp = stamp();
+ cc->status_stack = g_slist_prepend(cc->status_stack, s);
+ d(printf("start '%s'\n", msg, pc));
+
+ UNLOCK();
+
+ /* we dont report it yet */
+ /*cc->status(cc, msg, CAMEL_OPERATION_START, cc->status_data);*/
+}
+
+static unsigned int stamp(void)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ /* update 4 times/second */
+ return (tv.tv_sec * 4) + tv.tv_usec / (1000000/4);
+}
+
+/**
+ * camel_operation_progress:
+ * @cc: Operation to report to.
+ * @pc: Percent complete, 0 to 100.
+ *
+ * Report progress on the current operation. If @cc is NULL, then the
+ * currently registered operation is used. @pc reports the current
+ * percentage of completion, which should be in the range of 0 to 100.
+ *
+ * If the total percentage is not know, then use
+ * camel_operation_progress_count().
+ **/
+void
+camel_operation_progress (CamelOperation *cc, int pc)
+{
+ unsigned int now;
+ struct _status_stack *s;
+ char *msg = NULL;
+
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc == NULL)
+ return;
+
+ LOCK();
+
+ if (cc->status == NULL || cc->status_stack == NULL) {
+ UNLOCK();
+ return;
+ }
+
+ s = cc->status_stack->data;
+ s->pc = pc;
+
+ /* Transient messages dont start updating till 4 seconds after
+ they started, then they update every second */
+ now = stamp();
+ if (cc->status_update == now) {
+ cc = NULL;
+ } else if (s->flags & CAMEL_OPERATION_TRANSIENT) {
+ if (s->stamp + CAMEL_OPERATION_TRANSIENT_DELAY > now) {
+ cc = NULL;
+ } else {
+ cc->status_update = now;
+ cc->lastreport = s;
+ msg = g_strdup(s->msg);
+ }
+ } else {
+ s->stamp = cc->status_update = now;
+ cc->lastreport = s;
+ msg = g_strdup(s->msg);
+ }
+
+ UNLOCK();
+
+ if (cc) {
+ cc->status(cc, msg, pc, cc->status_data);
+ g_free(msg);
+ }
+}
+
+/**
+ * camel_operation_progress_count:
+ * @cc: operation context
+ * @sofar:
+ *
+ **/
+void
+camel_operation_progress_count (CamelOperation *cc, int sofar)
+{
+ camel_operation_progress(cc, sofar);
+}
+
+/**
+ * camel_operation_end:
+ * @cc: operation context
+ * @what: Format string.
+ * @Varargs: varargs
+ *
+ * Report the end of an operation. If @cc is NULL, then the currently
+ * registered operation is notified.
+ **/
+void
+camel_operation_end (CamelOperation *cc)
+{
+ struct _status_stack *s, *p;
+ unsigned int now;
+ char *msg = NULL;
+ int pc = 0;
+
+ if (cc == NULL)
+ cc = (CamelOperation *)pthread_getspecific(operation_key);
+
+ if (cc == NULL)
+ return;
+
+ LOCK();
+
+ if (cc->status == NULL || cc->status_stack == NULL) {
+ UNLOCK();
+ return;
+ }
+
+ /* so what we do here is this. If the operation that just
+ * ended was transient, see if we have any other transient
+ * messages that haven't been updated yet above us, otherwise,
+ * re-update as a non-transient at the last reported pc */
+ now = stamp();
+ s = cc->status_stack->data;
+ if (s->flags & CAMEL_OPERATION_TRANSIENT) {
+ if (cc->lastreport == s) {
+ GSList *l = cc->status_stack->next;
+ while (l) {
+ p = l->data;
+ if (p->flags & CAMEL_OPERATION_TRANSIENT) {
+ if (p->stamp + CAMEL_OPERATION_TRANSIENT_DELAY < now) {
+ msg = g_strdup(p->msg);
+ pc = p->pc;
+ cc->lastreport = p;
+ break;
+ }
+ } else {
+ msg = g_strdup(p->msg);
+ pc = p->pc;
+ cc->lastreport = p;
+ break;
+ }
+ l = l->next;
+ }
+ }
+ g_free(s->msg);
+ } else {
+ msg = s->msg;
+ pc = CAMEL_OPERATION_END;
+ cc->lastreport = s;
+ }
+ g_free(s);
+ cc->status_stack = g_slist_remove_link(cc->status_stack, cc->status_stack);
+
+ UNLOCK();
+
+ if (msg) {
+ cc->status(cc, msg, pc, cc->status_data);
+ g_free(msg);
+ }
+}
diff --git a/camel/camel-sasl-digest-md5.c b/camel/camel-sasl-digest-md5.c
new file mode 100644
index 0000000000..47b3339520
--- /dev/null
+++ b/camel/camel-sasl-digest-md5.c
@@ -0,0 +1,906 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include <e-util/md5-utils.h>
+
+#include <gal/util/e-iconv.h>
+
+#include "camel-charset-map.h"
+#include "camel-mime-utils.h"
+#include "camel-sasl-digest-md5.h"
+
+#define d(x)
+
+#define PARANOID(x) x
+
+/* Implements rfc2831 */
+
+CamelServiceAuthType camel_sasl_digest_md5_authtype = {
+ N_("DIGEST-MD5"),
+
+ N_("This option will connect to the server using a "
+ "secure DIGEST-MD5 password, if the server supports it."),
+
+ "DIGEST-MD5",
+ TRUE
+};
+
+static CamelSaslClass *parent_class = NULL;
+
+/* Returns the class for a CamelSaslDigestMd5 */
+#define CSCM_CLASS(so) CAMEL_SASL_DIGEST_MD5_CLASS (CAMEL_OBJECT_GET_CLASS (so))
+
+static GByteArray *digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
+
+enum {
+ STATE_AUTH,
+ STATE_FINAL
+};
+
+typedef struct {
+ char *name;
+ guint type;
+} DataType;
+
+enum {
+ DIGEST_REALM,
+ DIGEST_NONCE,
+ DIGEST_QOP,
+ DIGEST_STALE,
+ DIGEST_MAXBUF,
+ DIGEST_CHARSET,
+ DIGEST_ALGORITHM,
+ DIGEST_CIPHER,
+ DIGEST_UNKNOWN
+};
+
+static DataType digest_args[] = {
+ { "realm", DIGEST_REALM },
+ { "nonce", DIGEST_NONCE },
+ { "qop", DIGEST_QOP },
+ { "stale", DIGEST_STALE },
+ { "maxbuf", DIGEST_MAXBUF },
+ { "charset", DIGEST_CHARSET },
+ { "algorithm", DIGEST_ALGORITHM },
+ { "cipher", DIGEST_CIPHER },
+ { NULL, DIGEST_UNKNOWN }
+};
+
+#define QOP_AUTH (1<<0)
+#define QOP_AUTH_INT (1<<1)
+#define QOP_AUTH_CONF (1<<2)
+#define QOP_INVALID (1<<3)
+
+static DataType qop_types[] = {
+ { "auth", QOP_AUTH },
+ { "auth-int", QOP_AUTH_INT },
+ { "auth-conf", QOP_AUTH_CONF },
+ { NULL, QOP_INVALID }
+};
+
+#define CIPHER_DES (1<<0)
+#define CIPHER_3DES (1<<1)
+#define CIPHER_RC4 (1<<2)
+#define CIPHER_RC4_40 (1<<3)
+#define CIPHER_RC4_56 (1<<4)
+#define CIPHER_INVALID (1<<5)
+
+static DataType cipher_types[] = {
+ { "des", CIPHER_DES },
+ { "3des", CIPHER_3DES },
+ { "rc4", CIPHER_RC4 },
+ { "rc4-40", CIPHER_RC4_40 },
+ { "rc4-56", CIPHER_RC4_56 },
+ { NULL, CIPHER_INVALID }
+};
+
+struct _param {
+ char *name;
+ char *value;
+};
+
+struct _DigestChallenge {
+ GPtrArray *realms;
+ char *nonce;
+ guint qop;
+ gboolean stale;
+ gint32 maxbuf;
+ char *charset;
+ char *algorithm;
+ guint cipher;
+ GList *params;
+};
+
+struct _DigestURI {
+ char *type;
+ char *host;
+ char *name;
+};
+
+struct _DigestResponse {
+ char *username;
+ char *realm;
+ char *nonce;
+ char *cnonce;
+ char nc[9];
+ guint qop;
+ struct _DigestURI *uri;
+ char resp[33];
+ guint32 maxbuf;
+ char *charset;
+ guint cipher;
+ char *authzid;
+ char *param;
+};
+
+struct _CamelSaslDigestMd5Private {
+ struct _DigestChallenge *challenge;
+ struct _DigestResponse *response;
+ int state;
+};
+
+static void
+camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *camel_sasl_digest_md5_class)
+{
+ CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_digest_md5_class);
+
+ parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
+
+ /* virtual method overload */
+ camel_sasl_class->challenge = digest_md5_challenge;
+}
+
+static void
+camel_sasl_digest_md5_init (gpointer object, gpointer klass)
+{
+ CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (object);
+
+ sasl_digest->priv = g_new0 (struct _CamelSaslDigestMd5Private, 1);
+}
+
+static void
+camel_sasl_digest_md5_finalize (CamelObject *object)
+{
+ CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
+ struct _DigestChallenge *c = sasl->priv->challenge;
+ struct _DigestResponse *r = sasl->priv->response;
+ GList *p;
+ int i;
+
+ for (i = 0; i < c->realms->len; i++)
+ g_free (c->realms->pdata[i]);
+ g_ptr_array_free (c->realms, TRUE);
+ g_free (c->nonce);
+ g_free (c->charset);
+ g_free (c->algorithm);
+ for (p = c->params; p; p = p->next) {
+ struct _param *param = p->data;
+
+ g_free (param->name);
+ g_free (param->value);
+ g_free (param);
+ }
+ g_list_free (c->params);
+ g_free (c);
+
+ g_free (r->username);
+ g_free (r->realm);
+ g_free (r->nonce);
+ g_free (r->cnonce);
+ if (r->uri) {
+ g_free (r->uri->type);
+ g_free (r->uri->host);
+ g_free (r->uri->name);
+ }
+ g_free (r->charset);
+ g_free (r->authzid);
+ g_free (r->param);
+ g_free (r);
+
+ g_free (sasl->priv);
+}
+
+
+CamelType
+camel_sasl_digest_md5_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_sasl_get_type (),
+ "CamelSaslDigestMd5",
+ sizeof (CamelSaslDigestMd5),
+ sizeof (CamelSaslDigestMd5Class),
+ (CamelObjectClassInitFunc) camel_sasl_digest_md5_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_sasl_digest_md5_init,
+ (CamelObjectFinalizeFunc) camel_sasl_digest_md5_finalize);
+ }
+
+ return type;
+}
+
+static void
+decode_lwsp (const char **in)
+{
+ const char *inptr = *in;
+
+ while (isspace (*inptr))
+ inptr++;
+
+ *in = inptr;
+}
+
+static char *
+decode_quoted_string (const char **in)
+{
+ const char *inptr = *in;
+ char *out = NULL, *outptr;
+ int outlen;
+ int c;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ const char *intmp;
+ int skip = 0;
+
+ /* first, calc length */
+ inptr++;
+ intmp = inptr;
+ while ((c = *intmp++) && c != '"') {
+ if (c == '\\' && *intmp) {
+ intmp++;
+ skip++;
+ }
+ }
+
+ outlen = intmp - inptr - skip;
+ out = outptr = g_malloc (outlen + 1);
+
+ while ((c = *inptr++) && c != '"') {
+ if (c == '\\' && *inptr) {
+ c = *inptr++;
+ }
+ *outptr++ = c;
+ }
+ *outptr = '\0';
+ }
+
+ *in = inptr;
+
+ return out;
+}
+
+static char *
+decode_token (const char **in)
+{
+ const char *inptr = *in;
+ const char *start;
+
+ decode_lwsp (&inptr);
+ start = inptr;
+
+ while (*inptr && *inptr != '=' && *inptr != ',')
+ inptr++;
+
+ if (inptr > start) {
+ *in = inptr;
+ return g_strndup (start, inptr - start);
+ } else {
+ return NULL;
+ }
+}
+
+static char *
+decode_value (const char **in)
+{
+ const char *inptr = *in;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ d(printf ("decoding quoted string token\n"));
+ return decode_quoted_string (in);
+ } else {
+ d(printf ("decoding string token\n"));
+ return decode_token (in);
+ }
+}
+
+static GList *
+parse_param_list (const char *tokens)
+{
+ GList *params = NULL;
+ struct _param *param;
+ const char *ptr;
+
+ for (ptr = tokens; ptr && *ptr; ) {
+ param = g_new0 (struct _param, 1);
+ param->name = decode_token (&ptr);
+ if (*ptr == '=') {
+ ptr++;
+ param->value = decode_value (&ptr);
+ }
+
+ params = g_list_prepend (params, param);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+
+ return params;
+}
+
+static guint
+decode_data_type (DataType *dtype, const char *name)
+{
+ int i;
+
+ for (i = 0; dtype[i].name; i++) {
+ if (!g_ascii_strcasecmp (dtype[i].name, name))
+ break;
+ }
+
+ return dtype[i].type;
+}
+
+#define get_digest_arg(name) decode_data_type (digest_args, name)
+#define decode_qop(name) decode_data_type (qop_types, name)
+#define decode_cipher(name) decode_data_type (cipher_types, name)
+
+static const char *
+type_to_string (DataType *dtype, guint type)
+{
+ int i;
+
+ for (i = 0; dtype[i].name; i++) {
+ if (dtype[i].type == type)
+ break;
+ }
+
+ return dtype[i].name;
+}
+
+#define qop_to_string(type) type_to_string (qop_types, type)
+#define cipher_to_string(type) type_to_string (cipher_types, type)
+
+static void
+digest_abort (gboolean *have_type, gboolean *abort)
+{
+ if (*have_type)
+ *abort = TRUE;
+ *have_type = TRUE;
+}
+
+static struct _DigestChallenge *
+parse_server_challenge (const char *tokens, gboolean *abort)
+{
+ struct _DigestChallenge *challenge = NULL;
+ GList *params, *p;
+ const char *ptr;
+#ifdef PARANOID
+ gboolean got_algorithm = FALSE;
+ gboolean got_stale = FALSE;
+ gboolean got_maxbuf = FALSE;
+ gboolean got_charset = FALSE;
+#endif /* PARANOID */
+
+ params = parse_param_list (tokens);
+ if (!params) {
+ *abort = TRUE;
+ return NULL;
+ }
+
+ *abort = FALSE;
+
+ challenge = g_new0 (struct _DigestChallenge, 1);
+ challenge->realms = g_ptr_array_new ();
+ challenge->maxbuf = 65536;
+
+ for (p = params; p; p = p->next) {
+ struct _param *param = p->data;
+ int type;
+
+ type = get_digest_arg (param->name);
+ switch (type) {
+ case DIGEST_REALM:
+ for (ptr = param->value; ptr && *ptr; ) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ g_ptr_array_add (challenge->realms, token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_NONCE:
+ g_free (challenge->nonce);
+ challenge->nonce = param->value;
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_QOP:
+ for (ptr = param->value; ptr && *ptr; ) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ challenge->qop |= decode_qop (token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+
+ if (challenge->qop & QOP_INVALID)
+ challenge->qop = QOP_INVALID;
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_STALE:
+ PARANOID (digest_abort (&got_stale, abort));
+ if (!g_ascii_strcasecmp (param->value, "true"))
+ challenge->stale = TRUE;
+ else
+ challenge->stale = FALSE;
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_MAXBUF:
+ PARANOID (digest_abort (&got_maxbuf, abort));
+ challenge->maxbuf = atoi (param->value);
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_CHARSET:
+ PARANOID (digest_abort (&got_charset, abort));
+ g_free (challenge->charset);
+ if (param->value && *param->value)
+ challenge->charset = param->value;
+ else
+ challenge->charset = NULL;
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_ALGORITHM:
+ PARANOID (digest_abort (&got_algorithm, abort));
+ g_free (challenge->algorithm);
+ challenge->algorithm = param->value;
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_CIPHER:
+ for (ptr = param->value; ptr && *ptr; ) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ challenge->cipher |= decode_cipher (token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+ if (challenge->cipher & CIPHER_INVALID)
+ challenge->cipher = CIPHER_INVALID;
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ default:
+ challenge->params = g_list_prepend (challenge->params, param);
+ break;
+ }
+ }
+
+ g_list_free (params);
+
+ return challenge;
+}
+
+static void
+digest_hex (guchar *digest, guchar hex[33])
+{
+ guchar *s, *p;
+
+ /* lowercase hexify that bad-boy... */
+ for (s = digest, p = hex; p < hex + 32; s++, p += 2)
+ sprintf (p, "%.2x", *s);
+}
+
+static char *
+digest_uri_to_string (struct _DigestURI *uri)
+{
+ if (uri->name)
+ return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
+ else
+ return g_strdup_printf ("%s/%s", uri->type, uri->host);
+}
+
+static void
+compute_response (struct _DigestResponse *resp, const char *passwd, gboolean client, guchar out[33])
+{
+ guchar hex_a1[33], hex_a2[33];
+ guchar digest[16];
+ MD5Context ctx;
+ char *buf;
+
+ /* compute A1 */
+ md5_init (&ctx);
+ md5_update (&ctx, resp->username, strlen (resp->username));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->realm, strlen (resp->realm));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, passwd, strlen (passwd));
+ md5_final (&ctx, digest);
+
+ md5_init (&ctx);
+ md5_update (&ctx, digest, 16);
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->nonce, strlen (resp->nonce));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
+ if (resp->authzid) {
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->authzid, strlen (resp->authzid));
+ }
+
+ /* hexify A1 */
+ md5_final (&ctx, digest);
+ digest_hex (digest, hex_a1);
+
+ /* compute A2 */
+ md5_init (&ctx);
+ if (client) {
+ /* we are calculating the client response */
+ md5_update (&ctx, "AUTHENTICATE:", strlen ("AUTHENTICATE:"));
+ } else {
+ /* we are calculating the server rspauth */
+ md5_update (&ctx, ":", 1);
+ }
+
+ buf = digest_uri_to_string (resp->uri);
+ md5_update (&ctx, buf, strlen (buf));
+ g_free (buf);
+
+ if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
+ md5_update (&ctx, ":00000000000000000000000000000000", 33);
+
+ /* now hexify A2 */
+ md5_final (&ctx, digest);
+ digest_hex (digest, hex_a2);
+
+ /* compute KD */
+ md5_init (&ctx);
+ md5_update (&ctx, hex_a1, 32);
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->nonce, strlen (resp->nonce));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->nc, 8);
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, qop_to_string (resp->qop), strlen (qop_to_string (resp->qop)));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, hex_a2, 32);
+ md5_final (&ctx, digest);
+
+ digest_hex (digest, out);
+}
+
+static struct _DigestResponse *
+generate_response (struct _DigestChallenge *challenge, const char *host,
+ const char *protocol, const char *user, const char *passwd)
+{
+ struct _DigestResponse *resp;
+ struct _DigestURI *uri;
+ char *bgen, digest[16];
+
+ resp = g_new0 (struct _DigestResponse, 1);
+ resp->username = g_strdup (user);
+ /* FIXME: we should use the preferred realm */
+ if (challenge->realms && challenge->realms->len > 0)
+ resp->realm = g_strdup (challenge->realms->pdata[0]);
+ else
+ resp->realm = g_strdup ("");
+
+ resp->nonce = g_strdup (challenge->nonce);
+
+ /* generate the cnonce */
+ bgen = g_strdup_printf ("%p:%lu:%lu", resp,
+ (unsigned long) getpid (),
+ (unsigned long) time (0));
+ md5_get_digest (bgen, strlen (bgen), digest);
+ g_free (bgen);
+ /* take our recommended 64 bits of entropy */
+ resp->cnonce = camel_base64_encode_simple (digest, 8);
+
+ /* we don't support re-auth so the nonce count is always 1 */
+ strcpy (resp->nc, "00000001");
+
+ /* choose the QOP */
+ /* FIXME: choose - probably choose "auth" ??? */
+ resp->qop = QOP_AUTH;
+
+ /* create the URI */
+ uri = g_new0 (struct _DigestURI, 1);
+ uri->type = g_strdup (protocol);
+ uri->host = g_strdup (host);
+ uri->name = NULL;
+ resp->uri = uri;
+
+ /* charsets... yay */
+ if (challenge->charset) {
+ /* I believe that this is only ever allowed to be
+ * UTF-8. We strdup the charset specified by the
+ * challenge anyway, just in case it's not UTF-8.
+ */
+ resp->charset = g_strdup (challenge->charset);
+ }
+
+ resp->cipher = CIPHER_INVALID;
+ if (resp->qop == QOP_AUTH_CONF) {
+ /* FIXME: choose a cipher? */
+ resp->cipher = CIPHER_INVALID;
+ }
+
+ /* we don't really care about this... */
+ resp->authzid = NULL;
+
+ compute_response (resp, passwd, TRUE, resp->resp);
+
+ return resp;
+}
+
+static GByteArray *
+digest_response (struct _DigestResponse *resp)
+{
+ GByteArray *buffer;
+ const char *str;
+ char *buf;
+
+ buffer = g_byte_array_new ();
+ g_byte_array_append (buffer, "username=\"", 10);
+ if (resp->charset) {
+ /* Encode the username using the requested charset */
+ char *username, *outbuf;
+ const char *charset;
+ size_t len, outlen;
+ const char *inbuf;
+ iconv_t cd;
+
+ charset = e_iconv_locale_charset ();
+ if (!charset)
+ charset = "iso-8859-1";
+
+ cd = e_iconv_open (resp->charset, charset);
+
+ len = strlen (resp->username);
+ outlen = 2 * len; /* plenty of space */
+
+ outbuf = username = g_malloc0 (outlen + 1);
+ inbuf = resp->username;
+ if (cd == (iconv_t) -1 || e_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (size_t) -1) {
+ /* We can't convert to UTF-8 - pretend we never got a charset param? */
+ g_free (resp->charset);
+ resp->charset = NULL;
+
+ /* Set the username to the non-UTF-8 version */
+ g_free (username);
+ username = g_strdup (resp->username);
+ }
+
+ if (cd != (iconv_t) -1)
+ e_iconv_close (cd);
+
+ g_byte_array_append (buffer, username, strlen (username));
+ g_free (username);
+ } else {
+ g_byte_array_append (buffer, resp->username, strlen (resp->username));
+ }
+
+ g_byte_array_append (buffer, "\",realm=\"", 9);
+ g_byte_array_append (buffer, resp->realm, strlen (resp->realm));
+
+ g_byte_array_append (buffer, "\",nonce=\"", 9);
+ g_byte_array_append (buffer, resp->nonce, strlen (resp->nonce));
+
+ g_byte_array_append (buffer, "\",cnonce=\"", 10);
+ g_byte_array_append (buffer, resp->cnonce, strlen (resp->cnonce));
+
+ g_byte_array_append (buffer, "\",nc=", 5);
+ g_byte_array_append (buffer, resp->nc, 8);
+
+ g_byte_array_append (buffer, ",qop=", 5);
+ str = qop_to_string (resp->qop);
+ g_byte_array_append (buffer, str, strlen (str));
+
+ g_byte_array_append (buffer, ",digest-uri=\"", 13);
+ buf = digest_uri_to_string (resp->uri);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+
+ g_byte_array_append (buffer, "\",response=", 11);
+ g_byte_array_append (buffer, resp->resp, 32);
+
+ if (resp->maxbuf > 0) {
+ g_byte_array_append (buffer, ",maxbuf=", 8);
+ buf = g_strdup_printf ("%d", resp->maxbuf);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+ }
+
+ if (resp->charset) {
+ g_byte_array_append (buffer, ",charset=", 9);
+ g_byte_array_append (buffer, resp->charset, strlen (resp->charset));
+ }
+
+ if (resp->cipher != CIPHER_INVALID) {
+ str = cipher_to_string (resp->cipher);
+ if (str) {
+ g_byte_array_append (buffer, ",cipher=\"", 9);
+ g_byte_array_append (buffer, str, strlen (str));
+ g_byte_array_append (buffer, "\"", 1);
+ }
+ }
+
+ if (resp->authzid) {
+ g_byte_array_append (buffer, ",authzid=\"", 10);
+ g_byte_array_append (buffer, resp->authzid, strlen (resp->authzid));
+ g_byte_array_append (buffer, "\"", 1);
+ }
+
+ return buffer;
+}
+
+static GByteArray *
+digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+{
+ CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
+ struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
+ struct _param *rspauth;
+ GByteArray *ret = NULL;
+ gboolean abort = FALSE;
+ const char *ptr;
+ guchar out[33];
+ char *tokens;
+ struct addrinfo *ai, hints;
+
+ /* Need to wait for the server */
+ if (!token)
+ return NULL;
+
+ g_return_val_if_fail (sasl->service->url->passwd != NULL, NULL);
+
+ switch (priv->state) {
+ case STATE_AUTH:
+ if (token->len > 2048) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server challenge too long (>2048 octets)\n"));
+ return NULL;
+ }
+
+ tokens = g_strndup (token->data, token->len);
+ priv->challenge = parse_server_challenge (tokens, &abort);
+ g_free (tokens);
+ if (!priv->challenge || abort) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server challenge invalid\n"));
+ return NULL;
+ }
+
+ if (priv->challenge->qop == QOP_INVALID) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server challenge contained invalid "
+ "\"Quality of Protection\" token\n"));
+ return NULL;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, NULL);
+ if (ai && ai->ai_canonname)
+ ptr = ai->ai_canonname;
+ else
+ ptr = "localhost.localdomain";
+
+ priv->response = generate_response (priv->challenge, ptr, sasl->service_name,
+ sasl->service->url->user,
+ sasl->service->url->passwd);
+ if (ai)
+ camel_freeaddrinfo(ai);
+ ret = digest_response (priv->response);
+
+ break;
+ case STATE_FINAL:
+ if (token->len)
+ tokens = g_strndup (token->data, token->len);
+ else
+ tokens = NULL;
+
+ if (!tokens || !*tokens) {
+ g_free (tokens);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server response did not contain authorization data\n"));
+ return NULL;
+ }
+
+ rspauth = g_new0 (struct _param, 1);
+
+ ptr = tokens;
+ rspauth->name = decode_token (&ptr);
+ if (*ptr == '=') {
+ ptr++;
+ rspauth->value = decode_value (&ptr);
+ }
+ g_free (tokens);
+
+ if (!rspauth->value) {
+ g_free (rspauth->name);
+ g_free (rspauth);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server response contained incomplete authorization data\n"));
+ return NULL;
+ }
+
+ compute_response (priv->response, sasl->service->url->passwd, FALSE, out);
+ if (memcmp (out, rspauth->value, 32) != 0) {
+ g_free (rspauth->name);
+ g_free (rspauth->value);
+ g_free (rspauth);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server response does not match\n"));
+ sasl->authenticated = TRUE;
+
+ return NULL;
+ }
+
+ g_free (rspauth->name);
+ g_free (rspauth->value);
+ g_free (rspauth);
+
+ ret = g_byte_array_new ();
+
+ sasl->authenticated = TRUE;
+ default:
+ break;
+ }
+
+ priv->state++;
+
+ return ret;
+}
diff --git a/camel/camel-sasl-gssapi.c b/camel/camel-sasl-gssapi.c
new file mode 100644
index 0000000000..1efbefee16
--- /dev/null
+++ b/camel/camel-sasl-gssapi.c
@@ -0,0 +1,346 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_KRB5
+
+#include <string.h>
+#ifdef HAVE_ET_COM_ERR_H
+#include <et/com_err.h>
+#else
+#include <com_err.h>
+#endif
+#ifdef HAVE_MIT_KRB5
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#else /* HAVE_HEIMDAL_KRB5 */
+#include <gssapi.h>
+#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
+#endif
+#include <errno.h>
+
+#ifndef GSS_C_OID_KRBV5_DES
+#define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
+#endif
+
+#include "camel-sasl-gssapi.h"
+
+CamelServiceAuthType camel_sasl_gssapi_authtype = {
+ N_("GSSAPI"),
+
+ N_("This option will connect to the server using "
+ "Kerberos 5 authentication."),
+
+ "GSSAPI",
+ FALSE
+};
+
+enum {
+ GSSAPI_STATE_INIT,
+ GSSAPI_STATE_CONTINUE_NEEDED,
+ GSSAPI_STATE_COMPLETE,
+ GSSAPI_STATE_AUTHENTICATED
+};
+
+#define GSSAPI_SECURITY_LAYER_NONE (1 << 0)
+#define GSSAPI_SECURITY_LAYER_INTEGRITY (1 << 1)
+#define GSSAPI_SECURITY_LAYER_PRIVACY (1 << 2)
+
+#define DESIRED_SECURITY_LAYER GSSAPI_SECURITY_LAYER_NONE
+
+struct _CamelSaslGssapiPrivate {
+ int state;
+ gss_ctx_id_t ctx;
+ gss_name_t target;
+};
+
+
+static GByteArray *gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
+
+
+static CamelSaslClass *parent_class = NULL;
+
+
+static void
+camel_sasl_gssapi_class_init (CamelSaslGssapiClass *klass)
+{
+ CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (klass);
+
+ parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
+
+ /* virtual method overload */
+ camel_sasl_class->challenge = gssapi_challenge;
+}
+
+static void
+camel_sasl_gssapi_init (gpointer object, gpointer klass)
+{
+ CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
+
+ gssapi->priv = g_new (struct _CamelSaslGssapiPrivate, 1);
+ gssapi->priv->state = GSSAPI_STATE_INIT;
+ gssapi->priv->ctx = GSS_C_NO_CONTEXT;
+ gssapi->priv->target = GSS_C_NO_NAME;
+}
+
+static void
+camel_sasl_gssapi_finalize (CamelObject *object)
+{
+ CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
+ guint32 status;
+
+ if (gssapi->priv->ctx != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context (&status, &gssapi->priv->ctx, GSS_C_NO_BUFFER);
+
+ if (gssapi->priv->target != GSS_C_NO_NAME)
+ gss_release_name (&status, &gssapi->priv->target);
+
+ g_free (gssapi->priv);
+}
+
+
+CamelType
+camel_sasl_gssapi_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (
+ camel_sasl_get_type (),
+ "CamelSaslGssapi",
+ sizeof (CamelSaslGssapi),
+ sizeof (CamelSaslGssapiClass),
+ (CamelObjectClassInitFunc) camel_sasl_gssapi_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_sasl_gssapi_init,
+ (CamelObjectFinalizeFunc) camel_sasl_gssapi_finalize);
+ }
+
+ return type;
+}
+
+static void
+gssapi_set_exception (OM_uint32 major, OM_uint32 minor, CamelException *ex)
+{
+ const char *str;
+
+ switch (major) {
+ case GSS_S_BAD_MECH:
+ str = _("The specified mechanism is not supported by the "
+ "provided credential, or is unrecognized by the "
+ "implementation.");
+ break;
+ case GSS_S_BAD_NAME:
+ str = _("The provided target_name parameter was ill-formed.");
+ break;
+ case GSS_S_BAD_NAMETYPE:
+ str = _("The provided target_name parameter contained an "
+ "invalid or unsupported type of name.");
+ break;
+ case GSS_S_BAD_BINDINGS:
+ str = _("The input_token contains different channel "
+ "bindings to those specified via the "
+ "input_chan_bindings parameter.");
+ break;
+ case GSS_S_BAD_SIG:
+ str = _("The input_token contains an invalid signature, or a "
+ "signature that could not be verified.");
+ break;
+ case GSS_S_NO_CRED:
+ str = _("The supplied credentials were not valid for context "
+ "initiation, or the credential handle did not "
+ "reference any credentials.");
+ break;
+ case GSS_S_NO_CONTEXT:
+ str = _("The supplied context handle did not refer to a valid context.");
+ break;
+ case GSS_S_DEFECTIVE_TOKEN:
+ str = _("The consistency checks performed on the input_token failed.");
+ break;
+ case GSS_S_DEFECTIVE_CREDENTIAL:
+ str = _("The consistency checks performed on the credential failed.");
+ break;
+ case GSS_S_CREDENTIALS_EXPIRED:
+ str = _("The referenced credentials have expired.");
+ break;
+ case GSS_S_FAILURE:
+ str = error_message (minor);
+ break;
+ default:
+ str = _("Bad authentication response from server.");
+ }
+
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, str);
+}
+
+static GByteArray *
+gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+{
+ struct _CamelSaslGssapiPrivate *priv = CAMEL_SASL_GSSAPI (sasl)->priv;
+ OM_uint32 major, minor, flags, time;
+ gss_buffer_desc inbuf, outbuf;
+ GByteArray *challenge = NULL;
+ gss_buffer_t input_token;
+ struct hostent *h;
+ int conf_state;
+ gss_qop_t qop;
+ gss_OID mech;
+ char *str;
+ struct addrinfo *ai, hints;
+
+ switch (priv->state) {
+ case GSSAPI_STATE_INIT:
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex);
+ if (ai == NULL)
+ return NULL;
+
+ str = g_strdup_printf("%s@%s", sasl->service_name, ai->ai_canonname);
+ camel_freeaddrinfo(ai);
+
+ inbuf.value = str;
+ inbuf.length = strlen (str);
+ major = gss_import_name (&minor, &inbuf, gss_nt_service_name, &priv->target);
+ g_free (str);
+
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (major, minor, ex);
+ return NULL;
+ }
+
+ input_token = GSS_C_NO_BUFFER;
+
+ goto challenge;
+ break;
+ case GSSAPI_STATE_CONTINUE_NEEDED:
+ if (token == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ return NULL;
+ }
+
+ inbuf.value = token->data;
+ inbuf.length = token->len;
+ input_token = &inbuf;
+
+ challenge:
+ major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
+ GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
+ GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
+ 0, GSS_C_NO_CHANNEL_BINDINGS,
+ input_token, &mech, &outbuf, &flags, &time);
+
+ switch (major) {
+ case GSS_S_COMPLETE:
+ priv->state = GSSAPI_STATE_COMPLETE;
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
+ break;
+ default:
+ gssapi_set_exception (major, minor, ex);
+ return NULL;
+ }
+
+ challenge = g_byte_array_new ();
+ g_byte_array_append (challenge, outbuf.value, outbuf.length);
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ break;
+ case GSSAPI_STATE_COMPLETE:
+ if (token == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ return NULL;
+ }
+
+ inbuf.value = token->data;
+ inbuf.length = token->len;
+
+ major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (major, minor, ex);
+ return NULL;
+ }
+
+ if (outbuf.length < 4) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ return NULL;
+ }
+
+ /* check that our desired security layer is supported */
+ if ((((unsigned char *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Unsupported security layer."));
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ return NULL;
+ }
+
+ inbuf.length = 4 + strlen (sasl->service->url->user);
+ inbuf.value = str = g_malloc (inbuf.length);
+ memcpy (inbuf.value, outbuf.value, 4);
+ str[0] = DESIRED_SECURITY_LAYER;
+ memcpy (str + 4, sasl->service->url->user, inbuf.length - 4);
+
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+
+ major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (major, minor, ex);
+ g_free (str);
+ return NULL;
+ }
+
+ g_free (str);
+ challenge = g_byte_array_new ();
+ g_byte_array_append (challenge, outbuf.value, outbuf.length);
+
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+
+ priv->state = GSSAPI_STATE_AUTHENTICATED;
+
+ sasl->authenticated = TRUE;
+ break;
+ default:
+ return NULL;
+ }
+
+ return challenge;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/camel/camel-sasl-kerberos4.c b/camel/camel-sasl-kerberos4.c
new file mode 100644
index 0000000000..fd366e61db
--- /dev/null
+++ b/camel/camel-sasl-kerberos4.c
@@ -0,0 +1,230 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_KRB4
+
+#include <krb.h>
+/* MIT krb4 des.h #defines _. Sigh. We don't need it. #undef it here
+ * so we get the gettexty _ definition later.
+ */
+#undef _
+
+#include <string.h>
+#include "camel-string-utils.h"
+#include "camel-sasl-kerberos4.h"
+#include "camel-service.h"
+
+CamelServiceAuthType camel_sasl_kerberos4_authtype = {
+ N_("Kerberos 4"),
+
+ N_("This option will connect to the server using "
+ "Kerberos 4 authentication."),
+
+ "KERBEROS_V4",
+ FALSE
+};
+
+#define KERBEROS_V4_PROTECTION_NONE 1
+#define KERBEROS_V4_PROTECTION_INTEGRITY 2
+#define KERBEROS_V4_PROTECTION_PRIVACY 4
+
+static CamelSaslClass *parent_class = NULL;
+
+/* Returns the class for a CamelSaslKerberos4 */
+#define CSK4_CLASS(so) CAMEL_SASL_KERBEROS4_CLASS (CAMEL_OBJECT_GET_CLASS (so))
+
+static GByteArray *krb4_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
+
+struct _CamelSaslKerberos4Private {
+ int state;
+
+ guint32 nonce_n;
+ guint32 nonce_h;
+
+ des_cblock session;
+ des_key_schedule schedule;
+};
+
+static void
+camel_sasl_kerberos4_class_init (CamelSaslKerberos4Class *camel_sasl_kerberos4_class)
+{
+ CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_kerberos4_class);
+
+ parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
+
+ /* virtual method overload */
+ camel_sasl_class->challenge = krb4_challenge;
+}
+
+static void
+camel_sasl_kerberos4_init (gpointer object, gpointer klass)
+{
+ CamelSaslKerberos4 *sasl_krb4 = CAMEL_SASL_KERBEROS4 (object);
+
+ sasl_krb4->priv = g_new0 (struct _CamelSaslKerberos4Private, 1);
+}
+
+static void
+camel_sasl_kerberos4_finalize (CamelObject *object)
+{
+ CamelSaslKerberos4 *sasl = CAMEL_SASL_KERBEROS4 (object);
+
+ if (sasl->priv) {
+ memset (sasl->priv, 0, sizeof (sasl->priv));
+ g_free (sasl->priv);
+ }
+}
+
+
+CamelType
+camel_sasl_kerberos4_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_sasl_get_type (),
+ "CamelSaslKerberos4",
+ sizeof (CamelSaslKerberos4),
+ sizeof (CamelSaslKerberos4Class),
+ (CamelObjectClassInitFunc) camel_sasl_kerberos4_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_sasl_kerberos4_init,
+ (CamelObjectFinalizeFunc) camel_sasl_kerberos4_finalize);
+ }
+
+ return type;
+}
+
+static GByteArray *
+krb4_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+{
+ struct _CamelSaslKerberos4Private *priv = CAMEL_SASL_KERBEROS4 (sasl)->priv;
+ GByteArray *ret = NULL;
+ char *inst, *realm, *username;
+ struct hostent *h;
+ int status, len;
+ KTEXT_ST authenticator;
+ CREDENTIALS credentials;
+ guint32 plus1;
+ struct addrinfo *ai, hints;
+
+ /* Need to wait for the server */
+ if (!token)
+ return NULL;
+
+ switch (priv->state) {
+ case 0:
+ if (token->len != 4)
+ goto lose;
+
+ memcpy (&priv->nonce_n, token->data, 4);
+ priv->nonce_h = ntohl (priv->nonce_n);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex);
+ if (ai == NULL)
+ goto lose;
+
+ /* Our response is an authenticator including that number. */
+ inst = g_strndup (ai->ai_canonname, strcspn (ai->ai_canonname, "."));
+ camel_strdown (inst);
+ realm = g_strdup (krb_realmofhost (ai->ai_canonname));
+ camel_freeaddrinfo(ai);
+ status = krb_mk_req (&authenticator, sasl->service_name, inst, realm, priv->nonce_h);
+ if (status == KSUCCESS) {
+ status = krb_get_cred (sasl->service_name, inst, realm, &credentials);
+ memcpy (priv->session, credentials.session, sizeof (priv->session));
+ memset (&credentials, 0, sizeof (credentials));
+ }
+ g_free (inst);
+ g_free (realm);
+
+ if (status != KSUCCESS) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Could not get Kerberos ticket:\n%s"),
+ krb_err_txt[status]);
+ goto lose;
+ }
+ des_key_sched (&priv->session, priv->schedule);
+
+ ret = g_byte_array_new ();
+ g_byte_array_append (ret, (const guint8 *)authenticator.dat, authenticator.length);
+ break;
+
+ case 1:
+ if (token->len != 8)
+ goto lose;
+
+ /* This one is encrypted. */
+ des_ecb_encrypt ((des_cblock *)token->data, (des_cblock *)token->data, priv->schedule, 0);
+
+ /* Check that the returned value is the original nonce plus one. */
+ memcpy (&plus1, token->data, 4);
+ if (ntohl (plus1) != priv->nonce_h + 1)
+ goto lose;
+
+ /* "the fifth octet contain[s] a bit-mask specifying the
+ * protection mechanisms supported by the server"
+ */
+ if (!(token->data[4] & KERBEROS_V4_PROTECTION_NONE)) {
+ g_warning ("Server does not support `no protection' :-(");
+ goto lose;
+ }
+
+ username = sasl->service->url->user;
+ len = strlen (username) + 9;
+ len += 8 - len % 8;
+ ret = g_byte_array_new ();
+ g_byte_array_set_size (ret, len);
+ memset (ret->data, 0, len);
+ memcpy (ret->data, &priv->nonce_n, 4);
+ ret->data[4] = KERBEROS_V4_PROTECTION_NONE;
+ ret->data[5] = ret->data[6] = ret->data[7] = 0;
+ strcpy (ret->data + 8, username);
+
+ des_pcbc_encrypt ((void *)ret->data, (void *)ret->data, len,
+ priv->schedule, &priv->session, 1);
+ memset (&priv->session, 0, sizeof (priv->session));
+
+ sasl->authenticated = TRUE;
+ break;
+ }
+
+ priv->state++;
+ return ret;
+
+ lose:
+ memset (&priv->session, 0, sizeof (priv->session));
+
+ if (!camel_exception_is_set (ex)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ }
+ return NULL;
+}
+
+#endif /* HAVE_KRB4 */
diff --git a/camel/camel-sasl-ntlm.c b/camel/camel-sasl-ntlm.c
new file mode 100644
index 0000000000..81ad5ce2af
--- /dev/null
+++ b/camel/camel-sasl-ntlm.c
@@ -0,0 +1,706 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright 2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "camel-sasl-ntlm.h"
+
+#include <ctype.h>
+#include <string.h>
+
+CamelServiceAuthType camel_sasl_ntlm_authtype = {
+ N_("NTLM / SPA"),
+
+ N_("This option will connect to a Windows-based server using "
+ "NTLM / Secure Password Authentication."),
+
+ "NTLM",
+ TRUE
+};
+
+static CamelSaslClass *parent_class = NULL;
+
+static GByteArray *ntlm_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
+
+static void
+camel_sasl_ntlm_class_init (CamelSaslNTLMClass *camel_sasl_ntlm_class)
+{
+ CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_ntlm_class);
+
+ parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
+
+ /* virtual method overload */
+ camel_sasl_class->challenge = ntlm_challenge;
+}
+
+CamelType
+camel_sasl_ntlm_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (
+ camel_sasl_get_type (), "CamelSaslNTLM",
+ sizeof (CamelSaslNTLM),
+ sizeof (CamelSaslNTLMClass),
+ (CamelObjectClassInitFunc) camel_sasl_ntlm_class_init,
+ NULL, NULL, NULL);
+ }
+
+ return type;
+}
+
+#define NTLM_REQUEST "NTLMSSP\x00\x01\x00\x00\x00\x06\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00\x00"
+
+#define NTLM_CHALLENGE_NONCE_OFFSET 24
+#define NTLM_CHALLENGE_DOMAIN_OFFSET 48
+#define NTLM_CHALLENGE_DOMAIN_LEN_OFFSET 44
+
+#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00"
+#define NTLM_RESPONSE_FLAGS "\x82\x01"
+#define NTLM_RESPONSE_BASE_SIZE 64
+#define NTLM_RESPONSE_LM_RESP_OFFSET 12
+#define NTLM_RESPONSE_NT_RESP_OFFSET 20
+#define NTLM_RESPONSE_DOMAIN_OFFSET 28
+#define NTLM_RESPONSE_USER_OFFSET 36
+#define NTLM_RESPONSE_HOST_OFFSET 44
+#define NTLM_RESPONSE_FLAGS_OFFSET 60
+
+static void ntlm_calc_response (const guchar key[21],
+ const guchar plaintext[8],
+ guchar results[24]);
+static void ntlm_lanmanager_hash (const char *password, char hash[21]);
+static void ntlm_nt_hash (const char *password, char hash[21]);
+static void ntlm_set_string (GByteArray *ba, int offset,
+ const char *data, int len);
+
+static GByteArray *
+ntlm_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+{
+ GByteArray *ret;
+ guchar nonce[8], hash[21], lm_resp[24], nt_resp[24];
+
+ ret = g_byte_array_new ();
+
+ if (!token || !token->len) {
+ g_byte_array_append (ret, NTLM_REQUEST,
+ sizeof (NTLM_REQUEST) - 1);
+ return ret;
+ }
+
+ memcpy (nonce, token->data + NTLM_CHALLENGE_NONCE_OFFSET, 8);
+ ntlm_lanmanager_hash (sasl->service->url->passwd, hash);
+ ntlm_calc_response (hash, nonce, lm_resp);
+ ntlm_nt_hash (sasl->service->url->passwd, hash);
+ ntlm_calc_response (hash, nonce, nt_resp);
+
+ ret = g_byte_array_new ();
+ g_byte_array_set_size (ret, NTLM_RESPONSE_BASE_SIZE);
+ memset (ret->data, 0, NTLM_RESPONSE_BASE_SIZE);
+ memcpy (ret->data, NTLM_RESPONSE_HEADER,
+ sizeof (NTLM_RESPONSE_HEADER) - 1);
+ memcpy (ret->data + NTLM_RESPONSE_FLAGS_OFFSET,
+ NTLM_RESPONSE_FLAGS, sizeof (NTLM_RESPONSE_FLAGS) - 1);
+
+ ntlm_set_string (ret, NTLM_RESPONSE_DOMAIN_OFFSET,
+ token->data + NTLM_CHALLENGE_DOMAIN_OFFSET,
+ atoi (token->data + NTLM_CHALLENGE_DOMAIN_LEN_OFFSET));
+ ntlm_set_string (ret, NTLM_RESPONSE_USER_OFFSET,
+ sasl->service->url->user,
+ strlen (sasl->service->url->user));
+ ntlm_set_string (ret, NTLM_RESPONSE_HOST_OFFSET,
+ "UNKNOWN", sizeof ("UNKNOWN") - 1);
+ ntlm_set_string (ret, NTLM_RESPONSE_LM_RESP_OFFSET,
+ lm_resp, sizeof (lm_resp));
+ ntlm_set_string (ret, NTLM_RESPONSE_NT_RESP_OFFSET,
+ nt_resp, sizeof (nt_resp));
+
+ sasl->authenticated = TRUE;
+ return ret;
+}
+
+/* MD4 */
+static void md4sum (const unsigned char *in,
+ int nbytes,
+ unsigned char digest[16]);
+
+/* DES */
+typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */
+
+static void deskey (DES_KS, unsigned char *, int);
+
+static void des (DES_KS, unsigned char *);
+
+static void setup_schedule (const guchar *key_56, DES_KS ks);
+
+
+
+#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \
+ "\x4B\x47\x53\x21\x40\x23\x24\x25" \
+ "\x00\x00\x00\x00\x00"
+
+static void
+ntlm_lanmanager_hash (const char *password, char hash[21])
+{
+ guchar lm_password [15];
+ DES_KS ks;
+ int i;
+
+ for (i = 0; i < 14 && password [i]; i++)
+ lm_password [i] = toupper ((unsigned char) password [i]);
+
+ for (; i < 15; i++)
+ lm_password [i] = '\0';
+
+ memcpy (hash, LM_PASSWORD_MAGIC, 21);
+
+ setup_schedule (lm_password, ks);
+ des (ks, hash);
+
+ setup_schedule (lm_password + 7, ks);
+ des (ks, hash + 8);
+}
+
+static void
+ntlm_nt_hash (const char *password, char hash[21])
+{
+ unsigned char *buf, *p;
+
+ p = buf = g_malloc (strlen (password) * 2);
+
+ while (*password) {
+ *p++ = *password++;
+ *p++ = '\0';
+ }
+
+ md4sum (buf, p - buf, hash);
+ memset (hash + 16, 0, 5);
+
+ g_free (buf);
+}
+
+static void
+ntlm_set_string (GByteArray *ba, int offset, const char *data, int len)
+{
+ ba->data[offset ] = ba->data[offset + 2] = len & 0xFF;
+ ba->data[offset + 1] = ba->data[offset + 3] = (len >> 8) & 0xFF;
+ ba->data[offset + 4] = ba->len & 0xFF;
+ ba->data[offset + 5] = (ba->len >> 8) & 0xFF;
+ g_byte_array_append (ba, data, len);
+}
+
+
+#define KEYBITS(k,s) \
+ (((k[(s)/8] << ((s)%8)) & 0xFF) | (k[(s)/8+1] >> (8-(s)%8)))
+
+/* DES utils */
+/* Set up a key schedule based on a 56bit key */
+static void
+setup_schedule (const guchar *key_56, DES_KS ks)
+{
+ guchar key[8];
+ int i, c, bit;
+
+ for (i = 0; i < 8; i++) {
+ key [i] = KEYBITS (key_56, i * 7);
+
+ /* Fix parity */
+ for (c = bit = 0; bit < 8; bit++)
+ if (key [i] & (1 << bit))
+ c++;
+ if (!(c & 1))
+ key [i] ^= 0x01;
+ }
+
+ deskey (ks, key, 0);
+}
+
+static void
+ntlm_calc_response (const guchar key[21], const guchar plaintext[8],
+ guchar results[24])
+{
+ DES_KS ks;
+
+ memcpy (results, plaintext, 8);
+ memcpy (results + 8, plaintext, 8);
+ memcpy (results + 16, plaintext, 8);
+
+ setup_schedule (key, ks);
+ des (ks, results);
+
+ setup_schedule (key + 7, ks);
+ des (ks, results + 8);
+
+ setup_schedule (key + 14, ks);
+ des (ks, results + 16);
+}
+
+
+/*
+ * MD4 encoder. (The one everyone else uses is not GPL-compatible;
+ * this is a reimplementation from spec.) This doesn't need to be
+ * efficient for our purposes, although it would be nice to fix
+ * it to not malloc()...
+ */
+
+#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) )
+#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) )
+#define H(X,Y,Z) ( (X)^(Y)^(Z) )
+#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) )
+
+static void
+md4sum (const unsigned char *in, int nbytes, unsigned char digest[16])
+{
+ unsigned char *M;
+ guint32 A, B, C, D, AA, BB, CC, DD, X[16];
+ int pbytes, nbits = nbytes * 8, i, j;
+
+ pbytes = (120 - (nbytes % 64)) % 64;
+ M = alloca (nbytes + pbytes + 8);
+ memcpy (M, in, nbytes);
+ memset (M + nbytes, 0, pbytes + 8);
+ M[nbytes] = 0x80;
+ M[nbytes + pbytes] = nbits & 0xFF;
+ M[nbytes + pbytes + 1] = (nbits >> 8) & 0xFF;
+ M[nbytes + pbytes + 2] = (nbits >> 16) & 0xFF;
+ M[nbytes + pbytes + 3] = (nbits >> 24) & 0xFF;
+
+ A = 0x67452301;
+ B = 0xEFCDAB89;
+ C = 0x98BADCFE;
+ D = 0x10325476;
+
+ for (i = 0; i < nbytes + pbytes + 8; i += 64) {
+ for (j = 0; j < 16; j++) {
+ X[j] = (M[i + j*4]) |
+ (M[i + j*4 + 1] << 8) |
+ (M[i + j*4 + 2] << 16) |
+ (M[i + j*4 + 3] << 24);
+ }
+
+ AA = A;
+ BB = B;
+ CC = C;
+ DD = D;
+
+ A = ROT (A + F(B, C, D) + X[0], 3);
+ D = ROT (D + F(A, B, C) + X[1], 7);
+ C = ROT (C + F(D, A, B) + X[2], 11);
+ B = ROT (B + F(C, D, A) + X[3], 19);
+ A = ROT (A + F(B, C, D) + X[4], 3);
+ D = ROT (D + F(A, B, C) + X[5], 7);
+ C = ROT (C + F(D, A, B) + X[6], 11);
+ B = ROT (B + F(C, D, A) + X[7], 19);
+ A = ROT (A + F(B, C, D) + X[8], 3);
+ D = ROT (D + F(A, B, C) + X[9], 7);
+ C = ROT (C + F(D, A, B) + X[10], 11);
+ B = ROT (B + F(C, D, A) + X[11], 19);
+ A = ROT (A + F(B, C, D) + X[12], 3);
+ D = ROT (D + F(A, B, C) + X[13], 7);
+ C = ROT (C + F(D, A, B) + X[14], 11);
+ B = ROT (B + F(C, D, A) + X[15], 19);
+
+ A = ROT (A + G(B, C, D) + X[0] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[4] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[8] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13);
+ A = ROT (A + G(B, C, D) + X[1] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[5] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[9] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13);
+ A = ROT (A + G(B, C, D) + X[2] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[6] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13);
+ A = ROT (A + G(B, C, D) + X[3] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[7] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13);
+
+ A = ROT (A + H(B, C, D) + X[0] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[8] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[4] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15);
+ A = ROT (A + H(B, C, D) + X[2] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[6] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15);
+ A = ROT (A + H(B, C, D) + X[1] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[9] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[5] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15);
+ A = ROT (A + H(B, C, D) + X[3] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[7] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15);
+
+ A += AA;
+ B += BB;
+ C += CC;
+ D += DD;
+ }
+
+ digest[0] = A & 0xFF;
+ digest[1] = (A >> 8) & 0xFF;
+ digest[2] = (A >> 16) & 0xFF;
+ digest[3] = (A >> 24) & 0xFF;
+ digest[4] = B & 0xFF;
+ digest[5] = (B >> 8) & 0xFF;
+ digest[6] = (B >> 16) & 0xFF;
+ digest[7] = (B >> 24) & 0xFF;
+ digest[8] = C & 0xFF;
+ digest[9] = (C >> 8) & 0xFF;
+ digest[10] = (C >> 16) & 0xFF;
+ digest[11] = (C >> 24) & 0xFF;
+ digest[12] = D & 0xFF;
+ digest[13] = (D >> 8) & 0xFF;
+ digest[14] = (D >> 16) & 0xFF;
+ digest[15] = (D >> 24) & 0xFF;
+}
+
+
+/* Public domain DES implementation from Phil Karn */
+static guint32 Spbox[8][64] = {
+ { 0x01010400, 0x00000000, 0x00010000, 0x01010404,
+ 0x01010004, 0x00010404, 0x00000004, 0x00010000,
+ 0x00000400, 0x01010400, 0x01010404, 0x00000400,
+ 0x01000404, 0x01010004, 0x01000000, 0x00000004,
+ 0x00000404, 0x01000400, 0x01000400, 0x00010400,
+ 0x00010400, 0x01010000, 0x01010000, 0x01000404,
+ 0x00010004, 0x01000004, 0x01000004, 0x00010004,
+ 0x00000000, 0x00000404, 0x00010404, 0x01000000,
+ 0x00010000, 0x01010404, 0x00000004, 0x01010000,
+ 0x01010400, 0x01000000, 0x01000000, 0x00000400,
+ 0x01010004, 0x00010000, 0x00010400, 0x01000004,
+ 0x00000400, 0x00000004, 0x01000404, 0x00010404,
+ 0x01010404, 0x00010004, 0x01010000, 0x01000404,
+ 0x01000004, 0x00000404, 0x00010404, 0x01010400,
+ 0x00000404, 0x01000400, 0x01000400, 0x00000000,
+ 0x00010004, 0x00010400, 0x00000000, 0x01010004 },
+ { 0x80108020, 0x80008000, 0x00008000, 0x00108020,
+ 0x00100000, 0x00000020, 0x80100020, 0x80008020,
+ 0x80000020, 0x80108020, 0x80108000, 0x80000000,
+ 0x80008000, 0x00100000, 0x00000020, 0x80100020,
+ 0x00108000, 0x00100020, 0x80008020, 0x00000000,
+ 0x80000000, 0x00008000, 0x00108020, 0x80100000,
+ 0x00100020, 0x80000020, 0x00000000, 0x00108000,
+ 0x00008020, 0x80108000, 0x80100000, 0x00008020,
+ 0x00000000, 0x00108020, 0x80100020, 0x00100000,
+ 0x80008020, 0x80100000, 0x80108000, 0x00008000,
+ 0x80100000, 0x80008000, 0x00000020, 0x80108020,
+ 0x00108020, 0x00000020, 0x00008000, 0x80000000,
+ 0x00008020, 0x80108000, 0x00100000, 0x80000020,
+ 0x00100020, 0x80008020, 0x80000020, 0x00100020,
+ 0x00108000, 0x00000000, 0x80008000, 0x00008020,
+ 0x80000000, 0x80100020, 0x80108020, 0x00108000 },
+ { 0x00000208, 0x08020200, 0x00000000, 0x08020008,
+ 0x08000200, 0x00000000, 0x00020208, 0x08000200,
+ 0x00020008, 0x08000008, 0x08000008, 0x00020000,
+ 0x08020208, 0x00020008, 0x08020000, 0x00000208,
+ 0x08000000, 0x00000008, 0x08020200, 0x00000200,
+ 0x00020200, 0x08020000, 0x08020008, 0x00020208,
+ 0x08000208, 0x00020200, 0x00020000, 0x08000208,
+ 0x00000008, 0x08020208, 0x00000200, 0x08000000,
+ 0x08020200, 0x08000000, 0x00020008, 0x00000208,
+ 0x00020000, 0x08020200, 0x08000200, 0x00000000,
+ 0x00000200, 0x00020008, 0x08020208, 0x08000200,
+ 0x08000008, 0x00000200, 0x00000000, 0x08020008,
+ 0x08000208, 0x00020000, 0x08000000, 0x08020208,
+ 0x00000008, 0x00020208, 0x00020200, 0x08000008,
+ 0x08020000, 0x08000208, 0x00000208, 0x08020000,
+ 0x00020208, 0x00000008, 0x08020008, 0x00020200 },
+ { 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802080, 0x00800081, 0x00800001, 0x00002001,
+ 0x00000000, 0x00802000, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00800080, 0x00800001,
+ 0x00000001, 0x00002000, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002001, 0x00002080,
+ 0x00800081, 0x00000001, 0x00002080, 0x00800080,
+ 0x00002000, 0x00802080, 0x00802081, 0x00000081,
+ 0x00800080, 0x00800001, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00000000, 0x00802000,
+ 0x00002080, 0x00800080, 0x00800081, 0x00000001,
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802081, 0x00000081, 0x00000001, 0x00002000,
+ 0x00800001, 0x00002001, 0x00802080, 0x00800081,
+ 0x00002001, 0x00002080, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002000, 0x00802080 },
+ { 0x00000100, 0x02080100, 0x02080000, 0x42000100,
+ 0x00080000, 0x00000100, 0x40000000, 0x02080000,
+ 0x40080100, 0x00080000, 0x02000100, 0x40080100,
+ 0x42000100, 0x42080000, 0x00080100, 0x40000000,
+ 0x02000000, 0x40080000, 0x40080000, 0x00000000,
+ 0x40000100, 0x42080100, 0x42080100, 0x02000100,
+ 0x42080000, 0x40000100, 0x00000000, 0x42000000,
+ 0x02080100, 0x02000000, 0x42000000, 0x00080100,
+ 0x00080000, 0x42000100, 0x00000100, 0x02000000,
+ 0x40000000, 0x02080000, 0x42000100, 0x40080100,
+ 0x02000100, 0x40000000, 0x42080000, 0x02080100,
+ 0x40080100, 0x00000100, 0x02000000, 0x42080000,
+ 0x42080100, 0x00080100, 0x42000000, 0x42080100,
+ 0x02080000, 0x00000000, 0x40080000, 0x42000000,
+ 0x00080100, 0x02000100, 0x40000100, 0x00080000,
+ 0x00000000, 0x40080000, 0x02080100, 0x40000100 },
+ { 0x20000010, 0x20400000, 0x00004000, 0x20404010,
+ 0x20400000, 0x00000010, 0x20404010, 0x00400000,
+ 0x20004000, 0x00404010, 0x00400000, 0x20000010,
+ 0x00400010, 0x20004000, 0x20000000, 0x00004010,
+ 0x00000000, 0x00400010, 0x20004010, 0x00004000,
+ 0x00404000, 0x20004010, 0x00000010, 0x20400010,
+ 0x20400010, 0x00000000, 0x00404010, 0x20404000,
+ 0x00004010, 0x00404000, 0x20404000, 0x20000000,
+ 0x20004000, 0x00000010, 0x20400010, 0x00404000,
+ 0x20404010, 0x00400000, 0x00004010, 0x20000010,
+ 0x00400000, 0x20004000, 0x20000000, 0x00004010,
+ 0x20000010, 0x20404010, 0x00404000, 0x20400000,
+ 0x00404010, 0x20404000, 0x00000000, 0x20400010,
+ 0x00000010, 0x00004000, 0x20400000, 0x00404010,
+ 0x00004000, 0x00400010, 0x20004010, 0x00000000,
+ 0x20404000, 0x20000000, 0x00400010, 0x20004010 },
+ { 0x00200000, 0x04200002, 0x04000802, 0x00000000,
+ 0x00000800, 0x04000802, 0x00200802, 0x04200800,
+ 0x04200802, 0x00200000, 0x00000000, 0x04000002,
+ 0x00000002, 0x04000000, 0x04200002, 0x00000802,
+ 0x04000800, 0x00200802, 0x00200002, 0x04000800,
+ 0x04000002, 0x04200000, 0x04200800, 0x00200002,
+ 0x04200000, 0x00000800, 0x00000802, 0x04200802,
+ 0x00200800, 0x00000002, 0x04000000, 0x00200800,
+ 0x04000000, 0x00200800, 0x00200000, 0x04000802,
+ 0x04000802, 0x04200002, 0x04200002, 0x00000002,
+ 0x00200002, 0x04000000, 0x04000800, 0x00200000,
+ 0x04200800, 0x00000802, 0x00200802, 0x04200800,
+ 0x00000802, 0x04000002, 0x04200802, 0x04200000,
+ 0x00200800, 0x00000000, 0x00000002, 0x04200802,
+ 0x00000000, 0x00200802, 0x04200000, 0x00000800,
+ 0x04000002, 0x04000800, 0x00000800, 0x00200002 },
+ { 0x10001040, 0x00001000, 0x00040000, 0x10041040,
+ 0x10000000, 0x10001040, 0x00000040, 0x10000000,
+ 0x00040040, 0x10040000, 0x10041040, 0x00041000,
+ 0x10041000, 0x00041040, 0x00001000, 0x00000040,
+ 0x10040000, 0x10000040, 0x10001000, 0x00001040,
+ 0x00041000, 0x00040040, 0x10040040, 0x10041000,
+ 0x00001040, 0x00000000, 0x00000000, 0x10040040,
+ 0x10000040, 0x10001000, 0x00041040, 0x00040000,
+ 0x00041040, 0x00040000, 0x10041000, 0x00001000,
+ 0x00000040, 0x10040040, 0x00001000, 0x00041040,
+ 0x10001000, 0x00000040, 0x10000040, 0x10040000,
+ 0x10040040, 0x10000000, 0x00040000, 0x10001040,
+ 0x00000000, 0x10041040, 0x00040040, 0x10000040,
+ 0x10040000, 0x10001000, 0x10001040, 0x00000000,
+ 0x10041040, 0x00041000, 0x00041000, 0x00001040,
+ 0x00001040, 0x00040040, 0x10000000, 0x10041000 }
+};
+
+#undef F
+#define F(l,r,key){\
+ work = ((r >> 4) | (r << 28)) ^ key[0];\
+ l ^= Spbox[6][work & 0x3f];\
+ l ^= Spbox[4][(work >> 8) & 0x3f];\
+ l ^= Spbox[2][(work >> 16) & 0x3f];\
+ l ^= Spbox[0][(work >> 24) & 0x3f];\
+ work = r ^ key[1];\
+ l ^= Spbox[7][work & 0x3f];\
+ l ^= Spbox[5][(work >> 8) & 0x3f];\
+ l ^= Spbox[3][(work >> 16) & 0x3f];\
+ l ^= Spbox[1][(work >> 24) & 0x3f];\
+}
+/* Encrypt or decrypt a block of data in ECB mode */
+static void
+des(ks,block)
+guint32 ks[16][2]; /* Key schedule */
+unsigned char block[8]; /* Data block */
+{
+ guint32 left,right,work;
+
+ /* Read input block and place in left/right in big-endian order */
+ left = ((guint32)block[0] << 24)
+ | ((guint32)block[1] << 16)
+ | ((guint32)block[2] << 8)
+ | (guint32)block[3];
+ right = ((guint32)block[4] << 24)
+ | ((guint32)block[5] << 16)
+ | ((guint32)block[6] << 8)
+ | (guint32)block[7];
+
+ /* Hoey's clever initial permutation algorithm, from Outerbridge
+ * (see Schneier p 478)
+ *
+ * The convention here is the same as Outerbridge: rotate each
+ * register left by 1 bit, i.e., so that "left" contains permuted
+ * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32
+ * (using origin-1 numbering as in the FIPS). This allows us to avoid
+ * one of the two rotates that would otherwise be required in each of
+ * the 16 rounds.
+ */
+ work = ((left >> 4) ^ right) & 0x0f0f0f0f;
+ right ^= work;
+ left ^= work << 4;
+ work = ((left >> 16) ^ right) & 0xffff;
+ right ^= work;
+ left ^= work << 16;
+ work = ((right >> 2) ^ left) & 0x33333333;
+ left ^= work;
+ right ^= (work << 2);
+ work = ((right >> 8) ^ left) & 0xff00ff;
+ left ^= work;
+ right ^= (work << 8);
+ right = (right << 1) | (right >> 31);
+ work = (left ^ right) & 0xaaaaaaaa;
+ left ^= work;
+ right ^= work;
+ left = (left << 1) | (left >> 31);
+
+ /* Now do the 16 rounds */
+ F(left,right,ks[0]);
+ F(right,left,ks[1]);
+ F(left,right,ks[2]);
+ F(right,left,ks[3]);
+ F(left,right,ks[4]);
+ F(right,left,ks[5]);
+ F(left,right,ks[6]);
+ F(right,left,ks[7]);
+ F(left,right,ks[8]);
+ F(right,left,ks[9]);
+ F(left,right,ks[10]);
+ F(right,left,ks[11]);
+ F(left,right,ks[12]);
+ F(right,left,ks[13]);
+ F(left,right,ks[14]);
+ F(right,left,ks[15]);
+
+ /* Inverse permutation, also from Hoey via Outerbridge and Schneier */
+ right = (right << 31) | (right >> 1);
+ work = (left ^ right) & 0xaaaaaaaa;
+ left ^= work;
+ right ^= work;
+ left = (left >> 1) | (left << 31);
+ work = ((left >> 8) ^ right) & 0xff00ff;
+ right ^= work;
+ left ^= work << 8;
+ work = ((left >> 2) ^ right) & 0x33333333;
+ right ^= work;
+ left ^= work << 2;
+ work = ((right >> 16) ^ left) & 0xffff;
+ left ^= work;
+ right ^= work << 16;
+ work = ((right >> 4) ^ left) & 0x0f0f0f0f;
+ left ^= work;
+ right ^= work << 4;
+
+ /* Put the block back into the user's buffer with final swap */
+ block[0] = right >> 24;
+ block[1] = right >> 16;
+ block[2] = right >> 8;
+ block[3] = right;
+ block[4] = left >> 24;
+ block[5] = left >> 16;
+ block[6] = left >> 8;
+ block[7] = left;
+}
+
+/* Key schedule-related tables from FIPS-46 */
+
+/* permuted choice table (key) */
+static unsigned char pc1[] = {
+ 57, 49, 41, 33, 25, 17, 9,
+ 1, 58, 50, 42, 34, 26, 18,
+ 10, 2, 59, 51, 43, 35, 27,
+ 19, 11, 3, 60, 52, 44, 36,
+
+ 63, 55, 47, 39, 31, 23, 15,
+ 7, 62, 54, 46, 38, 30, 22,
+ 14, 6, 61, 53, 45, 37, 29,
+ 21, 13, 5, 28, 20, 12, 4
+};
+
+/* number left rotations of pc1 */
+static unsigned char totrot[] = {
+ 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
+};
+
+/* permuted choice key (table) */
+static unsigned char pc2[] = {
+ 14, 17, 11, 24, 1, 5,
+ 3, 28, 15, 6, 21, 10,
+ 23, 19, 12, 4, 26, 8,
+ 16, 7, 27, 20, 13, 2,
+ 41, 52, 31, 37, 47, 55,
+ 30, 40, 51, 45, 33, 48,
+ 44, 49, 39, 56, 34, 53,
+ 46, 42, 50, 36, 29, 32
+};
+
+/* End of DES-defined tables */
+
+
+/* bit 0 is left-most in byte */
+static int bytebit[] = {
+ 0200,0100,040,020,010,04,02,01
+};
+
+
+/* Generate key schedule for encryption or decryption
+ * depending on the value of "decrypt"
+ */
+static void
+deskey(k,key,decrypt)
+DES_KS k; /* Key schedule array */
+unsigned char *key; /* 64 bits (will use only 56) */
+int decrypt; /* 0 = encrypt, 1 = decrypt */
+{
+ unsigned char pc1m[56]; /* place to modify pc1 into */
+ unsigned char pcr[56]; /* place to rotate pc1 into */
+ register int i,j,l;
+ int m;
+ unsigned char ks[8];
+
+ for (j=0; j<56; j++) { /* convert pc1 to bits of key */
+ l=pc1[j]-1; /* integer bit location */
+ m = l & 07; /* find bit */
+ pc1m[j]=(key[l>>3] & /* find which key byte l is in */
+ bytebit[m]) /* and which bit of that byte */
+ ? 1 : 0; /* and store 1-bit result */
+ }
+ for (i=0; i<16; i++) { /* key chunk for each iteration */
+ memset(ks,0,sizeof(ks)); /* Clear key schedule */
+ for (j=0; j<56; j++) /* rotate pc1 the right amount */
+ pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28];
+ /* rotate left and right halves independently */
+ for (j=0; j<48; j++){ /* select bits individually */
+ /* check bit that goes to ks[j] */
+ if (pcr[pc2[j]-1]){
+ /* mask it in if it's there */
+ l= j % 6;
+ ks[j/6] |= bytebit[l] >> 2;
+ }
+ }
+ /* Now convert to packed odd/even interleaved form */
+ k[i][0] = ((guint32)ks[0] << 24)
+ | ((guint32)ks[2] << 16)
+ | ((guint32)ks[4] << 8)
+ | ((guint32)ks[6]);
+ k[i][1] = ((guint32)ks[1] << 24)
+ | ((guint32)ks[3] << 16)
+ | ((guint32)ks[5] << 8)
+ | ((guint32)ks[7]);
+ }
+}
diff --git a/camel/camel-service.c b/camel/camel-service.c
new file mode 100644
index 0000000000..13190844e6
--- /dev/null
+++ b/camel/camel-service.c
@@ -0,0 +1,1060 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-service.c : Abstract class for an email service */
+
+/*
+ *
+ * Author :
+ * Bertrand Guiheneuf <bertrand@helixcode.com>
+ *
+ * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <sys/poll.h>
+
+#include "e-util/e-msgport.h"
+
+#include "e-util/e-host-utils.h"
+
+#include "camel-service.h"
+#include "camel-session.h"
+#include "camel-exception.h"
+#include "camel-operation.h"
+#include "camel-private.h"
+
+#define d(x)
+#define w(x)
+
+static CamelObjectClass *parent_class = NULL;
+
+/* Returns the class for a CamelService */
+#define CSERV_CLASS(so) CAMEL_SERVICE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static void construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+static gboolean service_connect(CamelService *service, CamelException *ex);
+static gboolean service_disconnect(CamelService *service, gboolean clean,
+ CamelException *ex);
+static void cancel_connect (CamelService *service);
+static GList *query_auth_types (CamelService *service, CamelException *ex);
+static char *get_name (CamelService *service, gboolean brief);
+static char *get_path (CamelService *service);
+
+static int service_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
+static int service_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
+
+
+static void
+camel_service_class_init (CamelServiceClass *camel_service_class)
+{
+ CamelObjectClass *object_class = CAMEL_OBJECT_CLASS (camel_service_class);
+
+ parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
+
+ /* virtual method overloading */
+ object_class->setv = service_setv;
+ object_class->getv = service_getv;
+
+ /* virtual method definition */
+ camel_service_class->construct = construct;
+ camel_service_class->connect = service_connect;
+ camel_service_class->disconnect = service_disconnect;
+ camel_service_class->cancel_connect = cancel_connect;
+ camel_service_class->query_auth_types = query_auth_types;
+ camel_service_class->get_name = get_name;
+ camel_service_class->get_path = get_path;
+}
+
+static void
+camel_service_init (void *o, void *k)
+{
+ CamelService *service = o;
+
+ service->priv = g_malloc0(sizeof(*service->priv));
+ service->priv->connect_lock = e_mutex_new(E_MUTEX_REC);
+ service->priv->connect_op_lock = e_mutex_new(E_MUTEX_SIMPLE);
+}
+
+static void
+camel_service_finalize (CamelObject *object)
+{
+ CamelService *service = CAMEL_SERVICE (object);
+
+ if (service->status == CAMEL_SERVICE_CONNECTED) {
+ CamelException ex;
+
+ camel_exception_init (&ex);
+ CSERV_CLASS (service)->disconnect (service, TRUE, &ex);
+ if (camel_exception_is_set (&ex)) {
+ w(g_warning ("camel_service_finalize: silent disconnect failure: %s",
+ camel_exception_get_description (&ex)));
+ }
+ camel_exception_clear (&ex);
+ }
+
+ if (service->url)
+ camel_url_free (service->url);
+ if (service->session)
+ camel_object_unref (service->session);
+
+ e_mutex_destroy (service->priv->connect_lock);
+ e_mutex_destroy (service->priv->connect_op_lock);
+
+ g_free (service->priv);
+}
+
+
+
+CamelType
+camel_service_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type =
+ camel_type_register (CAMEL_OBJECT_TYPE,
+ "CamelService",
+ sizeof (CamelService),
+ sizeof (CamelServiceClass),
+ (CamelObjectClassInitFunc) camel_service_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_service_init,
+ camel_service_finalize );
+ }
+
+ return type;
+}
+
+
+static int
+service_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+ CamelService *service = (CamelService *) object;
+ CamelURL *url = service->url;
+ gboolean reconnect = FALSE;
+ guint32 tag;
+ int i;
+
+ for (i = 0; i < args->argc; i++) {
+ tag = args->argv[i].tag;
+
+ /* make sure this is an arg we're supposed to handle */
+ if ((tag & CAMEL_ARG_TAG) <= CAMEL_SERVICE_ARG_FIRST ||
+ (tag & CAMEL_ARG_TAG) >= CAMEL_SERVICE_ARG_FIRST + 100)
+ continue;
+
+ if (tag == CAMEL_SERVICE_USERNAME) {
+ /* set the username */
+ if (strcmp (url->user, args->argv[i].ca_str) != 0) {
+ camel_url_set_user (url, args->argv[i].ca_str);
+ reconnect = TRUE;
+ }
+ } else if (tag == CAMEL_SERVICE_AUTH) {
+ /* set the auth mechanism */
+ if (strcmp (url->authmech, args->argv[i].ca_str) != 0) {
+ camel_url_set_authmech (url, args->argv[i].ca_str);
+ reconnect = TRUE;
+ }
+ } else if (tag == CAMEL_SERVICE_HOSTNAME) {
+ /* set the hostname */
+ if (strcmp (url->host, args->argv[i].ca_str) != 0) {
+ camel_url_set_host (url, args->argv[i].ca_str);
+ reconnect = TRUE;
+ }
+ } else if (tag == CAMEL_SERVICE_PORT) {
+ /* set the port */
+ if (url->port != args->argv[i].ca_int) {
+ camel_url_set_port (url, args->argv[i].ca_int);
+ reconnect = TRUE;
+ }
+ } else if (tag == CAMEL_SERVICE_PATH) {
+ /* set the path */
+ if (strcmp (url->path, args->argv[i].ca_str) != 0) {
+ camel_url_set_path (url, args->argv[i].ca_str);
+ reconnect = TRUE;
+ }
+ } else {
+ /* error? */
+ continue;
+ }
+
+ /* let our parent know that we've handled this arg */
+ camel_argv_ignore (args, i);
+ }
+
+ /* FIXME: what if we are in the process of connecting? */
+ if (reconnect && service->status == CAMEL_SERVICE_CONNECTED) {
+ /* reconnect the service using the new URL */
+ if (camel_service_disconnect (service, TRUE, ex))
+ camel_service_connect (service, ex);
+ }
+
+ return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
+}
+
+static int
+service_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ CamelService *service = (CamelService *) object;
+ CamelURL *url = service->url;
+ guint32 tag;
+ int i;
+
+ for (i = 0; i < args->argc; i++) {
+ tag = args->argv[i].tag;
+
+ /* make sure this is an arg we're supposed to handle */
+ if ((tag & CAMEL_ARG_TAG) <= CAMEL_SERVICE_ARG_FIRST ||
+ (tag & CAMEL_ARG_TAG) >= CAMEL_SERVICE_ARG_FIRST + 100)
+ continue;
+
+ switch (tag) {
+ case CAMEL_SERVICE_USERNAME:
+ /* get the username */
+ *args->argv[i].ca_str = url->user;
+ break;
+ case CAMEL_SERVICE_AUTH:
+ /* get the auth mechanism */
+ *args->argv[i].ca_str = url->authmech;
+ break;
+ case CAMEL_SERVICE_HOSTNAME:
+ /* get the hostname */
+ *args->argv[i].ca_str = url->host;
+ break;
+ case CAMEL_SERVICE_PORT:
+ /* get the port */
+ *args->argv[i].ca_int = url->port;
+ break;
+ case CAMEL_SERVICE_PATH:
+ /* get the path */
+ *args->argv[i].ca_str = url->path;
+ break;
+ default:
+ /* error? */
+ break;
+ }
+ }
+
+ return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
+}
+
+static void
+construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
+{
+ char *err, *url_string;
+
+ if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER) &&
+ (url->user == NULL || url->user[0] == '\0')) {
+ err = _("URL '%s' needs a username component");
+ goto fail;
+ } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST) &&
+ (url->host == NULL || url->host[0] == '\0')) {
+ err = _("URL '%s' needs a host component");
+ goto fail;
+ } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH) &&
+ (url->path == NULL || url->path[0] == '\0')) {
+ err = _("URL '%s' needs a path component");
+ goto fail;
+ }
+
+ service->provider = provider;
+ service->url = camel_url_copy(url);
+ service->session = session;
+ camel_object_ref (session);
+
+ service->status = CAMEL_SERVICE_DISCONNECTED;
+
+ return;
+
+fail:
+ url_string = camel_url_to_string(url, CAMEL_URL_HIDE_PASSWORD);
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, err, url_string);
+ g_free(url_string);
+}
+
+/**
+ * camel_service_construct:
+ * @service: the CamelService
+ * @session: the session for the service
+ * @provider: the service's provider
+ * @url: the default URL for the service (may be NULL)
+ * @ex: a CamelException
+ *
+ * Constructs a CamelService initialized with the given parameters.
+ **/
+void
+camel_service_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex)
+{
+ g_return_if_fail (CAMEL_IS_SERVICE (service));
+ g_return_if_fail (CAMEL_IS_SESSION (session));
+
+ CSERV_CLASS (service)->construct (service, session, provider, url, ex);
+}
+
+
+static gboolean
+service_connect (CamelService *service, CamelException *ex)
+{
+ /* Things like the CamelMboxStore can validly
+ * not define a connect function.
+ */
+ return TRUE;
+}
+
+/**
+ * camel_service_connect:
+ * @service: CamelService object
+ * @ex: a CamelException
+ *
+ * Connect to the service using the parameters it was initialized
+ * with.
+ *
+ * Return value: whether or not the connection succeeded
+ **/
+
+gboolean
+camel_service_connect (CamelService *service, CamelException *ex)
+{
+ gboolean ret = FALSE;
+ gboolean unreg = FALSE;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE);
+ g_return_val_if_fail (service->session != NULL, FALSE);
+ g_return_val_if_fail (service->url != NULL, FALSE);
+
+ CAMEL_SERVICE_LOCK (service, connect_lock);
+
+ if (service->status == CAMEL_SERVICE_CONNECTED) {
+ CAMEL_SERVICE_UNLOCK (service, connect_lock);
+ return TRUE;
+ }
+
+ /* Register a separate operation for connecting, so that
+ * the offline code can cancel it.
+ */
+ CAMEL_SERVICE_LOCK (service, connect_op_lock);
+ service->connect_op = camel_operation_registered ();
+ if (!service->connect_op) {
+ service->connect_op = camel_operation_new (NULL, NULL);
+ camel_operation_register (service->connect_op);
+ unreg = TRUE;
+ }
+ CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
+
+ service->status = CAMEL_SERVICE_CONNECTING;
+ ret = CSERV_CLASS (service)->connect (service, ex);
+ service->status = ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED;
+
+ CAMEL_SERVICE_LOCK (service, connect_op_lock);
+ if (service->connect_op) {
+ if (unreg)
+ camel_operation_unregister (service->connect_op);
+
+ camel_operation_unref (service->connect_op);
+ service->connect_op = NULL;
+ }
+ CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
+
+ CAMEL_SERVICE_UNLOCK (service, connect_lock);
+
+ return ret;
+}
+
+static gboolean
+service_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+{
+ /*service->connect_level--;*/
+
+ /* We let people get away with not having a disconnect
+ * function -- CamelMboxStore, for example.
+ */
+
+ return TRUE;
+}
+
+/**
+ * camel_service_disconnect:
+ * @service: CamelService object
+ * @clean: whether or not to try to disconnect cleanly.
+ * @ex: a CamelException
+ *
+ * Disconnect from the service. If @clean is %FALSE, it should not
+ * try to do any synchronizing or other cleanup of the connection.
+ *
+ * Return value: whether or not the disconnection succeeded without
+ * errors. (Consult @ex if %FALSE.)
+ **/
+gboolean
+camel_service_disconnect (CamelService *service, gboolean clean,
+ CamelException *ex)
+{
+ gboolean res = TRUE;
+ int unreg = FALSE;
+
+ CAMEL_SERVICE_LOCK (service, connect_lock);
+
+ if (service->status != CAMEL_SERVICE_DISCONNECTED
+ && service->status != CAMEL_SERVICE_DISCONNECTING) {
+ CAMEL_SERVICE_LOCK (service, connect_op_lock);
+ service->connect_op = camel_operation_registered ();
+ if (!service->connect_op) {
+ service->connect_op = camel_operation_new (NULL, NULL);
+ camel_operation_register (service->connect_op);
+ unreg = TRUE;
+ }
+ CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
+
+ service->status = CAMEL_SERVICE_DISCONNECTING;
+ res = CSERV_CLASS (service)->disconnect (service, clean, ex);
+ service->status = CAMEL_SERVICE_DISCONNECTED;
+
+ CAMEL_SERVICE_LOCK (service, connect_op_lock);
+ if (unreg)
+ camel_operation_unregister (service->connect_op);
+
+ camel_operation_unref (service->connect_op);
+ service->connect_op = NULL;
+ CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
+ }
+
+ CAMEL_SERVICE_UNLOCK (service, connect_lock);
+
+ return res;
+}
+
+static void
+cancel_connect (CamelService *service)
+{
+ camel_operation_cancel (service->connect_op);
+}
+
+/**
+ * camel_service_cancel_connect:
+ * @service: a service
+ *
+ * If @service is currently attempting to connect to or disconnect
+ * from a server, this causes it to stop and fail. Otherwise it is a
+ * no-op.
+ **/
+void
+camel_service_cancel_connect (CamelService *service)
+{
+ CAMEL_SERVICE_LOCK (service, connect_op_lock);
+ if (service->connect_op)
+ CSERV_CLASS (service)->cancel_connect (service);
+ CAMEL_SERVICE_UNLOCK (service, connect_op_lock);
+}
+
+/**
+ * camel_service_get_url:
+ * @service: a service
+ *
+ * Returns the URL representing a service. The returned URL must be
+ * freed when it is no longer needed. For security reasons, this
+ * routine does not return the password.
+ *
+ * Return value: the url name
+ **/
+char *
+camel_service_get_url (CamelService *service)
+{
+ return camel_url_to_string (service->url, CAMEL_URL_HIDE_PASSWORD);
+}
+
+
+static char *
+get_name (CamelService *service, gboolean brief)
+{
+ w(g_warning ("CamelService::get_name not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (service))));
+ return g_strdup ("???");
+}
+
+/**
+ * camel_service_get_name:
+ * @service: the service
+ * @brief: whether or not to use a briefer form
+ *
+ * This gets the name of the service in a "friendly" (suitable for
+ * humans) form. If @brief is %TRUE, this should be a brief description
+ * such as for use in the folder tree. If @brief is %FALSE, it should
+ * be a more complete and mostly unambiguous description.
+ *
+ * Return value: the description, which the caller must free.
+ **/
+char *
+camel_service_get_name (CamelService *service, gboolean brief)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+ g_return_val_if_fail (service->url, NULL);
+
+ return CSERV_CLASS (service)->get_name (service, brief);
+}
+
+
+static char *
+get_path (CamelService *service)
+{
+ CamelProvider *prov = service->provider;
+ CamelURL *url = service->url;
+ GString *gpath;
+ char *path;
+
+ /* A sort of ad-hoc default implementation that works for our
+ * current set of services.
+ */
+
+ gpath = g_string_new (service->provider->protocol);
+ if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_USER)) {
+ if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
+ g_string_append_printf (gpath, "/%s@%s",
+ url->user ? url->user : "",
+ url->host ? url->host : "");
+
+ if (url->port)
+ g_string_append_printf (gpath, ":%d", url->port);
+ } else {
+ g_string_append_printf (gpath, "/%s%s", url->user ? url->user : "",
+ CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@");
+ }
+ } else if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) {
+ g_string_append_printf (gpath, "/%s%s",
+ CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_HOST) ? "" : "@",
+ url->host ? url->host : "");
+
+ if (url->port)
+ g_string_append_printf (gpath, ":%d", url->port);
+ }
+
+ if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH))
+ g_string_append_printf (gpath, "%s%s", *url->path == '/' ? "" : "/", url->path);
+
+ path = gpath->str;
+ g_string_free (gpath, FALSE);
+
+ return path;
+}
+
+/**
+ * camel_service_get_path:
+ * @service: the service
+ *
+ * This gets a valid UNIX relative path describing the service, which
+ * is guaranteed to be different from the path returned for any
+ * different service. This path MUST start with the name of the
+ * provider, followed by a "/", but after that, it is up to the
+ * provider.
+ *
+ * Return value: the path, which the caller must free.
+ **/
+char *
+camel_service_get_path (CamelService *service)
+{
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+ g_return_val_if_fail (service->url, NULL);
+
+ return CSERV_CLASS (service)->get_path (service);
+}
+
+
+/**
+ * camel_service_get_session:
+ * @service: a service
+ *
+ * Returns the CamelSession associated with the service.
+ *
+ * Return value: the session
+ **/
+CamelSession *
+camel_service_get_session (CamelService *service)
+{
+ return service->session;
+}
+
+/**
+ * camel_service_get_provider:
+ * @service: a service
+ *
+ * Returns the CamelProvider associated with the service.
+ *
+ * Return value: the provider
+ **/
+CamelProvider *
+camel_service_get_provider (CamelService *service)
+{
+ return service->provider;
+}
+
+static GList *
+query_auth_types (CamelService *service, CamelException *ex)
+{
+ return NULL;
+}
+
+/**
+ * camel_service_query_auth_types:
+ * @service: a CamelService
+ * @ex: a CamelException
+ *
+ * This is used by the mail source wizard to get the list of
+ * authentication types supported by the protocol, and information
+ * about them.
+ *
+ * Return value: a list of CamelServiceAuthType records. The caller
+ * must free the list with g_list_free() when it is done with it.
+ **/
+GList *
+camel_service_query_auth_types (CamelService *service, CamelException *ex)
+{
+ GList *ret;
+
+ /* note that we get the connect lock here, which means the callee
+ must not call the connect functions itself */
+ CAMEL_SERVICE_LOCK (service, connect_lock);
+ ret = CSERV_CLASS (service)->query_auth_types (service, ex);
+ CAMEL_SERVICE_UNLOCK (service, connect_lock);
+
+ return ret;
+}
+
+/* ********************************************************************** */
+struct _addrinfo_msg {
+ EMsg msg;
+ unsigned int cancelled:1;
+
+ /* for host lookup */
+ const char *name;
+ const char *service;
+ int result;
+ const struct addrinfo *hints;
+ struct addrinfo **res;
+
+ /* for host lookup emulation */
+#ifdef NEED_ADDRINFO
+ struct hostent hostbuf;
+ int hostbuflen;
+ char *hostbufmem;
+#endif
+
+ /* for name lookup */
+ const struct sockaddr *addr;
+ socklen_t addrlen;
+ char *host;
+ int hostlen;
+ char *serv;
+ int servlen;
+ int flags;
+};
+
+static void
+cs_freeinfo(struct _addrinfo_msg *msg)
+{
+ g_free(msg->host);
+ g_free(msg->serv);
+#ifdef NEED_ADDRINFO
+ g_free(msg->hostbufmem);
+#endif
+ g_free(msg);
+}
+
+/* returns -1 if cancelled */
+static int
+cs_waitinfo(void *(worker)(void *), struct _addrinfo_msg *msg, const char *error, CamelException *ex)
+{
+ EMsgPort *reply_port;
+ pthread_t id;
+ int err, cancel_fd, cancel = 0, fd;
+
+ cancel_fd = camel_operation_cancel_fd(NULL);
+ if (cancel_fd == -1) {
+ worker(msg);
+ return 0;
+ }
+
+ reply_port = msg->msg.reply_port = e_msgport_new();
+ fd = e_msgport_fd(msg->msg.reply_port);
+ if ((err = pthread_create(&id, NULL, worker, msg)) == 0) {
+ struct pollfd polls[2];
+ int status;
+
+ polls[0].fd = fd;
+ polls[0].events = POLLIN;
+ polls[1].fd = cancel_fd;
+ polls[1].events = POLLIN;
+
+ d(printf("waiting for name return/cancellation in main process\n"));
+ do {
+ polls[0].revents = 0;
+ polls[1].revents = 0;
+ status = poll(polls, 2, -1);
+ } while (status == -1 && errno == EINTR);
+
+ if (status == -1 || (polls[1].revents & POLLIN)) {
+ if (status == -1)
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error, g_strerror(errno));
+ else
+ camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+
+ /* We cancel so if the thread impl is decent it causes immediate exit.
+ We detach so we dont need to wait for it to exit if it isn't.
+ We check the reply port incase we had a reply in the mean time, which we free later */
+ d(printf("Cancelling lookup thread and leaving it\n"));
+ msg->cancelled = 1;
+ pthread_detach(id);
+ pthread_cancel(id);
+ cancel = 1;
+ } else {
+ struct _addrinfo_msg *reply = (struct _addrinfo_msg *)e_msgport_get(reply_port);
+
+ g_assert(reply == msg);
+ d(printf("waiting for child to exit\n"));
+ pthread_join(id, NULL);
+ d(printf("child done\n"));
+ }
+ } else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", error, _("cannot create thread"), g_strerror(err));
+ }
+ e_msgport_destroy(reply_port);
+
+ return cancel;
+}
+
+#ifdef NEED_ADDRINFO
+static void *
+cs_getaddrinfo(void *data)
+{
+ struct _addrinfo_msg *msg = data;
+ int herr;
+ struct hostent h;
+ struct addrinfo *res, *last = NULL;
+ struct sockaddr_in *sin;
+ in_port_t port = 0;
+ int i;
+
+ /* This is a pretty simplistic emulation of getaddrinfo */
+
+ while ((msg->result = e_gethostbyname_r(msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
+ pthread_testcancel();
+ msg->hostbuflen *= 2;
+ msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
+ }
+
+ /* If we got cancelled, dont reply, just free it */
+ if (msg->cancelled)
+ goto cancel;
+
+ /* FIXME: map error numbers across */
+ if (msg->result != 0)
+ goto reply;
+
+ /* check hints matched */
+ if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
+ msg->result = EAI_FAMILY;
+ goto reply;
+ }
+
+ /* we only support ipv4 for this interface, even if it could supply ipv6 */
+ if (h.h_addrtype != AF_INET) {
+ msg->result = EAI_FAMILY;
+ goto reply;
+ }
+
+ /* check service mapping */
+ if (msg->service) {
+ const char *p = msg->service;
+
+ while (*p) {
+ if (*p < '0' || *p > '9')
+ break;
+ p++;
+ }
+
+ if (*p) {
+ const char *socktype = NULL;
+ struct servent *serv;
+
+ if (msg->hints && msg->hints->ai_socktype) {
+ if (msg->hints->ai_socktype == SOCK_STREAM)
+ socktype = "tcp";
+ else if (msg->hints->ai_socktype == SOCK_DGRAM)
+ socktype = "udp";
+ }
+
+ serv = getservbyname(msg->service, socktype);
+ if (serv == NULL) {
+ msg->result = EAI_NONAME;
+ goto reply;
+ }
+ port = serv->s_port;
+ } else {
+ port = htons(strtoul(msg->service, NULL, 10));
+ }
+ }
+
+ for (i=0;h.h_addr_list[i];i++) {
+ res = g_malloc0(sizeof(*res));
+ if (msg->hints) {
+ res->ai_flags = msg->hints->ai_flags;
+ if (msg->hints->ai_flags & AI_CANONNAME)
+ res->ai_canonname = g_strdup(h.h_name);
+ res->ai_socktype = msg->hints->ai_socktype;
+ res->ai_protocol = msg->hints->ai_protocol;
+ } else {
+ res->ai_flags = 0;
+ res->ai_socktype = SOCK_STREAM; /* fudge */
+ res->ai_protocol = 0; /* fudge */
+ }
+ res->ai_family = AF_INET;
+ res->ai_addrlen = sizeof(*sin);
+ res->ai_addr = g_malloc(sizeof(*sin));
+ sin = (struct sockaddr_in *)res->ai_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_port = port;
+ memcpy(&sin->sin_addr, h.h_addr_list[i], sizeof(sin->sin_addr));
+
+ if (last == NULL) {
+ *msg->res = last = res;
+ } else {
+ last->ai_next = res;
+ last = res;
+ }
+ }
+reply:
+ e_msgport_reply((EMsg *)msg);
+ return NULL;
+cancel:
+ cs_freeinfo(msg);
+ return NULL;
+}
+#else
+static void *
+cs_getaddrinfo(void *data)
+{
+ struct _addrinfo_msg *info = data;
+
+ info->result = getaddrinfo(info->name, info->service, info->hints, info->res);
+
+ if (info->cancelled) {
+ g_free(info);
+ } else {
+ e_msgport_reply((EMsg *)info);
+ }
+
+ return NULL;
+}
+#endif /* NEED_ADDRINFO */
+
+struct addrinfo *
+camel_getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, CamelException *ex)
+{
+ struct _addrinfo_msg *msg;
+ struct addrinfo *res = NULL;
+#ifndef ENABLE_IPv6
+ struct addrinfo myhints;
+#endif
+ g_return_val_if_fail(name != NULL, NULL);
+
+ if (camel_operation_cancel_check(NULL)) {
+ camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ return NULL;
+ }
+
+ camel_operation_start_transient(NULL, _("Resolving: %s"), name);
+
+ /* force ipv4 addresses only */
+#ifndef ENABLE_IPv6
+ if (hints == NULL)
+ memset(&myhints, 0, sizeof(myhints));
+ else
+ memcpy (&myhints, hints, sizeof (myhints));
+
+ myhints.ai_family = AF_INET;
+ hints = &myhints;
+#endif
+
+ msg = g_malloc0(sizeof(*msg));
+ msg->name = name;
+ msg->service = service;
+ msg->hints = hints;
+ msg->res = &res;
+#ifdef NEED_ADDRINFO
+ msg->hostbuflen = 1024;
+ msg->hostbufmem = g_malloc(msg->hostbuflen);
+#endif
+ if (cs_waitinfo(cs_getaddrinfo, msg, _("Host lookup failed"), ex) == 0) {
+ if (msg->result != 0)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: %s: %s"),
+ name, gai_strerror (msg->result));
+
+ cs_freeinfo(msg);
+ } else
+ res = NULL;
+
+ camel_operation_end(NULL);
+
+ return res;
+}
+
+void
+camel_freeaddrinfo(struct addrinfo *host)
+{
+#ifdef NEED_ADDRINFO
+ while (host) {
+ struct addrinfo *next = host->ai_next;
+
+ g_free(host->ai_canonname);
+ g_free(host->ai_addr);
+ g_free(host);
+ host = next;
+ }
+#else
+ freeaddrinfo(host);
+#endif
+}
+
+#ifdef NEED_ADDRINFO
+static void *
+cs_getnameinfo(void *data)
+{
+ struct _addrinfo_msg *msg = data;
+ int herr;
+ struct hostent h;
+ struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr;
+
+ /* FIXME: error code */
+ if (msg->addr->sa_family != AF_INET) {
+ msg->result = -1;
+ return NULL;
+ }
+
+ /* FIXME: honour getnameinfo flags: do we care, not really */
+
+ while ((msg->result = e_gethostbyaddr_r((const char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET, &h,
+ msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
+ pthread_testcancel ();
+ msg->hostbuflen *= 2;
+ msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
+ }
+
+ if (msg->cancelled)
+ goto cancel;
+
+ if (msg->host) {
+ g_free(msg->host);
+ if (msg->result == 0 && h.h_name && h.h_name[0]) {
+ msg->host = g_strdup(h.h_name);
+ } else {
+ unsigned char *in = (unsigned char *)&sin->sin_addr;
+
+ /* sin_addr is always network order which is big-endian */
+ msg->host = g_strdup_printf("%u.%u.%u.%u", in[0], in[1], in[2], in[3]);
+ }
+ }
+
+ /* we never actually use this anyway */
+ if (msg->serv)
+ sprintf(msg->serv, "%d", sin->sin_port);
+
+ e_msgport_reply((EMsg *)msg);
+ return NULL;
+cancel:
+ cs_freeinfo(msg);
+ return NULL;
+}
+#else
+static void *
+cs_getnameinfo(void *data)
+{
+ struct _addrinfo_msg *msg = data;
+
+ /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */
+ msg->result = getnameinfo(msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags);
+
+ if (msg->cancelled)
+ cs_freeinfo(msg);
+ else
+ e_msgport_reply((EMsg *)msg);
+
+ return NULL;
+}
+#endif
+
+int
+camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, int flags, CamelException *ex)
+{
+ struct _addrinfo_msg *msg;
+ int result;
+
+ if (camel_operation_cancel_check(NULL)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ return -1;
+ }
+
+ camel_operation_start_transient(NULL, _("Resolving address"));
+
+ msg = g_malloc0(sizeof(*msg));
+ msg->addr = sa;
+ msg->addrlen = salen;
+ if (host) {
+ msg->hostlen = NI_MAXHOST;
+ msg->host = g_malloc(msg->hostlen);
+ msg->host[0] = 0;
+ }
+ if (serv) {
+ msg->servlen = NI_MAXSERV;
+ msg->serv = g_malloc(msg->servlen);
+ msg->serv[0] = 0;
+ }
+ msg->flags = flags;
+#ifdef NEED_ADDRINFO
+ msg->hostbuflen = 1024;
+ msg->hostbufmem = g_malloc(msg->hostbuflen);
+#endif
+ cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex);
+
+ if ((result = msg->result) != 0)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Name lookup failed: %s"),
+ gai_strerror (result));
+
+ if (host)
+ *host = g_strdup(msg->host);
+ if (serv)
+ *serv = g_strdup(msg->serv);
+
+ g_free(msg->host);
+ g_free(msg->serv);
+ g_free(msg);
+
+ camel_operation_end(NULL);
+
+ return result;
+}
+
diff --git a/camel/camel-service.h b/camel/camel-service.h
new file mode 100644
index 0000000000..f49472cc5a
--- /dev/null
+++ b/camel/camel-service.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-service.h : Abstract class for an email service */
+
+/*
+ *
+ * Author :
+ * Bertrand Guiheneuf <bertrand@helixcode.com>
+ *
+ * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifndef CAMEL_SERVICE_H
+#define CAMEL_SERVICE_H 1
+
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus }*/
+
+#include <netdb.h>
+#include <camel/camel-object.h>
+#include <camel/camel-url.h>
+#include <camel/camel-provider.h>
+#include <camel/camel-operation.h>
+
+#define CAMEL_SERVICE_TYPE (camel_service_get_type ())
+#define CAMEL_SERVICE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_SERVICE_TYPE, CamelService))
+#define CAMEL_SERVICE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_SERVICE_TYPE, CamelServiceClass))
+#define CAMEL_IS_SERVICE(o) (CAMEL_CHECK_TYPE((o), CAMEL_SERVICE_TYPE))
+
+enum {
+ CAMEL_SERVICE_ARG_FIRST = CAMEL_ARG_FIRST + 100,
+ CAMEL_SERVICE_ARG_USERNAME,
+ CAMEL_SERVICE_ARG_AUTH,
+ CAMEL_SERVICE_ARG_HOSTNAME,
+ CAMEL_SERVICE_ARG_PORT,
+ CAMEL_SERVICE_ARG_PATH,
+};
+
+#define CAMEL_SERVICE_USERNAME (CAMEL_SERVICE_ARG_USERNAME | CAMEL_ARG_STR)
+#define CAMEL_SERVICE_AUTH (CAMEL_SERVICE_ARG_AUTH | CAMEL_ARG_STR)
+#define CAMEL_SERVICE_HOSTNAME (CAMEL_SERVICE_ARG_HOSTNAME | CAMEL_ARG_STR)
+#define CAMEL_SERVICE_PORT (CAMEL_SERVICE_ARG_PORT | CAMEL_ARG_INT)
+#define CAMEL_SERVICE_PATH (CAMEL_SERVICE_ARG_PATH | CAMEL_ARG_STR)
+
+typedef enum {
+ CAMEL_SERVICE_DISCONNECTED,
+ CAMEL_SERVICE_CONNECTING,
+ CAMEL_SERVICE_CONNECTED,
+ CAMEL_SERVICE_DISCONNECTING
+} CamelServiceConnectionStatus;
+
+struct _CamelService {
+ CamelObject parent_object;
+ struct _CamelServicePrivate *priv;
+
+ CamelSession *session;
+ CamelProvider *provider;
+ CamelServiceConnectionStatus status;
+ CamelOperation *connect_op;
+ CamelURL *url;
+};
+
+
+typedef struct {
+ CamelObjectClass parent_class;
+
+ void (*construct) (CamelService *service,
+ CamelSession *session,
+ CamelProvider *provider,
+ CamelURL *url,
+ CamelException *ex);
+
+ gboolean (*connect) (CamelService *service,
+ CamelException *ex);
+ gboolean (*disconnect) (CamelService *service,
+ gboolean clean,
+ CamelException *ex);
+ void (*cancel_connect) (CamelService *service);
+
+ GList * (*query_auth_types) (CamelService *service,
+ CamelException *ex);
+
+ char * (*get_name) (CamelService *service,
+ gboolean brief);
+ char * (*get_path) (CamelService *service);
+
+} CamelServiceClass;
+
+
+/* query_auth_types returns a GList of these */
+typedef struct {
+ char *name; /* user-friendly name */
+ char *description;
+ char *authproto;
+
+ gboolean need_password; /* needs a password to authenticate */
+} CamelServiceAuthType;
+
+
+/* public methods */
+void camel_service_construct (CamelService *service,
+ CamelSession *session,
+ CamelProvider *provider,
+ CamelURL *url,
+ CamelException *ex);
+gboolean camel_service_connect (CamelService *service,
+ CamelException *ex);
+gboolean camel_service_disconnect (CamelService *service,
+ gboolean clean,
+ CamelException *ex);
+void camel_service_cancel_connect (CamelService *service);
+char * camel_service_get_url (CamelService *service);
+char * camel_service_get_name (CamelService *service,
+ gboolean brief);
+char * camel_service_get_path (CamelService *service);
+CamelSession * camel_service_get_session (CamelService *service);
+CamelProvider * camel_service_get_provider (CamelService *service);
+GList * camel_service_query_auth_types (CamelService *service,
+ CamelException *ex);
+
+#ifdef NEED_ADDRINFO
+/* Some of this is copied from GNU's netdb.h
+
+ Copyright (C) 1996-2002, 2003, 2004 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C 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.
+*/
+struct addrinfo {
+ int ai_flags;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ size_t ai_addrlen;
+ struct sockaddr *ai_addr;
+ char *ai_canonname;
+ struct addrinfo *ai_next;
+};
+
+#define AI_CANONNAME 0x0002 /* Request for canonical name. */
+#define AI_NUMERICHOST 0x0004 /* Don't use name resolution. */
+
+/* Error values for `getaddrinfo' function. */
+#define EAI_BADFLAGS -1 /* Invalid value for `ai_flags' field. */
+#define EAI_NONAME -2 /* NAME or SERVICE is unknown. */
+#define EAI_AGAIN -3 /* Temporary failure in name resolution. */
+#define EAI_FAIL -4 /* Non-recoverable failure in name res. */
+#define EAI_NODATA -5 /* No address associated with NAME. */
+#define EAI_FAMILY -6 /* `ai_family' not supported. */
+#define EAI_SOCKTYPE -7 /* `ai_socktype' not supported. */
+#define EAI_SERVICE -8 /* SERVICE not supported for `ai_socktype'. */
+#define EAI_ADDRFAMILY -9 /* Address family for NAME not supported. */
+#define EAI_MEMORY -10 /* Memory allocation failure. */
+#define EAI_SYSTEM -11 /* System error returned in `errno'. */
+#define EAI_OVERFLOW -12 /* Argument buffer overflow. */
+
+#define NI_MAXHOST 1025
+#define NI_MAXSERV 32
+
+#define NI_NUMERICHOST 1 /* Don't try to look up hostname. */
+#define NI_NUMERICSERV 2 /* Don't convert port number to name. */
+#define NI_NOFQDN 4 /* Only return nodename portion. */
+#define NI_NAMEREQD 8 /* Don't return numeric addresses. */
+#define NI_DGRAM 16 /* Look up UDP service rather than TCP. */
+#endif
+
+/* new hostname interfaces */
+struct addrinfo *camel_getaddrinfo(const char *name, const char *service,
+ const struct addrinfo *hints, CamelException *ex);
+void camel_freeaddrinfo(struct addrinfo *host);
+int camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv,
+ int flags, CamelException *ex);
+
+/* Standard Camel function */
+CamelType camel_service_get_type (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_SERVICE_H */
+
diff --git a/camel/camel-smime-context.c b/camel/camel-smime-context.c
new file mode 100644
index 0000000000..80b10081a5
--- /dev/null
+++ b/camel/camel-smime-context.c
@@ -0,0 +1,1054 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation. Portions created by Netscape are
+ * Copyright (C) 1994-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef ENABLE_SMIME
+
+#include "nss.h"
+#include <cms.h>
+#include <cert.h>
+#include <certdb.h>
+#include <pkcs11.h>
+#include <smime.h>
+#include <pkcs11t.h>
+#include <pk11func.h>
+
+#include <errno.h>
+
+#include <camel/camel-exception.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-data-wrapper.h>
+
+#include <camel/camel-mime-part.h>
+#include <camel/camel-multipart-signed.h>
+#include <camel/camel-stream-fs.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-mime-filter-basic.h>
+#include <camel/camel-mime-filter-canon.h>
+
+#include "camel-smime-context.h"
+#include "camel-operation.h"
+
+#define d(x)
+
+struct _CamelSMIMEContextPrivate {
+ CERTCertDBHandle *certdb;
+
+ char *encrypt_key;
+ camel_smime_sign_t sign_mode;
+
+ int password_tries;
+ unsigned int send_encrypt_key_prefs:1;
+};
+
+static CamelCipherContextClass *parent_class = NULL;
+
+/* used for decode content callback, for streaming decode */
+static void
+sm_write_stream(void *arg, const char *buf, unsigned long len)
+{
+ camel_stream_write((CamelStream *)arg, buf, len);
+}
+
+static PK11SymKey *
+sm_decrypt_key(void *arg, SECAlgorithmID *algid)
+{
+ printf("Decrypt key called\n");
+ return (PK11SymKey *)arg;
+}
+
+/**
+ * camel_smime_context_new:
+ * @session: session
+ *
+ * Creates a new sm cipher context object.
+ *
+ * Returns a new sm cipher context object.
+ **/
+CamelCipherContext *
+camel_smime_context_new(CamelSession *session)
+{
+ CamelCipherContext *cipher;
+ CamelSMIMEContext *ctx;
+
+ g_return_val_if_fail(CAMEL_IS_SESSION(session), NULL);
+
+ ctx =(CamelSMIMEContext *) camel_object_new(camel_smime_context_get_type());
+
+ cipher =(CamelCipherContext *) ctx;
+ cipher->session = session;
+ camel_object_ref(session);
+
+ return cipher;
+}
+
+void
+camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key)
+{
+ context->priv->send_encrypt_key_prefs = use;
+ g_free(context->priv->encrypt_key);
+ context->priv->encrypt_key = g_strdup(key);
+}
+
+/* set signing mode, clearsigned multipart/signed or enveloped */
+void
+camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type)
+{
+ context->priv->sign_mode = type;
+}
+
+/* TODO: This is suboptimal, but the only other solution is to pass around NSSCMSMessages */
+guint32
+camel_smime_context_describe_part(CamelSMIMEContext *context, CamelMimePart *part)
+{
+ guint32 flags = 0;
+ CamelContentType *ct;
+ const char *tmp;
+
+ ct = camel_mime_part_get_content_type(part);
+
+ if (camel_content_type_is(ct, "multipart", "signed")) {
+ tmp = camel_content_type_param(ct, "protocol");
+ if (tmp &&
+ (g_ascii_strcasecmp(tmp, ((CamelCipherContext *)context)->sign_protocol) == 0
+ || g_ascii_strcasecmp(tmp, "application/pkcs7-signature") == 0))
+ flags = CAMEL_SMIME_SIGNED;
+ } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
+ CamelStreamMem *istream;
+ NSSCMSMessage *cmsg;
+ NSSCMSDecoderContext *dec;
+
+ /* FIXME: stream this to the decoder incrementally */
+ istream = (CamelStreamMem *)camel_stream_mem_new();
+ camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)part), (CamelStream *)istream);
+ camel_stream_reset((CamelStream *)istream);
+
+ dec = NSS_CMSDecoder_Start(NULL,
+ NULL, NULL,
+ NULL, NULL, /* password callback */
+ NULL, NULL); /* decrypt key callback */
+
+ NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len);
+ camel_object_unref(istream);
+
+ cmsg = NSS_CMSDecoder_Finish(dec);
+ if (cmsg) {
+ if (NSS_CMSMessage_IsSigned(cmsg)) {
+ printf("message is signed\n");
+ flags |= CAMEL_SMIME_SIGNED;
+ }
+
+ if (NSS_CMSMessage_IsEncrypted(cmsg)) {
+ printf("message is encrypted\n");
+ flags |= CAMEL_SMIME_ENCRYPTED;
+ }
+#if 0
+ if (NSS_CMSMessage_ContainsCertsOrCrls(cmsg)) {
+ printf("message contains certs or crls\n");
+ flags |= CAMEL_SMIME_CERTS;
+ }
+#endif
+ NSS_CMSMessage_Destroy(cmsg);
+ } else {
+ printf("Message could not be parsed\n");
+ }
+ }
+
+ return flags;
+}
+
+static const char *
+sm_hash_to_id(CamelCipherContext *context, CamelCipherHash hash)
+{
+ switch(hash) {
+ case CAMEL_CIPHER_HASH_MD5:
+ return "md5";
+ case CAMEL_CIPHER_HASH_SHA1:
+ case CAMEL_CIPHER_HASH_DEFAULT:
+ return "sha1";
+ default:
+ return NULL;
+ }
+}
+
+static CamelCipherHash
+sm_id_to_hash(CamelCipherContext *context, const char *id)
+{
+ if (id) {
+ if (!strcmp(id, "md5"))
+ return CAMEL_CIPHER_HASH_MD5;
+ else if (!strcmp(id, "sha1"))
+ return CAMEL_CIPHER_HASH_SHA1;
+ }
+
+ return CAMEL_CIPHER_HASH_DEFAULT;
+}
+
+static NSSCMSMessage *
+sm_signing_cmsmessage(CamelSMIMEContext *context, const char *nick, SECOidTag hash, int detached, CamelException *ex)
+{
+ struct _CamelSMIMEContextPrivate *p = context->priv;
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSContentInfo *cinfo;
+ NSSCMSSignedData *sigd;
+ NSSCMSSignerInfo *signerinfo;
+ CERTCertificate *cert= NULL, *ekpcert = NULL;
+
+ if ((cert = CERT_FindUserCertByUsage(p->certdb,
+ (char *)nick,
+ certUsageEmailSigner,
+ PR_FALSE,
+ NULL)) == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find certificate for '%s'"), nick);
+ return NULL;
+ }
+
+ cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
+ if (cmsg == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS message"));
+ goto fail;
+ }
+
+ if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS signedData"));
+ goto fail;
+ }
+
+ cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS signedData"));
+ goto fail;
+ }
+
+ /* if !detatched, the contentinfo will alloc a data item for us */
+ cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
+ if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, detached) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS data"));
+ goto fail;
+ }
+
+ signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, hash);
+ if (signerinfo == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS SignerInfo"));
+ goto fail;
+ }
+
+ /* we want the cert chain included for this one */
+ if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find cert chain"));
+ goto fail;
+ }
+
+ /* SMIME RFC says signing time should always be added */
+ if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add CMS SigningTime"));
+ goto fail;
+ }
+
+#if 0
+ /* this can but needn't be added. not sure what general usage is */
+ if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
+ fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n");
+ goto loser;
+ }
+#endif
+
+ /* Check if we need to send along our return encrypt cert, rfc2633 2.5.3 */
+ if (p->send_encrypt_key_prefs) {
+ CERTCertificate *enccert = NULL;
+
+ if (p->encrypt_key) {
+ /* encrypt key has its own nick */
+ if ((ekpcert = CERT_FindUserCertByUsage(
+ p->certdb,
+ p->encrypt_key,
+ certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Encryption cert for '%s' does not exist"), p->encrypt_key);
+ goto fail;
+ }
+ enccert = ekpcert;
+ } else if (CERT_CheckCertUsage(cert, certUsageEmailRecipient) == SECSuccess) {
+ /* encrypt key is signing key */
+ enccert = cert;
+ } else {
+ /* encrypt key uses same nick */
+ if ((ekpcert = CERT_FindUserCertByUsage(
+ p->certdb, (char *)nick,
+ certUsageEmailRecipient, PR_FALSE, NULL)) == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Encryption cert for '%s' does not exist"), nick);
+ goto fail;
+ }
+ enccert = ekpcert;
+ }
+
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add SMIMEEncKeyPrefs attribute"));
+ goto fail;
+ }
+
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, enccert, p->certdb) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add MS SMIMEEncKeyPrefs attribute"));
+ goto fail;
+ }
+
+ if (ekpcert != NULL && NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add encryption certificate"));
+ goto fail;
+ }
+ }
+
+ if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add CMS SignerInfo"));
+ goto fail;
+ }
+
+ if (ekpcert)
+ CERT_DestroyCertificate(ekpcert);
+
+ if (cert)
+ CERT_DestroyCertificate(cert);
+
+ return cmsg;
+fail:
+ if (ekpcert)
+ CERT_DestroyCertificate(ekpcert);
+
+ if (cert)
+ CERT_DestroyCertificate(cert);
+
+ NSS_CMSMessage_Destroy(cmsg);
+
+ return NULL;
+}
+
+static int
+sm_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ int res = -1;
+ NSSCMSMessage *cmsg;
+ CamelStream *ostream, *istream;
+ SECOidTag sechash;
+ NSSCMSEncoderContext *enc;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+
+ switch (hash) {
+ case CAMEL_CIPHER_HASH_SHA1:
+ case CAMEL_CIPHER_HASH_DEFAULT:
+ default:
+ sechash = SEC_OID_SHA1;
+ break;
+ case CAMEL_CIPHER_HASH_MD5:
+ sechash = SEC_OID_MD5;
+ break;
+ }
+
+ cmsg = sm_signing_cmsmessage((CamelSMIMEContext *)context, userid, sechash,
+ ((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN, ex);
+ if (cmsg == NULL)
+ return -1;
+
+ ostream = camel_stream_mem_new();
+
+ /* FIXME: stream this, we stream output at least */
+ istream = camel_stream_mem_new();
+ if (camel_cipher_canonical_to_stream(ipart,
+ CAMEL_MIME_FILTER_CANON_STRIP
+ |CAMEL_MIME_FILTER_CANON_CRLF
+ |CAMEL_MIME_FILTER_CANON_FROM, istream) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not generate signing data: %s"), g_strerror(errno));
+ goto fail;
+ }
+
+ enc = NSS_CMSEncoder_Start(cmsg,
+ sm_write_stream, ostream, /* DER output callback */
+ NULL, NULL, /* destination storage */
+ NULL, NULL, /* password callback */
+ NULL, NULL, /* decrypt key callback */
+ NULL, NULL ); /* detached digests */
+ if (!enc) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create encoder context"));
+ goto fail;
+ }
+
+ if (NSS_CMSEncoder_Update(enc, ((CamelStreamMem *)istream)->buffer->data, ((CamelStreamMem *)istream)->buffer->len) != SECSuccess) {
+ NSS_CMSEncoder_Cancel(enc);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to add data to CMS encoder"));
+ goto fail;
+ }
+
+ if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to encode data"));
+ goto fail;
+ }
+
+ res = 0;
+
+ dw = camel_data_wrapper_new();
+ camel_stream_reset(ostream);
+ camel_data_wrapper_construct_from_stream(dw, ostream);
+ dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
+
+ if (((CamelSMIMEContext *)context)->priv->sign_mode == CAMEL_SMIME_SIGN_CLEARSIGN) {
+ CamelMultipartSigned *mps;
+ CamelMimePart *sigpart;
+
+ sigpart = camel_mime_part_new();
+ ct = camel_content_type_new("application", "x-pkcs7-signature");
+ camel_content_type_set_param(ct, "name", "smime.p7s");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)sigpart, dw);
+
+ camel_mime_part_set_filename(sigpart, "smime.p7s");
+ camel_mime_part_set_disposition(sigpart, "attachment");
+ camel_mime_part_set_encoding(sigpart, CAMEL_TRANSFER_ENCODING_BASE64);
+
+ mps = camel_multipart_signed_new();
+ ct = camel_content_type_new("multipart", "signed");
+ camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
+ camel_content_type_set_param(ct, "protocol", context->sign_protocol);
+ camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
+ camel_content_type_unref(ct);
+ camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
+
+ mps->signature = sigpart;
+ mps->contentraw = istream;
+ camel_stream_reset(istream);
+ camel_object_ref(istream);
+
+ camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
+ } else {
+ ct = camel_content_type_new("application", "x-pkcs7-mime");
+ camel_content_type_set_param(ct, "name", "smime.p7m");
+ camel_content_type_set_param(ct, "smime-type", "signed-data");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)opart, dw);
+
+ camel_mime_part_set_filename(opart, "smime.p7m");
+ camel_mime_part_set_description(opart, "S/MIME Signed Message");
+ camel_mime_part_set_disposition(opart, "attachment");
+ camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
+ }
+
+ camel_object_unref(dw);
+fail:
+ camel_object_unref(ostream);
+ camel_object_unref(istream);
+
+ return res;
+}
+
+static const char *
+sm_status_description(NSSCMSVerificationStatus status)
+{
+ /* could use this but then we can't control i18n? */
+ /*NSS_CMSUtil_VerificationStatusToString(status));*/
+
+ switch(status) {
+ case NSSCMSVS_Unverified:
+ default:
+ return _("Unverified");
+ case NSSCMSVS_GoodSignature:
+ return _("Good signature");
+ case NSSCMSVS_BadSignature:
+ return _("Bad signature");
+ case NSSCMSVS_DigestMismatch:
+ return _("Content tampered with or altered in transit");
+ case NSSCMSVS_SigningCertNotFound:
+ return _("Signing certificate not found");
+ case NSSCMSVS_SigningCertNotTrusted:
+ return _("Signing certificate not trusted");
+ case NSSCMSVS_SignatureAlgorithmUnknown:
+ return _("Signature algorithm unknown");
+ case NSSCMSVS_SignatureAlgorithmUnsupported:
+ return _("Signature algorithm unsupported");
+ case NSSCMSVS_MalformedSignature:
+ return _("Malformed signature");
+ case NSSCMSVS_ProcessingError:
+ return _("Processing error");
+ }
+}
+
+static CamelCipherValidity *
+sm_verify_cmsg(CamelCipherContext *context, NSSCMSMessage *cmsg, CamelStream *extstream, CamelException *ex)
+{
+ struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
+ NSSCMSSignedData *sigd = NULL;
+ NSSCMSEnvelopedData *envd;
+ NSSCMSEncryptedData *encd;
+ SECAlgorithmID **digestalgs;
+ NSSCMSDigestContext *digcx;
+ int count, i, nsigners, j;
+ SECItem **digests;
+ PLArenaPool *poolp = NULL;
+ CamelStreamMem *mem;
+ NSSCMSVerificationStatus status;
+ CamelCipherValidity *valid;
+ GString *description;
+
+ description = g_string_new("");
+ valid = camel_cipher_validity_new();
+ camel_cipher_validity_set_valid(valid, TRUE);
+ status = NSSCMSVS_Unverified;
+
+ /* NB: this probably needs to go into a decoding routine that can be used for processing
+ enveloped data too */
+ count = NSS_CMSMessage_ContentLevelCount(cmsg);
+ for (i = 0; i < count; i++) {
+ NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cmsg, i);
+ SECOidTag typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);
+
+ switch (typetag) {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
+ if (sigd == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("No signedData in signature"));
+ goto fail;
+ }
+
+ /* need to build digests of the content */
+ if (!NSS_CMSSignedData_HasDigests(sigd)) {
+ if (extstream == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Digests missing from enveloped data"));
+ goto fail;
+ }
+
+ if ((poolp = PORT_NewArena(1024)) == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, g_strerror (ENOMEM));
+ goto fail;
+ }
+
+ digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
+
+ digcx = NSS_CMSDigestContext_StartMultiple(digestalgs);
+ if (digcx == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot calculate digests"));
+ goto fail;
+ }
+
+ mem = (CamelStreamMem *)camel_stream_mem_new();
+ camel_stream_write_to_stream(extstream, (CamelStream *)mem);
+ NSS_CMSDigestContext_Update(digcx, mem->buffer->data, mem->buffer->len);
+ camel_object_unref(mem);
+
+ if (NSS_CMSDigestContext_FinishMultiple(digcx, poolp, &digests) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot calculate digests"));
+ goto fail;
+ }
+
+ if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot set message digests"));
+ goto fail;
+ }
+
+ PORT_FreeArena(poolp, PR_FALSE);
+ poolp = NULL;
+ }
+
+ /* import all certificates present */
+ if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailSigner, PR_TRUE) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Certificate import failed"));
+ goto fail;
+ }
+
+ if (NSS_CMSSignedData_ImportCerts(sigd, p->certdb, certUsageEmailRecipient, PR_TRUE) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Certificate import failed"));
+ goto fail;
+ }
+
+ /* check for certs-only message */
+ nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
+ if (nsigners == 0) {
+ /* already imported certs above, not sure what usage we should use here or if this isn't handled above */
+ if (NSS_CMSSignedData_VerifyCertsOnly(sigd, p->certdb, certUsageEmailSigner) != SECSuccess) {
+ g_string_printf(description, _("Certificate only message, cannot verify certificates"));
+ } else {
+ status = NSSCMSVS_GoodSignature;
+ g_string_printf(description, _("Certificate only message, certificates imported and verified"));
+ }
+ } else {
+ if (!NSS_CMSSignedData_HasDigests(sigd)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find signature digests"));
+ goto fail;
+ }
+
+ for (j = 0; j < nsigners; j++) {
+ NSSCMSSignerInfo *si;
+ char *cn, *em;
+
+ si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
+ NSS_CMSSignedData_VerifySignerInfo(sigd, j, p->certdb, certUsageEmailSigner);
+
+ status = NSS_CMSSignerInfo_GetVerificationStatus(si);
+
+ cn = NSS_CMSSignerInfo_GetSignerCommonName(si);
+ em = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
+
+ g_string_append_printf(description, _("Signer: %s <%s>: %s\n"),
+ cn?cn:"<unknown>", em?em:"<unknown>",
+ sm_status_description(status));
+
+ camel_cipher_validity_add_certinfo(valid, CAMEL_CIPHER_VALIDITY_SIGN, cn, em);
+
+ if (cn)
+ PORT_Free(cn);
+ if (em)
+ PORT_Free(em);
+
+ if (status != NSSCMSVS_GoodSignature)
+ camel_cipher_validity_set_valid(valid, FALSE);
+ }
+ }
+ break;
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
+ break;
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
+ break;
+ case SEC_OID_PKCS7_DATA:
+ break;
+ default:
+ break;
+ }
+ }
+
+ camel_cipher_validity_set_valid(valid, status == NSSCMSVS_GoodSignature);
+ camel_cipher_validity_set_description(valid, description->str);
+ g_string_free(description, TRUE);
+
+ return valid;
+
+fail:
+ camel_cipher_validity_free(valid);
+ g_string_free(description, TRUE);
+
+ return NULL;
+}
+
+static CamelCipherValidity *
+sm_verify(CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
+{
+ NSSCMSDecoderContext *dec;
+ NSSCMSMessage *cmsg;
+ CamelStreamMem *mem;
+ CamelStream *constream;
+ CamelCipherValidity *valid = NULL;
+ CamelContentType *ct;
+ const char *tmp;
+ CamelMimePart *sigpart;
+ CamelDataWrapper *dw;
+
+ dw = camel_medium_get_content_object((CamelMedium *)ipart);
+ ct = dw->mime_type;
+
+ /* FIXME: we should stream this to the decoder */
+ mem = (CamelStreamMem *)camel_stream_mem_new();
+
+ if (camel_content_type_is(ct, "multipart", "signed")) {
+ CamelMultipart *mps = (CamelMultipart *)dw;
+
+ tmp = camel_content_type_param(ct, "protocol");
+ if (!CAMEL_IS_MULTIPART_SIGNED(mps)
+ || tmp == NULL
+ || (g_ascii_strcasecmp(tmp, context->sign_protocol) != 0
+ && g_ascii_strcasecmp(tmp, "application/pkcs7-signature") != 0)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ goto fail;
+ }
+
+ constream = camel_multipart_signed_get_content_stream((CamelMultipartSigned *)mps, ex);
+ if (constream == NULL)
+ goto fail;
+
+ sigpart = camel_multipart_get_part(mps, CAMEL_MULTIPART_SIGNED_SIGNATURE);
+ if (sigpart == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ goto fail;
+ }
+ } else if (camel_content_type_is(ct, "application", "x-pkcs7-mime")) {
+ sigpart = ipart;
+ } else {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ goto fail;
+ }
+
+ dec = NSS_CMSDecoder_Start(NULL,
+ NULL, NULL, /* content callback */
+ NULL, NULL, /* password callback */
+ NULL, NULL); /* decrypt key callback */
+
+ camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)sigpart), (CamelStream *)mem);
+ (void)NSS_CMSDecoder_Update(dec, mem->buffer->data, mem->buffer->len);
+ cmsg = NSS_CMSDecoder_Finish(dec);
+ if (cmsg == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Decoder failed"));
+ goto fail;
+ }
+
+ valid = sm_verify_cmsg(context, cmsg, constream, ex);
+
+ NSS_CMSMessage_Destroy(cmsg);
+fail:
+ camel_object_unref(mem);
+ if (constream)
+ camel_object_unref(constream);
+
+ return valid;
+}
+
+static int
+sm_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ struct _CamelSMIMEContextPrivate *p = ((CamelSMIMEContext *)context)->priv;
+ /*NSSCMSRecipientInfo **recipient_infos;*/
+ CERTCertificate **recipient_certs = NULL;
+ NSSCMSContentInfo *cinfo;
+ PK11SymKey *bulkkey = NULL;
+ SECOidTag bulkalgtag;
+ int bulkkeysize, i;
+ CK_MECHANISM_TYPE type;
+ PK11SlotInfo *slot;
+ PLArenaPool *poolp;
+ NSSCMSMessage *cmsg = NULL;
+ NSSCMSEnvelopedData *envd;
+ NSSCMSEncoderContext *enc = NULL;
+ CamelStreamMem *mem;
+ CamelStream *ostream = NULL;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+
+ poolp = PORT_NewArena(1024);
+ if (poolp == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, g_strerror (ENOMEM));
+ return -1;
+ }
+
+ /* Lookup all recipients certs, for later working */
+ recipient_certs = (CERTCertificate **)PORT_ArenaZAlloc(poolp, sizeof(*recipient_certs[0])*(recipients->len + 1));
+ if (recipient_certs == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, g_strerror (ENOMEM));
+ goto fail;
+ }
+
+ for (i=0;i<recipients->len;i++) {
+ recipient_certs[i] = CERT_FindCertByNicknameOrEmailAddr(p->certdb, recipients->pdata[i]);
+ if (recipient_certs[i] == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find certificate for `%s'"), recipients->pdata[i]);
+ goto fail;
+ }
+ }
+
+ /* Find a common algorithm, probably 3DES anyway ... */
+ if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipient_certs, &bulkalgtag, &bulkkeysize) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot find common bulk encryption algorithm"));
+ goto fail;
+ }
+
+ /* Generate a new bulk key based on the common algorithm - expensive */
+ type = PK11_AlgtagToMechanism(bulkalgtag);
+ slot = PK11_GetBestSlot(type, context);
+ if (slot == NULL) {
+ /* PORT_GetError(); ?? */
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot allocate slot for encryption bulk key"));
+ goto fail;
+ }
+
+ bulkkey = PK11_KeyGen(slot, type, NULL, bulkkeysize/8, context);
+ PK11_FreeSlot(slot);
+
+ /* Now we can start building the message */
+ /* msg->envelopedData->data */
+ cmsg = NSS_CMSMessage_Create(NULL);
+ if (cmsg == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS Message"));
+ goto fail;
+ }
+
+ envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, bulkkeysize);
+ if (envd == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS EnvelopedData"));
+ goto fail;
+ }
+
+ cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
+ if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS EnvelopedData"));
+ goto fail;
+ }
+
+ cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
+ if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot attach CMS data object"));
+ goto fail;
+ }
+
+ /* add recipient certs */
+ for (i=0;recipient_certs[i];i++) {
+ NSSCMSRecipientInfo *ri = NSS_CMSRecipientInfo_Create(cmsg, recipient_certs[i]);
+
+ if (ri == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create CMS RecipientInfo"));
+ goto fail;
+ }
+
+ if (NSS_CMSEnvelopedData_AddRecipient(envd, ri) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot add CMS RecipientInfo"));
+ goto fail;
+ }
+ }
+
+ /* dump it out */
+ ostream = camel_stream_mem_new();
+ enc = NSS_CMSEncoder_Start(cmsg,
+ sm_write_stream, ostream,
+ NULL, NULL,
+ NULL, NULL,
+ sm_decrypt_key, bulkkey,
+ NULL, NULL);
+ if (enc == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create encoder context"));
+ goto fail;
+ }
+
+ /* FIXME: Stream the input */
+ /* FIXME: Canonicalise the input? */
+ mem = (CamelStreamMem *)camel_stream_mem_new();
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)ipart, (CamelStream *)mem);
+ if (NSS_CMSEncoder_Update(enc, mem->buffer->data, mem->buffer->len) != SECSuccess) {
+ NSS_CMSEncoder_Cancel(enc);
+ camel_object_unref(mem);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to add data to encoder"));
+ goto fail;
+ }
+ camel_object_unref(mem);
+
+ if (NSS_CMSEncoder_Finish(enc) != SECSuccess) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to encode data"));
+ goto fail;
+ }
+
+ PK11_FreeSymKey(bulkkey);
+ NSS_CMSMessage_Destroy(cmsg);
+ for (i=0;recipient_certs[i];i++)
+ CERT_DestroyCertificate(recipient_certs[i]);
+ PORT_FreeArena(poolp, PR_FALSE);
+
+ dw = camel_data_wrapper_new();
+ camel_data_wrapper_construct_from_stream(dw, ostream);
+ camel_object_unref(ostream);
+ dw->encoding = CAMEL_TRANSFER_ENCODING_BINARY;
+
+ ct = camel_content_type_new("application", "x-pkcs7-mime");
+ camel_content_type_set_param(ct, "name", "smime.p7m");
+ camel_content_type_set_param(ct, "smime-type", "enveloped-data");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)opart, dw);
+ camel_object_unref(dw);
+
+ camel_mime_part_set_disposition(opart, "attachment");
+ camel_mime_part_set_filename(opart, "smime.p7m");
+ camel_mime_part_set_description(opart, "S/MIME Encrypted Message");
+ camel_mime_part_set_encoding(opart, CAMEL_TRANSFER_ENCODING_BASE64);
+
+ return 0;
+
+fail:
+ if (ostream)
+ camel_object_unref(ostream);
+ if (cmsg)
+ NSS_CMSMessage_Destroy(cmsg);
+ if (bulkkey)
+ PK11_FreeSymKey(bulkkey);
+
+ if (recipient_certs) {
+ for (i=0;recipient_certs[i];i++)
+ CERT_DestroyCertificate(recipient_certs[i]);
+ }
+
+ PORT_FreeArena(poolp, PR_FALSE);
+
+ return -1;
+}
+
+static CamelCipherValidity *
+sm_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ NSSCMSDecoderContext *dec;
+ NSSCMSMessage *cmsg;
+ CamelStreamMem *istream;
+ CamelStream *ostream;
+ CamelCipherValidity *valid = NULL;
+
+ /* FIXME: This assumes the content is only encrypted. Perhaps its ok for
+ this api to do this ... */
+
+ ostream = camel_stream_mem_new();
+ camel_stream_mem_set_secure((CamelStreamMem *)ostream);
+
+ /* FIXME: stream this to the decoder incrementally */
+ istream = (CamelStreamMem *)camel_stream_mem_new();
+ camel_data_wrapper_decode_to_stream(camel_medium_get_content_object((CamelMedium *)ipart), (CamelStream *)istream);
+ camel_stream_reset((CamelStream *)istream);
+
+ dec = NSS_CMSDecoder_Start(NULL,
+ sm_write_stream, ostream, /* content callback */
+ NULL, NULL,
+ NULL, NULL); /* decrypt key callback */
+
+ if (NSS_CMSDecoder_Update(dec, istream->buffer->data, istream->buffer->len) != SECSuccess) {
+ printf("decoder update failed\n");
+ }
+ camel_object_unref(istream);
+
+ cmsg = NSS_CMSDecoder_Finish(dec);
+ if (cmsg == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Decoder failed, error %d"), PORT_GetError());
+ goto fail;
+ }
+
+#if 0
+ /* not sure if we really care about this? */
+ if (!NSS_CMSMessage_IsEncrypted(cmsg)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("S/MIME Decrypt: No encrypted content found"));
+ NSS_CMSMessage_Destroy(cmsg);
+ goto fail;
+ }
+#endif
+
+ camel_stream_reset(ostream);
+ camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream);
+
+ if (NSS_CMSMessage_IsSigned(cmsg)) {
+ valid = sm_verify_cmsg(context, cmsg, NULL, ex);
+ } else {
+ valid = camel_cipher_validity_new();
+ valid->encrypt.description = g_strdup(_("Encrypted content"));
+ valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
+ }
+
+ NSS_CMSMessage_Destroy(cmsg);
+fail:
+ camel_object_unref(ostream);
+
+ return valid;
+}
+
+static int
+sm_import_keys(CamelCipherContext *context, CamelStream *istream, CamelException *ex)
+{
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("import keys: unimplemented"));
+
+ return -1;
+}
+
+static int
+sm_export_keys(CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
+{
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("export keys: unimplemented"));
+
+ return -1;
+}
+
+/* ********************************************************************** */
+
+static void
+camel_smime_context_class_init(CamelSMIMEContextClass *klass)
+{
+ CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS(klass);
+
+ parent_class = CAMEL_CIPHER_CONTEXT_CLASS(camel_type_get_global_classfuncs(camel_cipher_context_get_type()));
+
+ cipher_class->hash_to_id = sm_hash_to_id;
+ cipher_class->id_to_hash = sm_id_to_hash;
+ cipher_class->sign = sm_sign;
+ cipher_class->verify = sm_verify;
+ cipher_class->encrypt = sm_encrypt;
+ cipher_class->decrypt = sm_decrypt;
+ cipher_class->import_keys = sm_import_keys;
+ cipher_class->export_keys = sm_export_keys;
+}
+
+static void
+camel_smime_context_init(CamelSMIMEContext *context)
+{
+ CamelCipherContext *cipher =(CamelCipherContext *) context;
+
+ cipher->sign_protocol = "application/x-pkcs7-signature";
+ cipher->encrypt_protocol = "application/x-pkcs7-mime";
+ cipher->key_protocol = "application/x-pkcs7-signature";
+
+ context->priv = g_malloc0(sizeof(*context->priv));
+ context->priv->certdb = CERT_GetDefaultCertDB();
+ context->priv->sign_mode = CAMEL_SMIME_SIGN_CLEARSIGN;
+ context->priv->password_tries = 0;
+}
+
+static void
+camel_smime_context_finalise(CamelObject *object)
+{
+ CamelSMIMEContext *context = (CamelSMIMEContext *)object;
+
+ /* FIXME: do we have to free the certdb? */
+
+ g_free(context->priv);
+}
+
+CamelType
+camel_smime_context_get_type(void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register(camel_cipher_context_get_type(),
+ "CamelSMIMEContext",
+ sizeof(CamelSMIMEContext),
+ sizeof(CamelSMIMEContextClass),
+ (CamelObjectClassInitFunc) camel_smime_context_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_smime_context_init,
+ (CamelObjectFinalizeFunc) camel_smime_context_finalise);
+ }
+
+ return type;
+}
+
+#endif /* ENABLE_SMIME */
diff --git a/camel/camel-store.c b/camel/camel-store.c
new file mode 100644
index 0000000000..e523d16443
--- /dev/null
+++ b/camel/camel-store.c
@@ -0,0 +1,1205 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-store.c : Abstract class for an email store */
+
+/*
+ * Authors:
+ * Bertrand Guiheneuf <bertrand@helixcode.com>
+ * Dan Winship <danw@ximian.com>
+ *
+ * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "camel-debug.h"
+
+#include "camel-session.h"
+#include "camel-store.h"
+#include "camel-folder.h"
+#include "camel-vtrash-folder.h"
+#include "camel-exception.h"
+#include "camel-private.h"
+
+#define d(x)
+#define w(x)
+
+static CamelServiceClass *parent_class = NULL;
+
+/* Returns the class for a CamelStore */
+#define CS_CLASS(so) ((CamelStoreClass *)((CamelObject *)(so))->klass)
+
+static CamelFolder *get_folder (CamelStore *store, const char *folder_name,
+ guint32 flags, CamelException *ex);
+static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
+
+static CamelFolder *get_trash (CamelStore *store, CamelException *ex);
+static CamelFolder *get_junk (CamelStore *store, CamelException *ex);
+
+static CamelFolderInfo *create_folder (CamelStore *store,
+ const char *parent_name,
+ const char *folder_name,
+ CamelException *ex);
+static void delete_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex);
+static void rename_folder (CamelStore *store, const char *old_name,
+ const char *new_name, CamelException *ex);
+
+static void store_sync (CamelStore *store, int expunge, CamelException *ex);
+static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top,
+ guint32 flags, CamelException *ex);
+static void free_folder_info (CamelStore *store, CamelFolderInfo *tree);
+
+static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
+static void subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+
+static void noop (CamelStore *store, CamelException *ex);
+
+static void construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+
+static int store_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
+static int store_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
+
+static void
+camel_store_class_init (CamelStoreClass *camel_store_class)
+{
+ CamelObjectClass *camel_object_class = CAMEL_OBJECT_CLASS (camel_store_class);
+ CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_store_class);
+
+ parent_class = CAMEL_SERVICE_CLASS (camel_type_get_global_classfuncs (camel_service_get_type ()));
+
+ /* virtual method definition */
+ camel_store_class->hash_folder_name = g_str_hash;
+ camel_store_class->compare_folder_name = g_str_equal;
+ camel_store_class->get_folder = get_folder;
+ camel_store_class->get_inbox = get_inbox;
+ camel_store_class->get_trash = get_trash;
+ camel_store_class->get_junk = get_junk;
+ camel_store_class->create_folder = create_folder;
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = rename_folder;
+ camel_store_class->sync = store_sync;
+ camel_store_class->get_folder_info = get_folder_info;
+ camel_store_class->free_folder_info = free_folder_info;
+ camel_store_class->folder_subscribed = folder_subscribed;
+ camel_store_class->subscribe_folder = subscribe_folder;
+ camel_store_class->unsubscribe_folder = unsubscribe_folder;
+ camel_store_class->noop = noop;
+
+ /* virtual method overload */
+ camel_service_class->construct = construct;
+
+ camel_object_class->setv = store_setv;
+ camel_object_class->getv = store_getv;
+
+ camel_object_class_add_event(camel_object_class, "folder_opened", NULL);
+ camel_object_class_add_event(camel_object_class, "folder_created", NULL);
+ camel_object_class_add_event(camel_object_class, "folder_deleted", NULL);
+ camel_object_class_add_event(camel_object_class, "folder_renamed", NULL);
+ camel_object_class_add_event(camel_object_class, "folder_subscribed", NULL);
+ camel_object_class_add_event(camel_object_class, "folder_unsubscribed", NULL);
+}
+
+static void
+camel_store_init (void *o)
+{
+ CamelStore *store = o;
+ CamelStoreClass *store_class = (CamelStoreClass *)CAMEL_OBJECT_GET_CLASS (o);
+
+ if (store_class->hash_folder_name) {
+ store->folders = camel_object_bag_new(store_class->hash_folder_name,
+ store_class->compare_folder_name,
+ (CamelCopyFunc)g_strdup, g_free);
+ } else
+ store->folders = NULL;
+
+ /* set vtrash and vjunk on by default */
+ store->flags = CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK;
+
+ store->priv = g_malloc0 (sizeof (*store->priv));
+ store->priv->folder_lock = e_mutex_new (E_MUTEX_REC);
+}
+
+static void
+camel_store_finalize (CamelObject *object)
+{
+ CamelStore *store = CAMEL_STORE (object);
+
+ if (store->folders)
+ camel_object_bag_destroy(store->folders);
+
+ e_mutex_destroy (store->priv->folder_lock);
+
+ g_free (store->priv);
+}
+
+
+CamelType
+camel_store_get_type (void)
+{
+ static CamelType camel_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_store_type == CAMEL_INVALID_TYPE) {
+ camel_store_type = camel_type_register (CAMEL_SERVICE_TYPE, "CamelStore",
+ sizeof (CamelStore),
+ sizeof (CamelStoreClass),
+ (CamelObjectClassInitFunc) camel_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_store_init,
+ (CamelObjectFinalizeFunc) camel_store_finalize );
+ }
+
+ return camel_store_type;
+}
+
+static int
+store_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+ /* CamelStore doesn't currently have anything to set */
+ return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
+}
+
+static int
+store_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ /* CamelStore doesn't currently have anything to get */
+ return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
+}
+
+static void
+construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex)
+{
+ CamelStore *store = CAMEL_STORE(service);
+
+ parent_class->construct(service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ if (camel_url_get_param(url, "filter"))
+ store->flags |= CAMEL_STORE_FILTER_INBOX;
+}
+
+static CamelFolder *
+get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ w(g_warning ("CamelStore::get_folder not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_INVALID,
+ _("Cannot get folder: Invalid operation on this store"));
+
+ return NULL;
+}
+
+/**
+ * camel_store_get_folder: Return the folder corresponding to a path.
+ * @store: a CamelStore
+ * @folder_name: name of the folder to get
+ * @flags: folder flags (create, save body index, etc)
+ * @ex: a CamelException
+ *
+ * Return value: the folder corresponding to the path @folder_name.
+ **/
+CamelFolder *
+camel_store_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelFolder *folder = NULL;
+
+ g_return_val_if_fail (folder_name != NULL, NULL);
+
+ /* O_EXCL doesn't make sense if we aren't requesting to also create the folder if it doesn't exist */
+ if (!(flags & CAMEL_STORE_FOLDER_CREATE))
+ flags &= ~CAMEL_STORE_FOLDER_EXCL;
+
+ if (store->folders) {
+ /* Try cache first. */
+ folder = camel_object_bag_reserve(store->folders, folder_name);
+ if (folder && (flags & CAMEL_STORE_FOLDER_EXCL)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': folder exists"),
+ folder_name);
+
+ camel_object_unref (folder);
+ return NULL;
+ }
+ }
+
+ if (!folder) {
+ if ((store->flags & CAMEL_STORE_VTRASH) && strcmp(folder_name, CAMEL_VTRASH_NAME) == 0) {
+ folder = CS_CLASS(store)->get_trash(store, ex);
+ } else if ((store->flags & CAMEL_STORE_VJUNK) && strcmp(folder_name, CAMEL_VJUNK_NAME) == 0) {
+ folder = CS_CLASS(store)->get_junk(store, ex);
+ } else {
+ folder = CS_CLASS (store)->get_folder(store, folder_name, flags, ex);
+ if (folder) {
+ CamelVeeFolder *vfolder;
+
+ if ((store->flags & CAMEL_STORE_VTRASH)
+ && (vfolder = camel_object_bag_get(store->folders, CAMEL_VTRASH_NAME))) {
+ camel_vee_folder_add_folder(vfolder, folder);
+ camel_object_unref(vfolder);
+ }
+
+ if ((store->flags & CAMEL_STORE_VJUNK)
+ && (vfolder = camel_object_bag_get(store->folders, CAMEL_VJUNK_NAME))) {
+ camel_vee_folder_add_folder(vfolder, folder);
+ camel_object_unref(vfolder);
+ }
+ }
+ }
+
+ if (store->folders) {
+ if (folder)
+ camel_object_bag_add(store->folders, folder_name, folder);
+ else
+ camel_object_bag_abort(store->folders, folder_name);
+ }
+
+ if (folder)
+ camel_object_trigger_event(store, "folder_opened", folder);
+ }
+
+ return folder;
+}
+
+static CamelFolderInfo *
+create_folder (CamelStore *store, const char *parent_name,
+ const char *folder_name, CamelException *ex)
+{
+ w(g_warning ("CamelStore::create_folder not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_INVALID,
+ _("Cannot create folder: Invalid operation on this store"));
+
+ return NULL;
+}
+
+/**
+ * camel_store_create_folder:
+ * @store: a CamelStore
+ * @parent_name: name of the new folder's parent, or %NULL
+ * @folder_name: name of the folder to create
+ * @ex: a CamelException
+ *
+ * Creates a new folder as a child of an existing folder.
+ * @parent_name can be %NULL to create a new top-level folder.
+ *
+ * Return value: info about the created folder, which the caller must
+ * free with camel_store_free_folder_info().
+ **/
+CamelFolderInfo *
+camel_store_create_folder (CamelStore *store, const char *parent_name,
+ const char *folder_name, CamelException *ex)
+{
+ CamelFolderInfo *fi;
+
+ if ((parent_name == NULL || parent_name[0] == 0)
+ && (((store->flags & CAMEL_STORE_VTRASH) && strcmp(folder_name, CAMEL_VTRASH_NAME) == 0)
+ || ((store->flags & CAMEL_STORE_VJUNK) && strcmp(folder_name, CAMEL_VJUNK_NAME) == 0))) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_INVALID,
+ _("Cannot create folder: %s: folder exists"), folder_name);
+ return NULL;
+ }
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+ fi = CS_CLASS (store)->create_folder (store, parent_name, folder_name, ex);
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+
+ return fi;
+}
+
+/* deletes folder/removes it from the folder cache, if it's there */
+static void
+cs_delete_cached_folder(CamelStore *store, const char *folder_name)
+{
+ CamelFolder *folder;
+
+ if (store->folders
+ && (folder = camel_object_bag_get(store->folders, folder_name))) {
+ CamelVeeFolder *vfolder;
+
+ if ((store->flags & CAMEL_STORE_VTRASH)
+ && (vfolder = camel_object_bag_get(store->folders, CAMEL_VTRASH_NAME))) {
+ camel_vee_folder_remove_folder(vfolder, folder);
+ camel_object_unref(vfolder);
+ }
+
+ if ((store->flags & CAMEL_STORE_VJUNK)
+ && (vfolder = camel_object_bag_get(store->folders, CAMEL_VJUNK_NAME))) {
+ camel_vee_folder_remove_folder(vfolder, folder);
+ camel_object_unref(vfolder);
+ }
+
+ camel_folder_delete(folder);
+
+ camel_object_bag_remove(store->folders, folder);
+ camel_object_unref(folder);
+ }
+}
+
+static void
+delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ w(g_warning ("CamelStore::delete_folder not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+}
+
+/**
+ * camel_store_delete_folder: Delete the folder corresponding to a path.
+ * @store: a CamelStore
+ * @folder_name: name of the folder to delete
+ * @ex: a CamelException
+ *
+ * Deletes the named folder. The folder must be empty.
+ **/
+void
+camel_store_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelException local;
+
+ /* TODO: should probably be a parameter/bit on the storeinfo */
+ if (((store->flags & CAMEL_STORE_VTRASH) && strcmp(folder_name, CAMEL_VTRASH_NAME) == 0)
+ || ((store->flags & CAMEL_STORE_VJUNK) && strcmp(folder_name, CAMEL_VJUNK_NAME) == 0)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot delete folder: %s: Invalid operation"), folder_name);
+ return;
+ }
+
+ camel_exception_init(&local);
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+
+ CS_CLASS(store)->delete_folder(store, folder_name, &local);
+
+ if (!camel_exception_is_set(&local))
+ cs_delete_cached_folder(store, folder_name);
+ else
+ camel_exception_xfer(ex, &local);
+
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+}
+
+static void
+rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
+{
+ w(g_warning ("CamelStore::rename_folder not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+}
+
+/**
+ * camel_store_rename_folder:
+ * @store: a CamelStore
+ * @old_name: the current name of the folder
+ * @new_name: the new name of the folder
+ * @ex: a CamelException
+ *
+ * Rename a named folder to a new name.
+ **/
+void
+camel_store_rename_folder (CamelStore *store, const char *old_namein, const char *new_name, CamelException *ex)
+{
+ CamelFolder *folder;
+ int i, oldlen, namelen;
+ GPtrArray *folders;
+ char *old_name;
+
+ d(printf("store rename folder %s '%s' '%s'\n", ((CamelService *)store)->url->protocol, old_name, new_name));
+
+ if (strcmp(old_namein, new_name) == 0)
+ return;
+
+ if (((store->flags & CAMEL_STORE_VTRASH) && strcmp(old_namein, CAMEL_VTRASH_NAME) == 0)
+ || ((store->flags & CAMEL_STORE_VJUNK) && strcmp(old_namein, CAMEL_VJUNK_NAME) == 0)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot rename folder: %s: Invalid operation"), old_namein);
+ return;
+ }
+
+ /* need to save this, since old_namein might be folder->full_name, which could go away */
+ old_name = g_strdup(old_namein);
+ oldlen = strlen(old_name);
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+
+ /* If the folder is open (or any subfolders of the open folder)
+ We need to rename them atomically with renaming the actual folder path */
+ if (store->folders) {
+ folders = camel_object_bag_list(store->folders);
+ for (i=0;i<folders->len;i++) {
+ folder = folders->pdata[i];
+ namelen = strlen(folder->full_name);
+ if ((namelen == oldlen &&
+ strcmp(folder->full_name, old_name) == 0)
+ || ((namelen > oldlen)
+ && strncmp(folder->full_name, old_name, oldlen) == 0
+ && folder->full_name[oldlen] == '/')) {
+ d(printf("Found subfolder of '%s' == '%s'\n", old_name, folder->full_name));
+ CAMEL_FOLDER_LOCK(folder, lock);
+ } else {
+ g_ptr_array_remove_index_fast(folders, i);
+ i--;
+ camel_object_unref(folder);
+ }
+ }
+ }
+
+ /* Now try the real rename (will emit renamed event) */
+ CS_CLASS (store)->rename_folder (store, old_name, new_name, ex);
+
+ /* If it worked, update all open folders/unlock them */
+ if (!camel_exception_is_set(ex)) {
+ guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
+ CamelRenameInfo reninfo;
+
+ for (i=0;i<folders->len;i++) {
+ char *new;
+
+ folder = folders->pdata[i];
+
+ new = g_strdup_printf("%s%s", new_name, folder->full_name+strlen(old_name));
+ camel_object_bag_rekey(store->folders, folder, new);
+ camel_folder_rename(folder, new);
+ g_free(new);
+
+ CAMEL_FOLDER_UNLOCK(folder, lock);
+ camel_object_unref(folder);
+ }
+
+ /* Emit renamed signal */
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
+
+ reninfo.old_base = (char *)old_name;
+ reninfo.new = ((CamelStoreClass *)((CamelObject *)store)->klass)->get_folder_info(store, new_name, flags, ex);
+ if (reninfo.new != NULL) {
+ camel_object_trigger_event (store, "folder_renamed", &reninfo);
+ ((CamelStoreClass *)((CamelObject *)store)->klass)->free_folder_info(store, reninfo.new);
+ }
+ } else {
+ /* Failed, just unlock our folders for re-use */
+ for (i=0;i<folders->len;i++) {
+ folder = folders->pdata[i];
+ CAMEL_FOLDER_UNLOCK(folder, lock);
+ camel_object_unref(folder);
+ }
+ }
+
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+
+ g_ptr_array_free(folders, TRUE);
+ g_free(old_name);
+}
+
+
+static CamelFolder *
+get_inbox (CamelStore *store, CamelException *ex)
+{
+ /* Default: assume the inbox's name is "inbox"
+ * and open with default flags.
+ */
+ return CS_CLASS (store)->get_folder (store, "inbox", 0, ex);
+}
+
+/**
+ * camel_store_get_inbox:
+ * @store: a CamelStore
+ * @ex: a CamelException
+ *
+ * Return value: the folder in the store into which new mail is
+ * delivered, or %NULL if no such folder exists.
+ **/
+CamelFolder *
+camel_store_get_inbox (CamelStore *store, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+ folder = CS_CLASS (store)->get_inbox (store, ex);
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+
+ return folder;
+}
+
+static CamelFolder *
+get_special(CamelStore *store, enum _camel_vtrash_folder_t type)
+{
+ CamelFolder *folder;
+ GPtrArray *folders;
+ int i;
+
+ folder = camel_vtrash_folder_new(store, type);
+ folders = camel_object_bag_list(store->folders);
+ for (i=0;i<folders->len;i++) {
+ if (!CAMEL_IS_VTRASH_FOLDER(folders->pdata[i]))
+ camel_vee_folder_add_folder((CamelVeeFolder *)folder, (CamelFolder *)folders->pdata[i]);
+ camel_object_unref(folders->pdata[i]);
+ }
+ g_ptr_array_free(folders, TRUE);
+
+ return folder;
+}
+
+static CamelFolder *
+get_trash(CamelStore *store, CamelException *ex)
+{
+ return get_special(store, CAMEL_VTRASH_FOLDER_TRASH);
+}
+
+static CamelFolder *
+get_junk(CamelStore *store, CamelException *ex)
+{
+ return get_special(store, CAMEL_VTRASH_FOLDER_JUNK);
+}
+
+/**
+ * camel_store_get_trash:
+ * @store: a CamelStore
+ * @ex: a CamelException
+ *
+ * Return value: the folder in the store into which trash is
+ * delivered, or %NULL if no such folder exists.
+ **/
+CamelFolder *
+camel_store_get_trash (CamelStore *store, CamelException *ex)
+{
+ if ((store->flags & CAMEL_STORE_VTRASH) == 0)
+ return CS_CLASS(store)->get_trash(store, ex);
+ else
+ return camel_store_get_folder(store, CAMEL_VTRASH_NAME, 0, ex);
+}
+
+/**
+ * camel_store_get_junk:
+ * @store: a CamelStore
+ * @ex: a CamelException
+ *
+ * Return value: the folder in the store into which junk is
+ * delivered, or %NULL if no such folder exists.
+ **/
+CamelFolder *
+camel_store_get_junk (CamelStore *store, CamelException *ex)
+{
+ if ((store->flags & CAMEL_STORE_VJUNK) == 0)
+ return CS_CLASS(store)->get_junk(store, ex);
+ else
+ return camel_store_get_folder(store, CAMEL_VJUNK_NAME, 0, ex);
+}
+
+static void
+store_sync (CamelStore *store, int expunge, CamelException *ex)
+{
+ if (store->folders) {
+ GPtrArray *folders;
+ CamelFolder *folder;
+ CamelException x;
+ int i;
+
+ /* we don't sync any vFolders, that is used to update certain vfolder queries mainly,
+ and we're really only interested in storing/expunging the physical mails */
+ camel_exception_init(&x);
+ folders = camel_object_bag_list(store->folders);
+ for (i=0;i<folders->len;i++) {
+ folder = folders->pdata[i];
+ if (!CAMEL_IS_VEE_FOLDER(folder)
+ && !camel_exception_is_set(&x))
+ camel_folder_sync(folder, expunge, &x);
+ camel_object_unref(folder);
+ }
+ camel_exception_xfer(ex, &x);
+ g_ptr_array_free(folders, TRUE);
+ }
+}
+
+/**
+ * camel_store_sync:
+ * @store: a CamelStore
+ * @expunge: do we expunge deleted messages too?
+ * @ex: a CamelException
+ *
+ * Syncs any changes that have been made to the store object and its
+ * folders with the real store.
+ **/
+void
+camel_store_sync(CamelStore *store, int expunge, CamelException *ex)
+{
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ CS_CLASS(store)->sync(store, expunge, ex);
+}
+
+static CamelFolderInfo *
+get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ w(g_warning ("CamelStore::get_folder_info not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+
+ return NULL;
+}
+
+static void
+add_special_info (CamelStore *store, CamelFolderInfo *info, const char *name, const char *translated, gboolean unread_count)
+{
+ CamelFolderInfo *fi, *vinfo, *parent;
+ char *uri, *path;
+ CamelURL *url;
+
+ g_return_if_fail (info != NULL);
+
+ parent = NULL;
+ for (fi = info; fi; fi = fi->next) {
+ if (!strcmp (fi->name, name))
+ break;
+ parent = fi;
+ }
+
+ /* create our vTrash/vJunk URL */
+ url = camel_url_new (info->uri, NULL);
+ if (((CamelService *) store)->provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH) {
+ camel_url_set_fragment (url, name);
+ } else {
+ path = g_strdup_printf ("/%s", name);
+ camel_url_set_path (url, path);
+ g_free (path);
+ }
+
+ uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+
+ if (fi) {
+ /* We're going to replace the physical Trash/Junk folder with our vTrash/vJunk folder */
+ vinfo = fi;
+ g_free (vinfo->full_name);
+ g_free (vinfo->name);
+ g_free (vinfo->uri);
+ } else {
+ /* There wasn't a Trash/Junk folder so create a new folder entry */
+ vinfo = g_new0 (CamelFolderInfo, 1);
+
+ g_assert(parent != NULL);
+
+ vinfo->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_SUBSCRIBED;
+
+ /* link it into the right spot */
+ vinfo->next = parent->next;
+ parent->next = vinfo;
+ }
+
+ /* Fill in the new fields */
+ vinfo->flags |= CAMEL_FOLDER_VIRTUAL|CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_VTRASH;
+ vinfo->full_name = g_strdup (name);
+ vinfo->name = g_strdup (translated);
+ vinfo->uri = uri;
+ if (!unread_count)
+ vinfo->unread = -1;
+}
+
+static void
+dump_fi(CamelFolderInfo *fi, int depth)
+{
+ char *s;
+
+ s = g_alloca(depth+1);
+ memset(s, ' ', depth);
+ s[depth] = 0;
+
+ while (fi) {
+ printf("%suri: %s\n", s, fi->uri);
+ printf("%sfull_name: %s\n", s, fi->full_name);
+ printf("%sflags: %08x\n", s, fi->flags);
+ dump_fi(fi->child, depth+2);
+ fi = fi->next;
+ }
+}
+
+/**
+ * camel_store_get_folder_info:
+ * @store: a CamelStore
+ * @top: the name of the folder to start from
+ * @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
+ * @ex: a CamelException
+ *
+ * This fetches information about the folder structure of @store,
+ * starting with @top, and returns a tree of CamelFolderInfo
+ * structures. If @flags includes %CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ * only subscribed folders will be listed. (This flag can only be used
+ * for stores that support subscriptions.) If @flags includes
+ * %CAMEL_STORE_FOLDER_INFO_RECURSIVE, the returned tree will include
+ * all levels of hierarchy below @top. If not, it will only include
+ * the immediate subfolders of @top. If @flags includes
+ * %CAMEL_STORE_FOLDER_INFO_FAST, the unread_message_count fields of
+ * some or all of the structures may be set to -1, if the store cannot
+ * determine that information quickly. If @flags includes
+ * %CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL, don't include special virtual
+ * folders (such as vTrash or vJunk).
+ *
+ * Return value: a CamelFolderInfo tree, which must be freed with
+ * camel_store_free_folder_info.
+ **/
+CamelFolderInfo *
+camel_store_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *info;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
+ g_return_val_if_fail ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) ||
+ !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED),
+ NULL);
+
+ info = CS_CLASS (store)->get_folder_info (store, top, flags, ex);
+
+ if (info && (top == NULL || *top == '\0') && (flags & CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL) == 0) {
+ if (info->uri && (store->flags & CAMEL_STORE_VTRASH))
+ add_special_info (store, info, CAMEL_VTRASH_NAME, _("Trash"), FALSE);
+ if (info->uri && (store->flags & CAMEL_STORE_VJUNK))
+ add_special_info (store, info, CAMEL_VJUNK_NAME, _("Junk"), TRUE);
+ }
+
+ if (camel_debug_start("store:folder_info")) {
+ char *url = camel_url_to_string(((CamelService *)store)->url, CAMEL_URL_HIDE_ALL);
+ printf("Get folder info(%p:%s, '%s') =\n", store, url, top?top:"<null>");
+ g_free(url);
+ dump_fi(info, 2);
+ camel_debug_end();
+ }
+
+ return info;
+}
+
+static void
+free_folder_info (CamelStore *store, CamelFolderInfo *fi)
+{
+ w(g_warning ("CamelStore::free_folder_info not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+}
+
+/**
+ * camel_store_free_folder_info:
+ * @store: a CamelStore
+ * @tree: the tree returned by camel_store_get_folder_info()
+ *
+ * Frees the data returned by camel_store_get_folder_info().
+ **/
+void
+camel_store_free_folder_info (CamelStore *store, CamelFolderInfo *fi)
+{
+ g_return_if_fail (CAMEL_IS_STORE (store));
+
+ CS_CLASS (store)->free_folder_info (store, fi);
+}
+
+/**
+ * camel_store_free_folder_info_full:
+ * @store: a CamelStore
+ * @tree: the tree returned by camel_store_get_folder_info()
+ *
+ * An implementation for CamelStore::free_folder_info. Frees all
+ * of the data.
+ **/
+void
+camel_store_free_folder_info_full (CamelStore *store, CamelFolderInfo *fi)
+{
+ camel_folder_info_free (fi);
+}
+
+/**
+ * camel_store_free_folder_info_nop:
+ * @store: a CamelStore
+ * @tree: the tree returned by camel_store_get_folder_info()
+ *
+ * An implementation for CamelStore::free_folder_info. Does nothing.
+ **/
+void
+camel_store_free_folder_info_nop (CamelStore *store, CamelFolderInfo *fi)
+{
+ ;
+}
+
+/**
+ * camel_folder_info_free:
+ * @fi: the CamelFolderInfo
+ *
+ * Frees @fi.
+ **/
+void
+camel_folder_info_free (CamelFolderInfo *fi)
+{
+ if (fi) {
+ camel_folder_info_free (fi->next);
+ camel_folder_info_free (fi->child);
+ g_free (fi->name);
+ g_free (fi->full_name);
+ g_free (fi->uri);
+ g_free (fi);
+ }
+}
+
+static int
+folder_info_cmp (const void *ap, const void *bp)
+{
+ const CamelFolderInfo *a = ((CamelFolderInfo **)ap)[0];
+ const CamelFolderInfo *b = ((CamelFolderInfo **)bp)[0];
+
+ return strcmp (a->full_name, b->full_name);
+}
+
+static void
+free_name(void *key, void *data, void *user)
+{
+ g_free(key);
+}
+
+/**
+ * camel_folder_info_build:
+ * @folders: an array of CamelFolderInfo
+ * @namespace: an ignorable prefix on the folder names
+ * @separator: the hieararchy separator character
+ * @short_names: %TRUE if the (short) name of a folder is the part after
+ * the last @separator in the full name. %FALSE if it is the full name.
+ *
+ * This takes an array of folders and attaches them together according
+ * to the hierarchy described by their full_names and @separator. If
+ * @namespace is non-%NULL, then it will be ignored as a full_name
+ * prefix, for purposes of comparison. If necessary,
+ * camel_folder_info_build will create additional CamelFolderInfo with
+ * %NULL urls to fill in gaps in the tree. The value of @short_names
+ * is used in constructing the names of these intermediate folders.
+ *
+ * NOTE: This is deprected, do not use this.
+ * FIXME: remove this/move it to imap, which is the only user of it now.
+ *
+ * Return value: the top level of the tree of linked folder info.
+ **/
+CamelFolderInfo *
+camel_folder_info_build (GPtrArray *folders, const char *namespace,
+ char separator, gboolean short_names)
+{
+ CamelFolderInfo *fi, *pfi, *top = NULL, *tail = NULL;
+ GHashTable *hash;
+ char *name, *p, *pname;
+ int i, nlen;
+
+ if (!namespace)
+ namespace = "";
+ nlen = strlen (namespace);
+
+ qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_info_cmp);
+
+ /* Hash the folders. */
+ hash = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < folders->len; i++) {
+ fi = folders->pdata[i];
+ if (!strncmp (namespace, fi->full_name, nlen))
+ name = fi->full_name + nlen;
+ else
+ name = fi->full_name;
+ if (*name == separator)
+ name++;
+
+ g_hash_table_insert (hash, g_strdup(name), fi);
+ }
+
+ /* Now find parents. */
+ for (i = 0; i < folders->len; i++) {
+ fi = folders->pdata[i];
+ if (!strncmp (namespace, fi->full_name, nlen))
+ name = fi->full_name + nlen;
+ else
+ name = fi->full_name;
+ if (*name == separator)
+ name++;
+
+ p = strrchr (name, separator);
+ if (p) {
+ pname = g_strndup (name, p - name);
+ pfi = g_hash_table_lookup (hash, pname);
+ if (pfi) {
+ g_free (pname);
+ } else {
+ /* we are missing a folder in the heirarchy so
+ create a fake folder node */
+ CamelURL *url;
+ char *sep;
+
+ pfi = g_new0 (CamelFolderInfo, 1);
+ if (short_names) {
+ pfi->name = strrchr (pname, separator);
+ if (pfi->name)
+ pfi->name = g_strdup (pfi->name + 1);
+ else
+ pfi->name = g_strdup (pname);
+ } else
+ pfi->name = g_strdup (pname);
+
+ /* FIXME: url's with fragments should have the fragment truncated, not path */
+ url = camel_url_new (fi->uri, NULL);
+ sep = strrchr (url->path, separator);
+ if (sep)
+ *sep = '\0';
+ else
+ d(g_warning ("huh, no \"%c\" in \"%s\"?", separator, fi->url));
+
+ pfi->full_name = g_strdup(url->path+1);
+
+ /* since this is a "fake" folder node, it is not selectable */
+ camel_url_set_param (url, "noselect", "yes");
+ pfi->uri = camel_url_to_string (url, 0);
+ camel_url_free (url);
+
+ g_hash_table_insert (hash, pname, pfi);
+ g_ptr_array_add (folders, pfi);
+ }
+ tail = (CamelFolderInfo *)&pfi->child;
+ while (tail->next)
+ tail = tail->next;
+ tail->next = fi;
+ fi->parent = pfi;
+ } else if (!top)
+ top = fi;
+ }
+ g_hash_table_foreach(hash, free_name, NULL);
+ g_hash_table_destroy (hash);
+
+ /* Link together the top-level folders */
+ tail = top;
+ for (i = 0; i < folders->len; i++) {
+ fi = folders->pdata[i];
+ if (fi->parent || fi == top)
+ continue;
+ if (tail == NULL) {
+ tail = fi;
+ top = fi;
+ } else {
+ tail->next = fi;
+ tail = fi;
+ }
+ }
+
+ return top;
+}
+
+static CamelFolderInfo *folder_info_clone_rec(CamelFolderInfo *fi, CamelFolderInfo *parent)
+{
+ CamelFolderInfo *info;
+
+ info = g_malloc(sizeof(*info));
+ info->parent = parent;
+ info->uri = g_strdup(fi->uri);
+ info->name = g_strdup(fi->name);
+ info->full_name = g_strdup(fi->full_name);
+ info->unread = fi->unread;
+ info->flags = fi->flags;
+
+ if (fi->next)
+ info->next = folder_info_clone_rec(fi->next, parent);
+ else
+ info->next = NULL;
+
+ if (fi->child)
+ info->child = folder_info_clone_rec(fi->child, info);
+ else
+ info->child = NULL;
+
+ return info;
+}
+
+CamelFolderInfo *
+camel_folder_info_clone(CamelFolderInfo *fi)
+{
+ if (fi == NULL)
+ return NULL;
+
+ return folder_info_clone_rec(fi, NULL);
+}
+
+gboolean
+camel_store_supports_subscriptions (CamelStore *store)
+{
+ return (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
+}
+
+static gboolean
+folder_subscribed(CamelStore *store, const char *folder_name)
+{
+ w(g_warning ("CamelStore::folder_subscribed not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+
+ return FALSE;
+}
+
+/**
+ * camel_store_folder_subscribed: Tell whether or not a folder has been subscribed to.
+ * @store: a CamelStore
+ * @folder_name: the folder on which we're querying subscribed status.
+ * Return value: TRUE if folder is subscribed, FALSE if not.
+ **/
+gboolean
+camel_store_folder_subscribed(CamelStore *store, const char *folder_name)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS, FALSE);
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+
+ ret = CS_CLASS (store)->folder_subscribed (store, folder_name);
+
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+
+ return ret;
+}
+
+static void
+subscribe_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ w(g_warning ("CamelStore::subscribe_folder not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+}
+
+/**
+ * camel_store_subscribe_folder: marks a folder as subscribed.
+ * @store: a CamelStore
+ * @folder_name: the folder to subscribe to.
+ **/
+void
+camel_store_subscribe_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+
+ CS_CLASS (store)->subscribe_folder (store, folder_name, ex);
+
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+}
+
+static void
+unsubscribe_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ w(g_warning ("CamelStore::unsubscribe_folder not implemented for `%s'",
+ camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
+}
+
+/**
+ * camel_store_unsubscribe_folder: marks a folder as unsubscribed.
+ * @store: a CamelStore
+ * @folder_name: the folder to unsubscribe from.
+ **/
+void
+camel_store_unsubscribe_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelException local;
+
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
+
+ camel_exception_init(&local);
+
+ CAMEL_STORE_LOCK(store, folder_lock);
+
+ CS_CLASS (store)->unsubscribe_folder (store, folder_name, ex);
+
+ if (!camel_exception_is_set(&local))
+ cs_delete_cached_folder(store, folder_name);
+ else
+ camel_exception_xfer(ex, &local);
+
+ CAMEL_STORE_UNLOCK(store, folder_lock);
+}
+
+static void
+noop (CamelStore *store, CamelException *ex)
+{
+ /* no-op */
+ ;
+}
+
+/**
+ * camel_store_noop:
+ * @store: CamelStore
+ * @ex: exception
+ *
+ * Pings @store so that its connection doesn't timeout.
+ **/
+void
+camel_store_noop (CamelStore *store, CamelException *ex)
+{
+ CS_CLASS (store)->noop (store, ex);
+}
+
+
+/**
+ * camel_store_folder_uri_equal:
+ * @store: CamelStore
+ * @uri0: a uri
+ * @uri1: another uri
+ *
+ * Compares 2 folder uris to check that they are equal.
+ *
+ * Returns %TRUE if they are equal or %FALSE otherwise.
+ **/
+int
+camel_store_folder_uri_equal (CamelStore *store, const char *uri0, const char *uri1)
+{
+ CamelProvider *provider;
+ CamelURL *url0, *url1;
+ int equal;
+
+ g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
+ g_return_val_if_fail (uri0 && uri1, FALSE);
+
+ provider = ((CamelService *) store)->provider;
+
+ if (!(url0 = camel_url_new (uri0, NULL)))
+ return FALSE;
+
+ if (!(url1 = camel_url_new (uri1, NULL))) {
+ camel_url_free (url0);
+ return FALSE;
+ }
+
+ if ((equal = provider->url_equal (url0, url1))) {
+ const char *name0, *name1;
+
+ if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH) {
+ name0 = url0->fragment;
+ name1 = url1->fragment;
+ } else {
+ name0 = url0->path[0] == '/' ? url0->path + 1 : url0->path;
+ name1 = url1->path[0] == '/' ? url1->path + 1 : url1->path;
+ }
+
+ equal = name0 && name1 && CS_CLASS (store)->compare_folder_name (name0, name1);
+ }
+
+ camel_url_free (url0);
+ camel_url_free (url1);
+
+ return equal;
+}
diff --git a/camel/camel-tcp-stream-raw.c b/camel/camel-tcp-stream-raw.c
new file mode 100644
index 0000000000..93f3fbdbab
--- /dev/null
+++ b/camel/camel-tcp-stream-raw.c
@@ -0,0 +1,518 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "camel-tcp-stream-raw.h"
+#include "camel-file-utils.h"
+#include "camel-operation.h"
+
+static CamelTcpStreamClass *parent_class = NULL;
+
+/* Returns the class for a CamelTcpStreamRaw */
+#define CTSR_CLASS(so) CAMEL_TCP_STREAM_RAW_CLASS (CAMEL_OBJECT_GET_CLASS (so))
+
+static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
+static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
+static int stream_flush (CamelStream *stream);
+static int stream_close (CamelStream *stream);
+
+static int stream_connect (CamelTcpStream *stream, struct addrinfo *host);
+static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
+static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
+static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
+static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
+
+static void
+camel_tcp_stream_raw_class_init (CamelTcpStreamRawClass *camel_tcp_stream_raw_class)
+{
+ CamelTcpStreamClass *camel_tcp_stream_class =
+ CAMEL_TCP_STREAM_CLASS (camel_tcp_stream_raw_class);
+ CamelStreamClass *camel_stream_class =
+ CAMEL_STREAM_CLASS (camel_tcp_stream_raw_class);
+
+ parent_class = CAMEL_TCP_STREAM_CLASS (camel_type_get_global_classfuncs (camel_tcp_stream_get_type ()));
+
+ /* virtual method overload */
+ camel_stream_class->read = stream_read;
+ camel_stream_class->write = stream_write;
+ camel_stream_class->flush = stream_flush;
+ camel_stream_class->close = stream_close;
+
+ camel_tcp_stream_class->connect = stream_connect;
+ camel_tcp_stream_class->getsockopt = stream_getsockopt;
+ camel_tcp_stream_class->setsockopt = stream_setsockopt;
+ camel_tcp_stream_class->get_local_address = stream_get_local_address;
+ camel_tcp_stream_class->get_remote_address = stream_get_remote_address;
+}
+
+static void
+camel_tcp_stream_raw_init (gpointer object, gpointer klass)
+{
+ CamelTcpStreamRaw *stream = CAMEL_TCP_STREAM_RAW (object);
+
+ stream->sockfd = -1;
+}
+
+static void
+camel_tcp_stream_raw_finalize (CamelObject *object)
+{
+ CamelTcpStreamRaw *stream = CAMEL_TCP_STREAM_RAW (object);
+
+ if (stream->sockfd != -1)
+ close (stream->sockfd);
+}
+
+
+CamelType
+camel_tcp_stream_raw_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_tcp_stream_get_type (),
+ "CamelTcpStreamRaw",
+ sizeof (CamelTcpStreamRaw),
+ sizeof (CamelTcpStreamRawClass),
+ (CamelObjectClassInitFunc) camel_tcp_stream_raw_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_tcp_stream_raw_init,
+ (CamelObjectFinalizeFunc) camel_tcp_stream_raw_finalize);
+ }
+
+ return type;
+}
+
+#ifdef SIMULATE_FLAKY_NETWORK
+static ssize_t
+flaky_tcp_write (int fd, const char *buffer, size_t buflen)
+{
+ size_t len = buflen;
+ ssize_t nwritten;
+ int val;
+
+ if (buflen == 0)
+ return 0;
+
+ val = 1 + (int) (10.0 * rand () / (RAND_MAX + 1.0));
+
+ switch (val) {
+ case 1:
+ printf ("flaky_tcp_write (%d, ..., %d): (-1) EINTR\n", fd, buflen);
+ errno = EINTR;
+ return -1;
+ case 2:
+ printf ("flaky_tcp_write (%d, ..., %d): (-1) EAGAIN\n", fd, buflen);
+ errno = EAGAIN;
+ return -1;
+ case 3:
+ printf ("flaky_tcp_write (%d, ..., %d): (-1) EWOULDBLOCK\n", fd, buflen);
+ errno = EWOULDBLOCK;
+ return -1;
+ case 4:
+ case 5:
+ case 6:
+ len = 1 + (size_t) (buflen * rand () / (RAND_MAX + 1.0));
+ len = MIN (len, buflen);
+ /* fall through... */
+ default:
+ printf ("flaky_tcp_write (%d, ..., %d): (%d) '%.*s'", fd, buflen, len, (int) len, buffer);
+ nwritten = write (fd, buffer, len);
+ if (nwritten < 0)
+ printf (" errno => %s\n", strerror (errno));
+ else if (nwritten < len)
+ printf (" only wrote %d bytes\n", nwritten);
+ else
+ printf ("\n");
+
+ return nwritten;
+ }
+}
+
+#define write(fd, buffer, buflen) flaky_tcp_write (fd, buffer, buflen)
+
+static ssize_t
+flaky_tcp_read (int fd, char *buffer, size_t buflen)
+{
+ size_t len = buflen;
+ ssize_t nread;
+ int val;
+
+ if (buflen == 0)
+ return 0;
+
+ val = 1 + (int) (10.0 * rand () / (RAND_MAX + 1.0));
+
+ switch (val) {
+ case 1:
+ printf ("flaky_tcp_read (%d, ..., %d): (-1) EINTR\n", fd, buflen);
+ errno = EINTR;
+ return -1;
+ case 2:
+ printf ("flaky_tcp_read (%d, ..., %d): (-1) EAGAIN\n", fd, buflen);
+ errno = EAGAIN;
+ return -1;
+ case 3:
+ printf ("flaky_tcp_read (%d, ..., %d): (-1) EWOULDBLOCK\n", fd, buflen);
+ errno = EWOULDBLOCK;
+ return -1;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ len = 1 + (size_t) (10.0 * rand () / (RAND_MAX + 1.0));
+ len = MIN (len, buflen);
+ /* fall through... */
+ default:
+ printf ("flaky_tcp_read (%d, ..., %d): (%d)", fd, buflen, len);
+ nread = read (fd, buffer, len);
+ if (nread < 0)
+ printf (" errno => %s\n", strerror (errno));
+ else if (nread < len)
+ printf (" only read %d bytes\n", nread);
+ else
+ printf ("\n");
+
+ return nread;
+ }
+}
+
+#define read(fd, buffer, buflen) flaky_tcp_read (fd, buffer, buflen)
+
+#endif /* SIMULATE_FLAKY_NETWORK */
+
+
+
+/**
+ * camel_tcp_stream_raw_new:
+ *
+ * Return value: a tcp stream
+ **/
+CamelStream *
+camel_tcp_stream_raw_new ()
+{
+ CamelTcpStreamRaw *stream;
+
+ stream = CAMEL_TCP_STREAM_RAW (camel_object_new (camel_tcp_stream_raw_get_type ()));
+
+ return CAMEL_STREAM (stream);
+}
+
+static ssize_t
+stream_read (CamelStream *stream, char *buffer, size_t n)
+{
+ CamelTcpStreamRaw *raw = CAMEL_TCP_STREAM_RAW (stream);
+
+ return camel_read (raw->sockfd, buffer, n);
+}
+
+static ssize_t
+stream_write (CamelStream *stream, const char *buffer, size_t n)
+{
+ CamelTcpStreamRaw *raw = CAMEL_TCP_STREAM_RAW (stream);
+
+ return camel_write (raw->sockfd, buffer, n);
+}
+
+static int
+stream_flush (CamelStream *stream)
+{
+ return 0;
+}
+
+static int
+stream_close (CamelStream *stream)
+{
+ if (close (((CamelTcpStreamRaw *)stream)->sockfd) == -1)
+ return -1;
+
+ ((CamelTcpStreamRaw *)stream)->sockfd = -1;
+ return 0;
+}
+
+/* this is a 'cancellable' connect, cancellable from camel_operation_cancel etc */
+/* returns -1 & errno == EINTR if the connection was cancelled */
+static int
+socket_connect(struct addrinfo *h)
+{
+ struct timeval tv;
+ socklen_t len;
+ int cancel_fd;
+ int errnosav;
+ int ret, fd;
+
+ /* see if we're cancelled yet */
+ if (camel_operation_cancel_check (NULL)) {
+ errno = EINTR;
+ return -1;
+ }
+
+ if (h->ai_socktype != SOCK_STREAM) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((fd = socket (h->ai_family, SOCK_STREAM, 0)) == -1)
+ return -1;
+
+ cancel_fd = camel_operation_cancel_fd (NULL);
+ if (cancel_fd == -1) {
+ if (connect (fd, h->ai_addr, h->ai_addrlen) == -1) {
+ errnosav = errno;
+ close (fd);
+ errno = errnosav;
+ return -1;
+ }
+
+ return fd;
+ } else {
+ int flags, fdmax, status;
+ fd_set rdset, wrset;
+
+ flags = fcntl (fd, F_GETFL);
+ fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+
+ if (connect (fd, h->ai_addr, h->ai_addrlen) == 0) {
+ fcntl (fd, F_SETFL, flags);
+ return fd;
+ }
+
+ if (errno != EINPROGRESS) {
+ errnosav = errno;
+ close (fd);
+ errno = errnosav;
+ return -1;
+ }
+
+ do {
+ FD_ZERO (&rdset);
+ FD_ZERO (&wrset);
+ FD_SET (fd, &wrset);
+ FD_SET (cancel_fd, &rdset);
+ fdmax = MAX (fd, cancel_fd) + 1;
+ tv.tv_sec = 60 * 4;
+ tv.tv_usec = 0;
+
+ status = select (fdmax, &rdset, &wrset, 0, &tv);
+ } while (status == -1 && errno == EINTR);
+
+ if (status <= 0) {
+ close (fd);
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ if (cancel_fd != -1 && FD_ISSET (cancel_fd, &rdset)) {
+ close (fd);
+ errno = EINTR;
+ return -1;
+ } else {
+ len = sizeof (int);
+
+ if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &ret, &len) == -1) {
+ errnosav = errno;
+ close (fd);
+ errno = errnosav;
+ return -1;
+ }
+
+ if (ret != 0) {
+ close (fd);
+ errno = ret;
+ return -1;
+ }
+ }
+
+ fcntl (fd, F_SETFL, flags);
+ }
+
+ return fd;
+}
+
+static int
+stream_connect (CamelTcpStream *stream, struct addrinfo *host)
+{
+ CamelTcpStreamRaw *raw = CAMEL_TCP_STREAM_RAW (stream);
+
+ g_return_val_if_fail (host != NULL, -1);
+
+ while (host) {
+ raw->sockfd = socket_connect(host);
+ if (raw->sockfd != -1)
+ return 0;
+
+ host = host->ai_next;
+ }
+
+ return -1;
+}
+
+static int
+get_sockopt_level (const CamelSockOptData *data)
+{
+ switch (data->option) {
+ case CAMEL_SOCKOPT_MAXSEGMENT:
+ case CAMEL_SOCKOPT_NODELAY:
+ return IPPROTO_TCP;
+ default:
+ return SOL_SOCKET;
+ }
+}
+
+static int
+get_sockopt_optname (const CamelSockOptData *data)
+{
+ switch (data->option) {
+ case CAMEL_SOCKOPT_MAXSEGMENT:
+ return TCP_MAXSEG;
+ case CAMEL_SOCKOPT_NODELAY:
+ return TCP_NODELAY;
+ case CAMEL_SOCKOPT_BROADCAST:
+ return SO_BROADCAST;
+ case CAMEL_SOCKOPT_KEEPALIVE:
+ return SO_KEEPALIVE;
+ case CAMEL_SOCKOPT_LINGER:
+ return SO_LINGER;
+ case CAMEL_SOCKOPT_RECVBUFFERSIZE:
+ return SO_RCVBUF;
+ case CAMEL_SOCKOPT_SENDBUFFERSIZE:
+ return SO_SNDBUF;
+ case CAMEL_SOCKOPT_REUSEADDR:
+ return SO_REUSEADDR;
+ case CAMEL_SOCKOPT_IPTYPEOFSERVICE:
+ return SO_TYPE;
+ default:
+ return -1;
+ }
+}
+
+static int
+stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
+{
+ int optname, optlen;
+
+ if ((optname = get_sockopt_optname (data)) == -1)
+ return -1;
+
+ if (data->option == CAMEL_SOCKOPT_NONBLOCKING) {
+ int flags;
+
+ flags = fcntl (((CamelTcpStreamRaw *)stream)->sockfd, F_GETFL);
+ if (flags == -1)
+ return -1;
+
+ data->value.non_blocking = flags & O_NONBLOCK ? TRUE : FALSE;
+
+ return 0;
+ }
+
+ return getsockopt (((CamelTcpStreamRaw *)stream)->sockfd,
+ get_sockopt_level (data),
+ optname,
+ (void *) &data->value,
+ &optlen);
+}
+
+static int
+stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
+{
+ int optname;
+
+ if ((optname = get_sockopt_optname (data)) == -1)
+ return -1;
+
+ if (data->option == CAMEL_SOCKOPT_NONBLOCKING) {
+ int flags, set;
+
+ flags = fcntl (((CamelTcpStreamRaw *)stream)->sockfd, F_GETFL);
+ if (flags == -1)
+ return -1;
+
+ set = data->value.non_blocking ? O_NONBLOCK : 0;
+ flags = (flags & ~O_NONBLOCK) | set;
+
+ if (fcntl (((CamelTcpStreamRaw *)stream)->sockfd, F_SETFL, flags) == -1)
+ return -1;
+
+ return 0;
+ }
+
+ return setsockopt (((CamelTcpStreamRaw *)stream)->sockfd,
+ get_sockopt_level (data),
+ optname,
+ (void *) &data->value,
+ sizeof (data->value));
+}
+
+static struct sockaddr *
+stream_get_local_address (CamelTcpStream *stream, socklen_t *len)
+{
+#ifdef ENABLE_IPv6
+ struct sockaddr_in6 sin;
+#else
+ struct sockaddr_in sin;
+#endif
+ struct sockaddr *saddr = (struct sockaddr *)&sin;
+
+ *len = sizeof(sin);
+ if (getsockname (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, len) == -1)
+ return NULL;
+
+ saddr = g_malloc(*len);
+ memcpy(saddr, &sin, *len);
+
+ return saddr;
+}
+
+static struct sockaddr *
+stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
+{
+#ifdef ENABLE_IPv6
+ struct sockaddr_in6 sin;
+#else
+ struct sockaddr_in sin;
+#endif
+ struct sockaddr *saddr = (struct sockaddr *)&sin;
+
+ *len = sizeof(sin);
+ if (getpeername (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, len) == -1)
+ return NULL;
+
+ saddr = g_malloc(*len);
+ memcpy(saddr, &sin, *len);
+
+ return saddr;
+}
diff --git a/camel/camel-tcp-stream-ssl.c b/camel/camel-tcp-stream-ssl.c
new file mode 100644
index 0000000000..39bf89d468
--- /dev/null
+++ b/camel/camel-tcp-stream-ssl.c
@@ -0,0 +1,1256 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+/* NOTE: This is the default implementation of CamelTcpStreamSSL,
+ * used when the Mozilla NSS libraries are used. If you configured
+ * OpenSSL support instead, then this file won't be compiled and
+ * the CamelTcpStreamSSL implementation in camel-tcp-stream-openssl.c
+ * will be used instead.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_NSS
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <nspr.h>
+#include <prio.h>
+#include <prerror.h>
+#include <prerr.h>
+#include <secerr.h>
+#include <sslerr.h>
+#include "nss.h" /* Don't use <> here or it will include the system nss.h instead */
+#include <ssl.h>
+#include <cert.h>
+#include <certdb.h>
+#include <pk11func.h>
+
+/* this is commented because otherwise we get an error about the
+ redefinition of MD5Context...yay */
+/*#include <e-util/md5-utils.h>*/
+
+#include "camel-tcp-stream-ssl.h"
+#include "camel-stream-fs.h"
+#include "camel-session.h"
+#include "camel-certdb.h"
+#include "camel-operation.h"
+
+/* from md5-utils.h */
+void md5_get_digest (const char *buffer, int buffer_size, unsigned char digest[16]);
+
+#define IO_TIMEOUT (PR_TicksPerSecond() * 4 * 60)
+#define CONNECT_TIMEOUT (PR_TicksPerSecond () * 4 * 60)
+
+static CamelTcpStreamClass *parent_class = NULL;
+
+/* Returns the class for a CamelTcpStreamSSL */
+#define CTSS_CLASS(so) CAMEL_TCP_STREAM_SSL_CLASS (CAMEL_OBJECT_GET_CLASS (so))
+
+static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
+static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
+static int stream_flush (CamelStream *stream);
+static int stream_close (CamelStream *stream);
+
+static PRFileDesc *enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd);
+
+static int stream_connect (CamelTcpStream *stream, struct addrinfo *host);
+static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
+static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
+static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
+static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
+
+struct _CamelTcpStreamSSLPrivate {
+ PRFileDesc *sockfd;
+
+ struct _CamelSession *session;
+ char *expected_host;
+ gboolean ssl_mode;
+ guint32 flags;
+};
+
+static void
+camel_tcp_stream_ssl_class_init (CamelTcpStreamSSLClass *camel_tcp_stream_ssl_class)
+{
+ CamelTcpStreamClass *camel_tcp_stream_class =
+ CAMEL_TCP_STREAM_CLASS (camel_tcp_stream_ssl_class);
+ CamelStreamClass *camel_stream_class =
+ CAMEL_STREAM_CLASS (camel_tcp_stream_ssl_class);
+
+ parent_class = CAMEL_TCP_STREAM_CLASS (camel_type_get_global_classfuncs (camel_tcp_stream_get_type ()));
+
+ /* virtual method overload */
+ camel_stream_class->read = stream_read;
+ camel_stream_class->write = stream_write;
+ camel_stream_class->flush = stream_flush;
+ camel_stream_class->close = stream_close;
+
+ camel_tcp_stream_class->connect = stream_connect;
+ camel_tcp_stream_class->getsockopt = stream_getsockopt;
+ camel_tcp_stream_class->setsockopt = stream_setsockopt;
+ camel_tcp_stream_class->get_local_address = stream_get_local_address;
+ camel_tcp_stream_class->get_remote_address = stream_get_remote_address;
+}
+
+static void
+camel_tcp_stream_ssl_init (gpointer object, gpointer klass)
+{
+ CamelTcpStreamSSL *stream = CAMEL_TCP_STREAM_SSL (object);
+
+ stream->priv = g_new0 (struct _CamelTcpStreamSSLPrivate, 1);
+}
+
+static void
+camel_tcp_stream_ssl_finalize (CamelObject *object)
+{
+ CamelTcpStreamSSL *stream = CAMEL_TCP_STREAM_SSL (object);
+
+ if (stream->priv->sockfd != NULL)
+ PR_Close (stream->priv->sockfd);
+
+ if (stream->priv->session)
+ camel_object_unref(stream->priv->session);
+
+ g_free (stream->priv->expected_host);
+
+ g_free (stream->priv);
+}
+
+
+CamelType
+camel_tcp_stream_ssl_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_tcp_stream_get_type (),
+ "CamelTcpStreamSSL",
+ sizeof (CamelTcpStreamSSL),
+ sizeof (CamelTcpStreamSSLClass),
+ (CamelObjectClassInitFunc) camel_tcp_stream_ssl_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_tcp_stream_ssl_init,
+ (CamelObjectFinalizeFunc) camel_tcp_stream_ssl_finalize);
+ }
+
+ return type;
+}
+
+
+/**
+ * camel_tcp_stream_ssl_new:
+ * @session: active session
+ * @expected_host: host that the stream is expected to connect with.
+ * @flags: ENABLE_SSL2, ENABLE_SSL3 and/or ENABLE_TLS
+ *
+ * Since the SSL certificate authenticator may need to prompt the
+ * user, a CamelSession is needed. @expected_host is needed as a
+ * protection against an MITM attack.
+ *
+ * Return value: a ssl stream (in ssl mode)
+ **/
+CamelStream *
+camel_tcp_stream_ssl_new (CamelSession *session, const char *expected_host, guint32 flags)
+{
+ CamelTcpStreamSSL *stream;
+
+ g_assert(CAMEL_IS_SESSION(session));
+
+ stream = CAMEL_TCP_STREAM_SSL (camel_object_new (camel_tcp_stream_ssl_get_type ()));
+
+ stream->priv->session = session;
+ camel_object_ref(session);
+ stream->priv->expected_host = g_strdup (expected_host);
+ stream->priv->ssl_mode = TRUE;
+ stream->priv->flags = flags;
+
+ return CAMEL_STREAM (stream);
+}
+
+
+/**
+ * camel_tcp_stream_ssl_new_raw:
+ * @session: active session
+ * @expected_host: host that the stream is expected to connect with.
+ * @flags: ENABLE_SSL2, ENABLE_SSL3 and/or ENABLE_TLS
+ *
+ * Since the SSL certificate authenticator may need to prompt the
+ * user, a CamelSession is needed. @expected_host is needed as a
+ * protection against an MITM attack.
+ *
+ * Return value: a ssl-capable stream (in non ssl mode)
+ **/
+CamelStream *
+camel_tcp_stream_ssl_new_raw (CamelSession *session, const char *expected_host, guint32 flags)
+{
+ CamelTcpStreamSSL *stream;
+
+ g_assert(CAMEL_IS_SESSION(session));
+
+ stream = CAMEL_TCP_STREAM_SSL (camel_object_new (camel_tcp_stream_ssl_get_type ()));
+
+ stream->priv->session = session;
+ camel_object_ref(session);
+ stream->priv->expected_host = g_strdup (expected_host);
+ stream->priv->ssl_mode = FALSE;
+ stream->priv->flags = flags;
+
+ return CAMEL_STREAM (stream);
+}
+
+
+static void
+set_errno (int code)
+{
+ /* FIXME: this should handle more. */
+ switch (code) {
+ case PR_INVALID_ARGUMENT_ERROR:
+ errno = EINVAL;
+ break;
+ case PR_PENDING_INTERRUPT_ERROR:
+ errno = EINTR;
+ break;
+ case PR_IO_PENDING_ERROR:
+ errno = EAGAIN;
+ break;
+ case PR_WOULD_BLOCK_ERROR:
+ errno = EWOULDBLOCK;
+ break;
+ case PR_IN_PROGRESS_ERROR:
+ errno = EINPROGRESS;
+ break;
+ case PR_ALREADY_INITIATED_ERROR:
+ errno = EALREADY;
+ break;
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ errno = EHOSTUNREACH;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ errno = ECONNREFUSED;
+ break;
+ case PR_CONNECT_TIMEOUT_ERROR:
+ case PR_IO_TIMEOUT_ERROR:
+ errno = ETIMEDOUT;
+ break;
+ case PR_NOT_CONNECTED_ERROR:
+ errno = ENOTCONN;
+ break;
+ case PR_CONNECT_RESET_ERROR:
+ errno = ECONNRESET;
+ break;
+ case PR_IO_ERROR:
+ default:
+ errno = EIO;
+ break;
+ }
+}
+
+
+/**
+ * camel_tcp_stream_ssl_enable_ssl:
+ * @ssl: ssl stream
+ *
+ * Toggles an ssl-capable stream into ssl mode (if it isn't already).
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_tcp_stream_ssl_enable_ssl (CamelTcpStreamSSL *ssl)
+{
+ PRFileDesc *fd;
+
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (ssl), -1);
+
+ if (ssl->priv->sockfd && !ssl->priv->ssl_mode) {
+ if (!(fd = enable_ssl (ssl, NULL))) {
+ set_errno (PR_GetError ());
+ return -1;
+ }
+
+ ssl->priv->sockfd = fd;
+
+ if (SSL_ResetHandshake (fd, FALSE) == SECFailure) {
+ set_errno (PR_GetError ());
+ return -1;
+ }
+
+ if (SSL_ForceHandshake (fd) == -1) {
+ set_errno (PR_GetError ());
+ return -1;
+ }
+ }
+
+ ssl->priv->ssl_mode = TRUE;
+
+ return 0;
+}
+
+
+static ssize_t
+stream_read (CamelStream *stream, char *buffer, size_t n)
+{
+ CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
+ PRFileDesc *cancel_fd;
+ ssize_t nread;
+
+ if (camel_operation_cancel_check (NULL)) {
+ errno = EINTR;
+ return -1;
+ }
+
+ cancel_fd = camel_operation_cancel_prfd (NULL);
+ if (cancel_fd == NULL) {
+ do {
+ nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
+ if (nread == -1)
+ set_errno (PR_GetError ());
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+ } else {
+ PRSocketOptionData sockopts;
+ PRPollDesc pollfds[2];
+ gboolean nonblock;
+ int error;
+
+ /* get O_NONBLOCK options */
+ sockopts.option = PR_SockOpt_Nonblocking;
+ PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
+ sockopts.option = PR_SockOpt_Nonblocking;
+ nonblock = sockopts.value.non_blocking;
+ sockopts.value.non_blocking = TRUE;
+ PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
+
+ pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
+ pollfds[0].in_flags = PR_POLL_READ;
+ pollfds[1].fd = cancel_fd;
+ pollfds[1].in_flags = PR_POLL_READ;
+
+ do {
+ PRInt32 res;
+
+ pollfds[0].out_flags = 0;
+ pollfds[1].out_flags = 0;
+ nread = -1;
+
+ res = PR_Poll(pollfds, 2, IO_TIMEOUT);
+ if (res == -1)
+ set_errno(PR_GetError());
+ else if (res == 0)
+ errno = ETIMEDOUT;
+ else if (pollfds[1].out_flags == PR_POLL_READ) {
+ errno = EINTR;
+ goto failed;
+ } else {
+ do {
+ nread = PR_Read (tcp_stream_ssl->priv->sockfd, buffer, n);
+ if (nread == -1)
+ set_errno (PR_GetError ());
+ } while (nread == -1 && errno == EINTR);
+ }
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+
+ /* restore O_NONBLOCK options */
+ failed:
+ error = errno;
+ sockopts.option = PR_SockOpt_Nonblocking;
+ sockopts.value.non_blocking = nonblock;
+ PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
+ errno = error;
+ }
+
+ return nread;
+}
+
+static ssize_t
+stream_write (CamelStream *stream, const char *buffer, size_t n)
+{
+ CamelTcpStreamSSL *tcp_stream_ssl = CAMEL_TCP_STREAM_SSL (stream);
+ ssize_t w, written = 0;
+ PRFileDesc *cancel_fd;
+
+ if (camel_operation_cancel_check (NULL)) {
+ errno = EINTR;
+ return -1;
+ }
+
+ cancel_fd = camel_operation_cancel_prfd (NULL);
+ if (cancel_fd == NULL) {
+ do {
+ do {
+ w = PR_Write (tcp_stream_ssl->priv->sockfd, buffer + written, n - written);
+ if (w == -1)
+ set_errno (PR_GetError ());
+ } while (w == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+
+ if (w > 0)
+ written += w;
+ } while (w != -1 && written < n);
+ } else {
+ PRSocketOptionData sockopts;
+ PRPollDesc pollfds[2];
+ gboolean nonblock;
+ int error;
+
+ /* get O_NONBLOCK options */
+ sockopts.option = PR_SockOpt_Nonblocking;
+ PR_GetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
+ sockopts.option = PR_SockOpt_Nonblocking;
+ nonblock = sockopts.value.non_blocking;
+ sockopts.value.non_blocking = TRUE;
+ PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
+
+ pollfds[0].fd = tcp_stream_ssl->priv->sockfd;
+ pollfds[0].in_flags = PR_POLL_WRITE;
+ pollfds[1].fd = cancel_fd;
+ pollfds[1].in_flags = PR_POLL_READ;
+
+ do {
+ PRInt32 res;
+
+ pollfds[0].out_flags = 0;
+ pollfds[1].out_flags = 0;
+ w = -1;
+
+ res = PR_Poll (pollfds, 2, IO_TIMEOUT);
+ if (res == -1) {
+ set_errno(PR_GetError());
+ if (errno == EINTR)
+ w = 0;
+ } else if (res == 0)
+ errno = ETIMEDOUT;
+ else if (pollfds[1].out_flags == PR_POLL_READ) {
+ errno = EINTR;
+ } else {
+ do {
+ w = PR_Write (tcp_stream_ssl->priv->sockfd, buffer + written, n - written);
+ if (w == -1)
+ set_errno (PR_GetError ());
+ } while (w == -1 && errno == EINTR);
+
+ if (w == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ w = 0;
+ } else
+ written += w;
+ }
+ } while (w != -1 && written < n);
+
+ /* restore O_NONBLOCK options */
+ error = errno;
+ sockopts.option = PR_SockOpt_Nonblocking;
+ sockopts.value.non_blocking = nonblock;
+ PR_SetSocketOption (tcp_stream_ssl->priv->sockfd, &sockopts);
+ errno = error;
+ }
+
+ if (w == -1)
+ return -1;
+
+ return written;
+}
+
+static int
+stream_flush (CamelStream *stream)
+{
+ /*return PR_Sync (((CamelTcpStreamSSL *)stream)->priv->sockfd);*/
+ return 0;
+}
+
+static int
+stream_close (CamelStream *stream)
+{
+ if (PR_Close (((CamelTcpStreamSSL *)stream)->priv->sockfd) == PR_FAILURE)
+ return -1;
+
+ ((CamelTcpStreamSSL *)stream)->priv->sockfd = NULL;
+
+ return 0;
+}
+
+#if 0
+/* Since this is default implementation, let NSS handle it. */
+static SECStatus
+ssl_get_client_auth (void *data, PRFileDesc *sockfd,
+ struct CERTDistNamesStr *caNames,
+ struct CERTCertificateStr **pRetCert,
+ struct SECKEYPrivateKeyStr **pRetKey)
+{
+ SECStatus status = SECFailure;
+ SECKEYPrivateKey *privkey;
+ CERTCertificate *cert;
+ void *proto_win;
+
+ proto_win = SSL_RevealPinArg (sockfd);
+
+ if ((char *) data) {
+ cert = PK11_FindCertFromNickname ((char *) data, proto_win);
+ if (cert) {
+ privKey = PK11_FindKeyByAnyCert (cert, proto_win);
+ if (privkey) {
+ status = SECSuccess;
+ } else {
+ CERT_DestroyCertificate (cert);
+ }
+ }
+ } else {
+ /* no nickname given, automatically find the right cert */
+ CERTCertNicknames *names;
+ int i;
+
+ names = CERT_GetCertNicknames (CERT_GetDefaultCertDB (),
+ SEC_CERT_NICKNAMES_USER,
+ proto_win);
+
+ if (names != NULL) {
+ for (i = 0; i < names->numnicknames; i++) {
+ cert = PK11_FindCertFromNickname (names->nicknames[i],
+ proto_win);
+ if (!cert)
+ continue;
+
+ /* Only check unexpired certs */
+ if (CERT_CheckCertValidTimes (cert, PR_Now (), PR_FALSE) != secCertTimeValid) {
+ CERT_DestroyCertificate (cert);
+ continue;
+ }
+
+ status = NSS_CmpCertChainWCANames (cert, caNames);
+ if (status == SECSuccess) {
+ privkey = PK11_FindKeyByAnyCert (cert, proto_win);
+ if (privkey)
+ break;
+
+ status = SECFailure;
+ break;
+ }
+
+ CERT_FreeNicknames (names);
+ }
+ }
+ }
+
+ if (status == SECSuccess) {
+ *pRetCert = cert;
+ *pRetKey = privkey;
+ }
+
+ return status;
+}
+#endif
+
+#if 0
+/* Since this is the default NSS implementation, no need for us to use this. */
+static SECStatus
+ssl_auth_cert (void *data, PRFileDesc *sockfd, PRBool checksig, PRBool is_server)
+{
+ CERTCertificate *cert;
+ SECStatus status;
+ void *pinarg;
+ char *host;
+
+ cert = SSL_PeerCertificate (sockfd);
+ pinarg = SSL_RevealPinArg (sockfd);
+ status = CERT_VerifyCertNow ((CERTCertDBHandle *)data, cert,
+ checksig, certUsageSSLClient, pinarg);
+
+ if (status != SECSuccess)
+ return SECFailure;
+
+ /* Certificate is OK. Since this is the client side of an SSL
+ * connection, we need to verify that the name field in the cert
+ * matches the desired hostname. This is our defense against
+ * man-in-the-middle attacks.
+ */
+
+ /* SSL_RevealURL returns a hostname, not a URL. */
+ host = SSL_RevealURL (sockfd);
+
+ if (host && *host) {
+ status = CERT_VerifyCertName (cert, host);
+ } else {
+ PR_SetError (SSL_ERROR_BAD_CERT_DOMAIN, 0);
+ status = SECFailure;
+ }
+
+ if (host)
+ PR_Free (host);
+
+ return secStatus;
+}
+#endif
+
+CamelCert *camel_certdb_nss_cert_get(CamelCertDB *certdb, CERTCertificate *cert);
+CamelCert *camel_certdb_nss_cert_add(CamelCertDB *certdb, CERTCertificate *cert);
+void camel_certdb_nss_cert_set(CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert);
+
+static char *
+cert_fingerprint(CERTCertificate *cert)
+{
+ unsigned char md5sum[16], fingerprint[50], *f;
+ int i;
+ const char tohex[16] = "0123456789abcdef";
+
+ md5_get_digest (cert->derCert.data, cert->derCert.len, md5sum);
+ for (i=0,f = fingerprint; i<16; i++) {
+ unsigned int c = md5sum[i];
+
+ *f++ = tohex[(c >> 4) & 0xf];
+ *f++ = tohex[c & 0xf];
+ *f++ = ':';
+ }
+
+ fingerprint[47] = 0;
+
+ return g_strdup(fingerprint);
+}
+
+/* lookup a cert uses fingerprint to index an on-disk file */
+CamelCert *
+camel_certdb_nss_cert_get(CamelCertDB *certdb, CERTCertificate *cert)
+{
+ char *fingerprint, *path;
+ CamelCert *ccert;
+ struct stat st;
+ size_t nread;
+ ssize_t n;
+ int fd;
+
+ fingerprint = cert_fingerprint (cert);
+ ccert = camel_certdb_get_cert (certdb, fingerprint);
+ if (ccert == NULL) {
+ g_free (fingerprint);
+ return ccert;
+ }
+
+ if (ccert->rawcert == NULL) {
+ path = g_strdup_printf ("%s/.camel_certs/%s", getenv ("HOME"), fingerprint);
+ if (stat (path, &st) == -1
+ || (fd = open (path, O_RDONLY)) == -1) {
+ g_warning ("could not load cert %s: %s", path, strerror (errno));
+ g_free (fingerprint);
+ g_free (path);
+ camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
+ camel_certdb_touch (certdb);
+
+ return ccert;
+ }
+ g_free(path);
+
+ ccert->rawcert = g_byte_array_new ();
+ g_byte_array_set_size (ccert->rawcert, st.st_size);
+
+ nread = 0;
+ do {
+ do {
+ n = read (fd, ccert->rawcert->data + nread, st.st_size - nread);
+ } while (n == -1 && errno == EINTR);
+
+ if (n > 0)
+ nread += n;
+ } while (nread < st.st_size && n != -1);
+
+ close (fd);
+
+ if (nread != st.st_size) {
+ g_warning ("cert size read truncated %s: %d != %ld", path, nread, st.st_size);
+ g_byte_array_free(ccert->rawcert, TRUE);
+ ccert->rawcert = NULL;
+ g_free(fingerprint);
+ camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
+ camel_certdb_touch(certdb);
+
+ return ccert;
+ }
+ }
+
+ g_free(fingerprint);
+ if (ccert->rawcert->len != cert->derCert.len
+ || memcmp(ccert->rawcert->data, cert->derCert.data, cert->derCert.len) != 0) {
+ g_warning("rawcert != derCer");
+ camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
+ camel_certdb_touch(certdb);
+ }
+
+ return ccert;
+}
+
+/* add a cert to the certdb */
+CamelCert *
+camel_certdb_nss_cert_add(CamelCertDB *certdb, CERTCertificate *cert)
+{
+ CamelCert *ccert;
+ char *fingerprint;
+
+ fingerprint = cert_fingerprint(cert);
+
+ ccert = camel_certdb_cert_new(certdb);
+ camel_cert_set_issuer(certdb, ccert, CERT_NameToAscii(&cert->issuer));
+ camel_cert_set_subject(certdb, ccert, CERT_NameToAscii(&cert->subject));
+ /* hostname is set in caller */
+ /*camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);*/
+ camel_cert_set_fingerprint(certdb, ccert, fingerprint);
+ camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
+ g_free(fingerprint);
+
+ camel_certdb_nss_cert_set(certdb, ccert, cert);
+
+ camel_certdb_add(certdb, ccert);
+
+ return ccert;
+}
+
+/* set the 'raw' cert (& save it) */
+void
+camel_certdb_nss_cert_set(CamelCertDB *certdb, CamelCert *ccert, CERTCertificate *cert)
+{
+ char *dir, *path, *fingerprint;
+ CamelStream *stream;
+ struct stat st;
+
+ fingerprint = ccert->fingerprint;
+
+ if (ccert->rawcert == NULL)
+ ccert->rawcert = g_byte_array_new ();
+
+ g_byte_array_set_size (ccert->rawcert, cert->derCert.len);
+ memcpy (ccert->rawcert->data, cert->derCert.data, cert->derCert.len);
+
+ dir = g_strdup_printf ("%s/.camel_certs", getenv ("HOME"));
+ if (stat (dir, &st) == -1 && mkdir (dir, 0700) == -1) {
+ g_warning ("Could not create cert directory '%s': %s", dir, strerror (errno));
+ g_free (dir);
+ return;
+ }
+
+ path = g_strdup_printf ("%s/%s", dir, fingerprint);
+ g_free (dir);
+
+ stream = camel_stream_fs_new_with_name (path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (stream != NULL) {
+ if (camel_stream_write (stream, ccert->rawcert->data, ccert->rawcert->len) == -1) {
+ g_warning ("Could not save cert: %s: %s", path, strerror (errno));
+ unlink (path);
+ }
+ camel_stream_close (stream);
+ camel_object_unref (stream);
+ } else {
+ g_warning ("Could not save cert: %s: %s", path, strerror (errno));
+ }
+
+ g_free (path);
+}
+
+
+#if 0
+/* used by the mozilla-like code below */
+static char *
+get_nickname(CERTCertificate *cert)
+{
+ char *server, *nick = NULL;
+ int i;
+ PRBool status = PR_TRUE;
+
+ server = CERT_GetCommonName(&cert->subject);
+ if (server == NULL)
+ return NULL;
+
+ for (i=1;status == PR_TRUE;i++) {
+ if (nick) {
+ g_free(nick);
+ nick = g_strdup_printf("%s #%d", server, i);
+ } else {
+ nick = g_strdup(server);
+ }
+ status = SEC_CertNicknameConflict(server, &cert->derSubject, cert->dbhandle);
+ }
+
+ return nick;
+}
+#endif
+
+static SECStatus
+ssl_bad_cert (void *data, PRFileDesc *sockfd)
+{
+ gboolean accept;
+ CamelCertDB *certdb = NULL;
+ CamelCert *ccert = NULL;
+ char *prompt, *cert_str, *fingerprint;
+ CamelTcpStreamSSL *ssl;
+ CERTCertificate *cert;
+ SECStatus status = SECFailure;
+
+ g_return_val_if_fail (data != NULL, SECFailure);
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM_SSL (data), SECFailure);
+
+ ssl = data;
+
+ cert = SSL_PeerCertificate (sockfd);
+ if (cert == NULL)
+ return SECFailure;
+
+ certdb = camel_certdb_get_default();
+ ccert = camel_certdb_nss_cert_get(certdb, cert);
+ if (ccert == NULL) {
+ ccert = camel_certdb_nss_cert_add(certdb, cert);
+ camel_cert_set_hostname(certdb, ccert, ssl->priv->expected_host);
+ }
+
+ if (ccert->trust == CAMEL_CERT_TRUST_UNKNOWN) {
+ status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
+ fingerprint = cert_fingerprint(cert);
+ cert_str = g_strdup_printf (_("Issuer: %s\n"
+ "Subject: %s\n"
+ "Fingerprint: %s\n"
+ "Signature: %s"),
+ CERT_NameToAscii (&cert->issuer),
+ CERT_NameToAscii (&cert->subject),
+ fingerprint, status == SECSuccess?_("GOOD"):_("BAD"));
+ g_free(fingerprint);
+
+ /* construct our user prompt */
+ prompt = g_strdup_printf (_("SSL Certificate check for %s:\n\n%s\n\nDo you wish to accept?"),
+ ssl->priv->expected_host, cert_str);
+ g_free (cert_str);
+
+ /* query the user to find out if we want to accept this certificate */
+ accept = camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE);
+ g_free(prompt);
+ if (accept) {
+ camel_certdb_nss_cert_set(certdb, ccert, cert);
+ camel_cert_set_trust(certdb, ccert, CAMEL_CERT_TRUST_FULLY);
+ camel_certdb_touch(certdb);
+ }
+ } else {
+ accept = ccert->trust != CAMEL_CERT_TRUST_NEVER;
+ }
+
+ camel_certdb_cert_unref(certdb, ccert);
+ camel_object_unref(certdb);
+
+ return accept ? SECSuccess : SECFailure;
+
+#if 0
+ int i, error;
+ CERTCertTrust trust;
+ SECItem *certs[1];
+ int go = 1;
+ char *host, *nick;
+
+ error = PR_GetError();
+
+ /* This code is basically what mozilla does - however it doesn't seem to work here
+ very reliably :-/ */
+ while (go && status != SECSuccess) {
+ char *prompt = NULL;
+
+ printf("looping, error '%d'\n", error);
+
+ switch(error) {
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ /* add certificate */
+ printf("unknown issuer, adding ... \n");
+ prompt = g_strdup_printf(_("Certificate problem: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
+
+ if (camel_session_alert_user(ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
+
+ nick = get_nickname(cert);
+ if (NULL == nick) {
+ g_free(prompt);
+ status = SECFailure;
+ break;
+ }
+
+ printf("adding cert '%s'\n", nick);
+
+ if (!cert->trust) {
+ cert->trust = (CERTCertTrust*)PORT_ArenaZAlloc(cert->arena, sizeof(CERTCertTrust));
+ CERT_DecodeTrustString(cert->trust, "P");
+ }
+
+ certs[0] = &cert->derCert;
+ /*CERT_ImportCerts (cert->dbhandle, certUsageSSLServer, 1, certs, NULL, TRUE, FALSE, nick);*/
+ CERT_ImportCerts(cert->dbhandle, certUsageUserCertImport, 1, certs, NULL, TRUE, FALSE, nick);
+ g_free(nick);
+
+ printf(" cert type %08x\n", cert->nsCertType);
+
+ memset((void*)&trust, 0, sizeof(trust));
+ if (CERT_GetCertTrust(cert, &trust) != SECSuccess) {
+ CERT_DecodeTrustString(&trust, "P");
+ }
+ trust.sslFlags |= CERTDB_VALID_PEER | CERTDB_TRUSTED;
+ if (CERT_ChangeCertTrust(cert->dbhandle, cert, &trust) != SECSuccess) {
+ printf("couldn't change cert trust?\n");
+ }
+
+ /*status = SECSuccess;*/
+#if 1
+ /* re-verify? */
+ status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLServer, NULL);
+ error = PR_GetError();
+ printf("re-verify status %d, error %d\n", status, error);
+#endif
+
+ printf(" cert type %08x\n", cert->nsCertType);
+ } else {
+ printf("failed/cancelled\n");
+ go = 0;
+ }
+
+ break;
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ printf("bad domain\n");
+
+ prompt = g_strdup_printf(_("Bad certificate domain: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
+
+ if (camel_session_alert_user (ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
+ host = SSL_RevealURL(sockfd);
+ status = CERT_AddOKDomainName(cert, host);
+ printf("add ok domain name : %s\n", status == SECFailure?"fail":"ok");
+ error = PR_GetError();
+ if (status == SECFailure)
+ go = 0;
+ } else {
+ go = 0;
+ }
+
+ break;
+
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ printf("expired\n");
+
+ prompt = g_strdup_printf(_("Certificate expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
+
+ if (camel_session_alert_user(ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
+ cert->timeOK = PR_TRUE;
+ status = CERT_VerifyCertNow(cert->dbhandle, cert, TRUE, certUsageSSLClient, NULL);
+ error = PR_GetError();
+ if (status == SECFailure)
+ go = 0;
+ } else {
+ go = 0;
+ }
+
+ break;
+
+ case SEC_ERROR_CRL_EXPIRED:
+ printf("crl expired\n");
+
+ prompt = g_strdup_printf(_("Certificate revocation list expired: %s\nIssuer: %s"), cert->subjectName, cert->issuerName);
+
+ if (camel_session_alert_user(ssl->priv->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE)) {
+ host = SSL_RevealURL(sockfd);
+ status = CERT_AddOKDomainName(cert, host);
+ }
+
+ go = 0;
+ break;
+
+ default:
+ printf("generic error\n");
+ go = 0;
+ break;
+ }
+
+ g_free(prompt);
+ }
+
+ CERT_DestroyCertificate(cert);
+
+ return status;
+#endif
+}
+
+static PRFileDesc *
+enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd)
+{
+ PRFileDesc *ssl_fd;
+
+ ssl_fd = SSL_ImportFD (NULL, fd ? fd : ssl->priv->sockfd);
+ if (!ssl_fd)
+ return NULL;
+
+ SSL_OptionSet (ssl_fd, SSL_SECURITY, PR_TRUE);
+ if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL2)
+ SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_TRUE);
+ else
+ SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
+ if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+ SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_TRUE);
+ else
+ SSL_OptionSet (ssl_fd, SSL_ENABLE_SSL3, PR_FALSE);
+ if (ssl->priv->flags & CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+ SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_TRUE);
+ else
+ SSL_OptionSet (ssl_fd, SSL_ENABLE_TLS, PR_FALSE);
+
+ SSL_SetURL (ssl_fd, ssl->priv->expected_host);
+
+ /*SSL_GetClientAuthDataHook (sslSocket, ssl_get_client_auth, (void *) certNickname);*/
+ /*SSL_AuthCertificateHook (ssl_fd, ssl_auth_cert, (void *) CERT_GetDefaultCertDB ());*/
+ SSL_BadCertHook (ssl_fd, ssl_bad_cert, ssl);
+
+ ssl->priv->ssl_mode = TRUE;
+
+ return ssl_fd;
+}
+
+static int
+sockaddr_to_praddr(struct sockaddr *s, int len, PRNetAddr *addr)
+{
+ /* We assume the ip addresses are the same size - they have to be anyway.
+ We could probably just use memcpy *shrug* */
+
+ memset(addr, 0, sizeof(*addr));
+
+ if (s->sa_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)s;
+
+ if (len < sizeof(*sin))
+ return -1;
+
+ addr->inet.family = PR_AF_INET;
+ addr->inet.port = sin->sin_port;
+ memcpy(&addr->inet.ip, &sin->sin_addr, sizeof(addr->inet.ip));
+
+ return 0;
+ }
+#ifdef ENABLE_IPv6
+ else if (s->sa_family == PR_AF_INET6) {
+ struct sockaddr_in6 *sin = (struct sockaddr_in6 *)s;
+
+ if (len < sizeof(*sin))
+ return -1;
+
+ addr->ipv6.family = PR_AF_INET6;
+ addr->ipv6.port = sin->sin6_port;
+ addr->ipv6.flowinfo = sin->sin6_flowinfo;
+ memcpy(&addr->ipv6.ip, &sin->sin6_addr, sizeof(addr->ipv6.ip));
+ addr->ipv6.scope_id = sin->sin6_scope_id;
+
+ return 0;
+ }
+#endif
+
+ return -1;
+}
+
+static int
+socket_connect(CamelTcpStream *stream, struct addrinfo *host)
+{
+ CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream);
+ PRNetAddr netaddr;
+ PRFileDesc *fd, *cancel_fd;
+
+ if (sockaddr_to_praddr(host->ai_addr, host->ai_addrlen, &netaddr) != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd = PR_OpenTCPSocket(netaddr.raw.family);
+ if (fd == NULL) {
+ set_errno (PR_GetError ());
+ return -1;
+ }
+
+ if (ssl->priv->ssl_mode) {
+ PRFileDesc *ssl_fd;
+
+ ssl_fd = enable_ssl (ssl, fd);
+ if (ssl_fd == NULL) {
+ int errnosave;
+
+ set_errno (PR_GetError ());
+ errnosave = errno;
+ PR_Close (fd);
+ errno = errnosave;
+
+ return -1;
+ }
+
+ fd = ssl_fd;
+ }
+
+ cancel_fd = camel_operation_cancel_prfd(NULL);
+
+ if (PR_Connect (fd, &netaddr, cancel_fd?0:CONNECT_TIMEOUT) == PR_FAILURE) {
+ int errnosave;
+
+ set_errno (PR_GetError ());
+ if (errno == EINPROGRESS || (cancel_fd && errno == ETIMEDOUT)) {
+ gboolean connected = FALSE;
+ PRPollDesc poll[2];
+
+ poll[0].fd = fd;
+ poll[0].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ poll[1].fd = cancel_fd;
+ poll[1].in_flags = PR_POLL_READ;
+
+ do {
+ poll[0].out_flags = 0;
+ poll[1].out_flags = 0;
+
+ if (PR_Poll (poll, cancel_fd?2:1, CONNECT_TIMEOUT) == PR_FAILURE) {
+ set_errno (PR_GetError ());
+ goto exception;
+ }
+
+ if (poll[1].out_flags == PR_POLL_READ) {
+ errno = EINTR;
+ goto exception;
+ }
+
+ if (PR_ConnectContinue(fd, poll[0].out_flags) == PR_FAILURE) {
+ set_errno (PR_GetError ());
+ if (errno != EINPROGRESS)
+ goto exception;
+ } else {
+ connected = TRUE;
+ }
+ } while (!connected);
+ } else {
+ exception:
+ errnosave = errno;
+ PR_Close (fd);
+ ssl->priv->sockfd = NULL;
+ errno = errnosave;
+
+ return -1;
+ }
+
+ errno = 0;
+ }
+
+ ssl->priv->sockfd = fd;
+
+ return 0;
+}
+
+static int
+stream_connect(CamelTcpStream *stream, struct addrinfo *host)
+{
+ while (host) {
+ if (socket_connect(stream, host) == 0)
+ return 0;
+ host = host->ai_next;
+ }
+
+ return -1;
+}
+
+static int
+stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
+{
+ PRSocketOptionData sodata;
+
+ memset ((void *) &sodata, 0, sizeof (sodata));
+ memcpy ((void *) &sodata, (void *) data, sizeof (CamelSockOptData));
+
+ if (PR_GetSocketOption (((CamelTcpStreamSSL *)stream)->priv->sockfd, &sodata) == PR_FAILURE)
+ return -1;
+
+ memcpy ((void *) data, (void *) &sodata, sizeof (CamelSockOptData));
+
+ return 0;
+}
+
+static int
+stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
+{
+ PRSocketOptionData sodata;
+
+ memset ((void *) &sodata, 0, sizeof (sodata));
+ memcpy ((void *) &sodata, (void *) data, sizeof (CamelSockOptData));
+
+ if (PR_SetSocketOption (((CamelTcpStreamSSL *)stream)->priv->sockfd, &sodata) == PR_FAILURE)
+ return -1;
+
+ return 0;
+}
+
+static struct sockaddr *
+sockaddr_from_praddr(PRNetAddr *addr, socklen_t *len)
+{
+ /* We assume the ip addresses are the same size - they have to be anyway */
+
+ if (addr->raw.family == PR_AF_INET) {
+ struct sockaddr_in *sin = g_malloc0(sizeof(*sin));
+
+ sin->sin_family = AF_INET;
+ sin->sin_port = addr->inet.port;
+ memcpy(&sin->sin_addr, &addr->inet.ip, sizeof(sin->sin_addr));
+ *len = sizeof(*sin);
+
+ return (struct sockaddr *)sin;
+ }
+#ifdef ENABLE_IPv6
+ else if (addr->raw.family == PR_AF_INET6) {
+ struct sockaddr_in6 *sin = g_malloc0(sizeof(*sin));
+
+ sin->sin6_family = AF_INET6;
+ sin->sin6_port = addr->ipv6.port;
+ sin->sin6_flowinfo = addr->ipv6.flowinfo;
+ memcpy(&sin->sin6_addr, &addr->ipv6.ip, sizeof(sin->sin6_addr));
+ sin->sin6_scope_id = addr->ipv6.scope_id;
+ *len = sizeof(*sin);
+
+ return (struct sockaddr *)sin;
+ }
+#endif
+
+ return NULL;
+}
+
+static struct sockaddr *
+stream_get_local_address(CamelTcpStream *stream, socklen_t *len)
+{
+ PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
+ PRNetAddr addr;
+
+ if (PR_GetSockName(sockfd, &addr) != PR_SUCCESS)
+ return NULL;
+
+ return sockaddr_from_praddr(&addr, len);
+}
+
+static struct sockaddr *
+stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
+{
+ PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
+ PRNetAddr addr;
+
+ if (PR_GetPeerName(sockfd, &addr) != PR_SUCCESS)
+ return NULL;
+
+ return sockaddr_from_praddr(&addr, len);
+}
+
+#endif /* HAVE_NSS */
diff --git a/camel/camel-tcp-stream.c b/camel/camel-tcp-stream.c
new file mode 100644
index 0000000000..3aa2d0d368
--- /dev/null
+++ b/camel/camel-tcp-stream.c
@@ -0,0 +1,208 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "camel-tcp-stream.h"
+
+#define w(x)
+
+static CamelStreamClass *parent_class = NULL;
+
+/* Returns the class for a CamelTcpStream */
+#define CTS_CLASS(so) CAMEL_TCP_STREAM_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static int tcp_connect (CamelTcpStream *stream, struct addrinfo *host);
+static int tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
+static int tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
+static struct sockaddr *tcp_get_local_address (CamelTcpStream *stream, socklen_t *len);
+static struct sockaddr *tcp_get_remote_address (CamelTcpStream *stream, socklen_t *len);
+
+static void
+camel_tcp_stream_class_init (CamelTcpStreamClass *camel_tcp_stream_class)
+{
+ /*CamelStreamClass *camel_stream_class = CAMEL_STREAM_CLASS (camel_tcp_stream_class);*/
+
+ parent_class = CAMEL_STREAM_CLASS (camel_type_get_global_classfuncs (CAMEL_STREAM_TYPE));
+
+ /* tcp stream methods */
+ camel_tcp_stream_class->connect = tcp_connect;
+ camel_tcp_stream_class->getsockopt = tcp_getsockopt;
+ camel_tcp_stream_class->setsockopt = tcp_setsockopt;
+ camel_tcp_stream_class->get_local_address = tcp_get_local_address;
+ camel_tcp_stream_class->get_remote_address = tcp_get_remote_address;
+}
+
+static void
+camel_tcp_stream_init (void *o)
+{
+ ;
+}
+
+CamelType
+camel_tcp_stream_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (CAMEL_STREAM_TYPE,
+ "CamelTcpStream",
+ sizeof (CamelTcpStream),
+ sizeof (CamelTcpStreamClass),
+ (CamelObjectClassInitFunc) camel_tcp_stream_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_tcp_stream_init,
+ NULL);
+ }
+
+ return type;
+}
+
+
+static int
+tcp_connect (CamelTcpStream *stream, struct addrinfo *host)
+{
+ w(g_warning ("CamelTcpStream::connect called on default implementation"));
+ return -1;
+}
+
+/**
+ * camel_tcp_stream_connect:
+ * @stream: a CamelTcpStream object.
+ * @host: A linked list of addrinfo structures to try to connect, in
+ * the order of most likely to least likely to work.
+ *
+ * Create a socket and connect based upon the data provided.
+ *
+ * Return value: zero on success or -1 on fail.
+ **/
+int
+camel_tcp_stream_connect (CamelTcpStream *stream, struct addrinfo *host)
+{
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), -1);
+
+ return CTS_CLASS (stream)->connect (stream, host);
+}
+
+static int
+tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
+{
+ w(g_warning ("CamelTcpStream::getsockopt called on default implementation"));
+ return -1;
+}
+
+/**
+ * camel_tcp_stream_getsockopt:
+ * @stream: tcp stream object
+ * @data: socket option data
+ *
+ * Get the socket options set on the stream and populate #data.
+ *
+ * Return value: zero on success or -1 on fail.
+ **/
+int
+camel_tcp_stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), -1);
+
+ return CTS_CLASS (stream)->getsockopt (stream, data);
+}
+
+static int
+tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
+{
+ w(g_warning ("CamelTcpStream::setsockopt called on default implementation"));
+ return -1;
+}
+
+/**
+ * camel_tcp_stream_setsockopt:
+ * @stream: tcp stream object
+ * @data: socket option data
+ *
+ * Set the socket options contained in #data on the stream.
+ *
+ * Return value: zero on success or -1 on fail.
+ **/
+int
+camel_tcp_stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
+{
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), -1);
+
+ return CTS_CLASS (stream)->setsockopt (stream, data);
+}
+
+static struct sockaddr *
+tcp_get_local_address (CamelTcpStream *stream, socklen_t *len)
+{
+ w(g_warning ("CamelTcpStream::get_local_address called on default implementation"));
+ return NULL;
+}
+
+/**
+ * camel_tcp_stream_get_local_address:
+ * @stream: tcp stream object
+ * @len: Pointer to address length which must be supplied.
+ *
+ * Get the local address of @stream.
+ *
+ * Return value: the stream's local address (which must be freed with
+ * g_free()) if the stream is connected, or %NULL if not.
+ **/
+struct sockaddr *
+camel_tcp_stream_get_local_address (CamelTcpStream *stream, socklen_t *len)
+{
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), NULL);
+ g_return_val_if_fail(len != NULL, NULL);
+
+ return CTS_CLASS (stream)->get_local_address (stream, len);
+}
+
+static struct sockaddr *
+tcp_get_remote_address (CamelTcpStream *stream, socklen_t *len)
+{
+ w(g_warning ("CamelTcpStream::get_remote_address called on default implementation"));
+ return NULL;
+}
+
+/**
+ * camel_tcp_stream_get_remote_address:
+ * @stream: tcp stream object
+ * @len: Pointer to address length, which must be supplied.
+ *
+ * Get the remote address of @stream.
+ *
+ * Return value: the stream's remote address (which must be freed with
+ * g_free()) if the stream is connected, or %NULL if not.
+ **/
+struct sockaddr *
+camel_tcp_stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
+{
+ g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), NULL);
+ g_return_val_if_fail(len != NULL, NULL);
+
+ return CTS_CLASS (stream)->get_remote_address (stream, len);
+}
diff --git a/camel/camel-tcp-stream.h b/camel/camel-tcp-stream.h
new file mode 100644
index 0000000000..b7be61dbec
--- /dev/null
+++ b/camel/camel-tcp-stream.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifndef CAMEL_TCP_STREAM_H
+#define CAMEL_TCP_STREAM_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus }*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include <camel/camel-stream.h>
+
+#define CAMEL_TCP_STREAM_TYPE (camel_tcp_stream_get_type ())
+#define CAMEL_TCP_STREAM(obj) (CAMEL_CHECK_CAST((obj), CAMEL_TCP_STREAM_TYPE, CamelTcpStream))
+#define CAMEL_TCP_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_TCP_STREAM_TYPE, CamelTcpStreamClass))
+#define CAMEL_IS_TCP_STREAM(o) (CAMEL_CHECK_TYPE((o), CAMEL_TCP_STREAM_TYPE))
+
+typedef enum {
+ CAMEL_SOCKOPT_NONBLOCKING, /* nonblocking io */
+ CAMEL_SOCKOPT_LINGER, /* linger on close if data present */
+ CAMEL_SOCKOPT_REUSEADDR, /* allow local address reuse */
+ CAMEL_SOCKOPT_KEEPALIVE, /* keep connections alive */
+ CAMEL_SOCKOPT_RECVBUFFERSIZE, /* receive buffer size */
+ CAMEL_SOCKOPT_SENDBUFFERSIZE, /* send buffer size */
+
+ CAMEL_SOCKOPT_IPTIMETOLIVE, /* time to live */
+ CAMEL_SOCKOPT_IPTYPEOFSERVICE, /* type of service and precedence */
+
+ CAMEL_SOCKOPT_ADDMEMBER, /* add an IP group membership */
+ CAMEL_SOCKOPT_DROPMEMBER, /* drop an IP group membership */
+ CAMEL_SOCKOPT_MCASTINTERFACE, /* multicast interface address */
+ CAMEL_SOCKOPT_MCASTTIMETOLIVE, /* multicast timetolive */
+ CAMEL_SOCKOPT_MCASTLOOPBACK, /* multicast loopback */
+
+ CAMEL_SOCKOPT_NODELAY, /* don't delay send to coalesce packets */
+ CAMEL_SOCKOPT_MAXSEGMENT, /* maximum segment size */
+ CAMEL_SOCKOPT_BROADCAST, /* enable broadcast */
+ CAMEL_SOCKOPT_LAST
+} CamelSockOpt;
+
+typedef struct linger CamelLinger;
+
+typedef struct _CamelSockOptData {
+ CamelSockOpt option;
+ union {
+ guint ip_ttl; /* IP time to live */
+ guint mcast_ttl; /* IP multicast time to live */
+ guint tos; /* IP type of service and precedence */
+ gboolean non_blocking; /* Non-blocking (network) I/O */
+ gboolean reuse_addr; /* Allow local address reuse */
+ gboolean keep_alive; /* Keep connections alive */
+ gboolean mcast_loopback; /* IP multicast loopback */
+ gboolean no_delay; /* Don't delay send to coalesce packets */
+ gboolean broadcast; /* Enable broadcast */
+ size_t max_segment; /* Maximum segment size */
+ size_t recv_buffer_size; /* Receive buffer size */
+ size_t send_buffer_size; /* Send buffer size */
+ CamelLinger linger; /* Time to linger on close if data present */
+ } value;
+} CamelSockOptData;
+
+struct _CamelTcpStream {
+ CamelStream parent_object;
+
+};
+
+typedef struct {
+ CamelStreamClass parent_class;
+
+ /* Virtual methods */
+ int (*connect) (CamelTcpStream *stream, struct addrinfo *host);
+ int (*getsockopt) (CamelTcpStream *stream, CamelSockOptData *data);
+ int (*setsockopt) (CamelTcpStream *stream, const CamelSockOptData *data);
+
+ struct sockaddr * (*get_local_address) (CamelTcpStream *stream, socklen_t *len);
+ struct sockaddr * (*get_remote_address) (CamelTcpStream *stream, socklen_t *len);
+} CamelTcpStreamClass;
+
+/* Standard Camel function */
+CamelType camel_tcp_stream_get_type (void);
+
+/* public methods */
+int camel_tcp_stream_connect (CamelTcpStream *stream, struct addrinfo *host);
+int camel_tcp_stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
+int camel_tcp_stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
+
+struct sockaddr *camel_tcp_stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
+struct sockaddr *camel_tcp_stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_TCP_STREAM_H */
diff --git a/camel/camel-uid-cache.c b/camel/camel-uid-cache.c
new file mode 100644
index 0000000000..f884c11851
--- /dev/null
+++ b/camel/camel-uid-cache.c
@@ -0,0 +1,334 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-uid-cache.c: UID caching code. */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ *
+ * Copyright 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "camel-uid-cache.h"
+#include "camel-file-utils.h"
+
+struct _uid_state {
+ int level;
+ gboolean save;
+};
+
+
+/**
+ * camel_uid_cache_new:
+ * @filename: path to load the cache from
+ *
+ * Creates a new UID cache, initialized from @filename. If @filename
+ * doesn't already exist, the UID cache will be empty. Otherwise, if
+ * it does exist but can't be read, the function will return %NULL.
+ *
+ * Return value: a new UID cache, or %NULL
+ **/
+CamelUIDCache *
+camel_uid_cache_new (const char *filename)
+{
+ CamelUIDCache *cache;
+ struct stat st;
+ char *dirname, *buf, **uids;
+ int fd, i;
+
+ dirname = g_path_get_dirname (filename);
+ if (camel_mkdir (dirname, 0777) == -1) {
+ g_free (dirname);
+ return NULL;
+ }
+
+ g_free (dirname);
+
+ if ((fd = open (filename, O_RDONLY | O_CREAT, 0666)) == -1)
+ return NULL;
+
+ if (fstat (fd, &st) == -1) {
+ close (fd);
+ return NULL;
+ }
+
+ buf = g_malloc (st.st_size + 1);
+
+ if (st.st_size > 0 && camel_read (fd, buf, st.st_size) == -1) {
+ close (fd);
+ g_free (buf);
+ return NULL;
+ }
+
+ buf[st.st_size] = '\0';
+
+ close (fd);
+
+ cache = g_new (CamelUIDCache, 1);
+ cache->uids = g_hash_table_new (g_str_hash, g_str_equal);
+ cache->filename = g_strdup (filename);
+ cache->level = 1;
+ cache->expired = 0;
+ cache->size = 0;
+ cache->fd = -1;
+
+ uids = g_strsplit (buf, "\n", 0);
+ g_free (buf);
+ for (i = 0; uids[i]; i++) {
+ struct _uid_state *state;
+
+ state = g_new (struct _uid_state, 1);
+ state->level = cache->level;
+ state->save = TRUE;
+
+ g_hash_table_insert (cache->uids, uids[i], state);
+ }
+
+ g_free (uids);
+
+ return cache;
+}
+
+
+static void
+maybe_write_uid (gpointer key, gpointer value, gpointer data)
+{
+ CamelUIDCache *cache = data;
+ struct _uid_state *state = value;
+
+ if (cache->fd == -1)
+ return;
+
+ if (state && state->level == cache->level && state->save) {
+ if (camel_write (cache->fd, key, strlen (key)) == -1 ||
+ camel_write (cache->fd, "\n", 1) == -1) {
+ cache->fd = -1;
+ } else {
+ cache->size += strlen (key) + 1;
+ }
+ } else {
+ /* keep track of how much space the expired uids would
+ * have taken up in the cache */
+ cache->expired += strlen (key) + 1;
+ }
+}
+
+
+/**
+ * camel_uid_cache_save:
+ * @cache: a CamelUIDCache
+ *
+ * Attempts to save @cache back to disk.
+ *
+ * Return value: success or failure
+ **/
+gboolean
+camel_uid_cache_save (CamelUIDCache *cache)
+{
+ char *filename;
+ int errnosav;
+ int fd;
+
+ filename = g_strdup_printf ("%s~", cache->filename);
+ if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) {
+ g_free (filename);
+ return FALSE;
+ }
+
+ cache->fd = fd;
+ cache->size = 0;
+ cache->expired = 0;
+ g_hash_table_foreach (cache->uids, maybe_write_uid, cache);
+
+ if (cache->fd == -1)
+ goto exception;
+
+ if (fsync (fd) == -1)
+ goto exception;
+
+ close (fd);
+ fd = -1;
+
+ if (rename (filename, cache->filename) == -1)
+ goto exception;
+
+ g_free (filename);
+
+ return TRUE;
+
+ exception:
+
+ errnosav = errno;
+
+#ifdef ENABLE_SPASMOLYTIC
+ if (fd != -1) {
+ /**
+ * If our new cache size is larger than the old cache,
+ * even if we haven't finished writing it out
+ * successfully, we should still attempt to replace
+ * the old cache with the new cache because it will at
+ * least avoid re-downloading a few extra messages
+ * than if we just kept the old cache.
+ *
+ * Similarly, even if the new cache size is smaller
+ * than the old cache size, but we've expired enough
+ * uids to make up for the difference in size (or
+ * more), then we should replace the old cache with
+ * the new cache as well.
+ **/
+
+ if (stat (cache->filename, &st) == 0 &&
+ (cache->size > st.st_size || cache->size + cache->expired > st.st_size)) {
+ if (ftruncate (fd, (off_t) cache->size) != -1) {
+ cache->size = 0;
+ cache->expired = 0;
+ goto overwrite;
+ }
+ }
+
+ close (fd);
+ }
+#endif
+
+ unlink (filename);
+ g_free (filename);
+
+ errno = errnosav;
+
+ return FALSE;
+}
+
+
+static void
+free_uid (gpointer key, gpointer value, gpointer data)
+{
+ g_free (key);
+ g_free (value);
+}
+
+
+/**
+ * camel_uid_cache_destroy:
+ * @cache: a CamelUIDCache
+ *
+ * Destroys @cache and frees its data.
+ **/
+void
+camel_uid_cache_destroy (CamelUIDCache *cache)
+{
+ g_hash_table_foreach (cache->uids, free_uid, NULL);
+ g_hash_table_destroy (cache->uids);
+ g_free (cache->filename);
+ g_free (cache);
+}
+
+
+/**
+ * camel_uid_cache_get_new_uids:
+ * @cache: a CamelUIDCache
+ * @uids: an array of UIDs
+ *
+ * Returns an array of UIDs from @uids that are not in @cache, and
+ * removes UIDs from @cache that aren't in @uids.
+ *
+ * Return value: an array of new UIDs, which must be freed with
+ * camel_uid_cache_free_uids().
+ **/
+GPtrArray *
+camel_uid_cache_get_new_uids (CamelUIDCache *cache, GPtrArray *uids)
+{
+ GPtrArray *new_uids;
+ gpointer old_uid;
+ char *uid;
+ int i;
+
+ new_uids = g_ptr_array_new ();
+ cache->level++;
+
+ for (i = 0; i < uids->len; i++) {
+ struct _uid_state *state;
+
+ uid = uids->pdata[i];
+ if (g_hash_table_lookup_extended (cache->uids, uid, (void **)&old_uid, (void **)&state)) {
+ g_hash_table_remove (cache->uids, uid);
+ g_free (old_uid);
+ } else {
+ g_ptr_array_add (new_uids, g_strdup (uid));
+ state = g_new (struct _uid_state, 1);
+ state->save = FALSE;
+ }
+
+ state->level = cache->level;
+ g_hash_table_insert (cache->uids, g_strdup (uid), state);
+ }
+
+ return new_uids;
+}
+
+
+/**
+ * camel_uid_cache_save_uid:
+ * @cache: a CamelUIDCache
+ * @uid: a uid to save
+ *
+ * Marks a uid for saving.
+ **/
+void
+camel_uid_cache_save_uid (CamelUIDCache *cache, const char *uid)
+{
+ struct _uid_state *state;
+ gpointer old_uid;
+
+ g_return_if_fail (uid != NULL);
+
+ if (g_hash_table_lookup_extended (cache->uids, uid, (void **)&old_uid, (void **)&state)) {
+ state->save = TRUE;
+ state->level = cache->level;
+ } else {
+ state = g_new (struct _uid_state, 1);
+ state->save = TRUE;
+ state->level = cache->level;
+
+ g_hash_table_insert (cache->uids, g_strdup (uid), state);
+ }
+}
+
+
+/**
+ * camel_uid_cache_free_uids:
+ * @uids: an array returned from camel_uid_cache_get_new_uids()
+ *
+ * Frees the array of UIDs.
+ **/
+void
+camel_uid_cache_free_uids (GPtrArray *uids)
+{
+ int i;
+
+ for (i = 0; i < uids->len; i++)
+ g_free (uids->pdata[i]);
+ g_ptr_array_free (uids, TRUE);
+}
diff --git a/camel/camel-url.c b/camel/camel-url.c
new file mode 100644
index 0000000000..f5df28da95
--- /dev/null
+++ b/camel/camel-url.c
@@ -0,0 +1,592 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-url.c : utility functions to parse URLs */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 1999-2001 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "camel-url.h"
+#include "camel-exception.h"
+#include "camel-mime-utils.h"
+#include "camel-object.h"
+#include "camel-string-utils.h"
+
+static void copy_param (GQuark key_id, gpointer data, gpointer user_data);
+static void output_param (GQuark key_id, gpointer data, gpointer user_data);
+
+static void append_url_encoded (GString *str, const char *in, const char *extra_enc_chars);
+
+/**
+ * camel_url_new_with_base:
+ * @base: a base URL
+ * @url_string: the URL
+ *
+ * Parses @url_string relative to @base.
+ *
+ * Return value: a parsed CamelURL.
+ **/
+CamelURL *
+camel_url_new_with_base (CamelURL *base, const char *url_string)
+{
+ CamelURL *url;
+ const char *end, *hash, *colon, *semi, *at, *slash, *question;
+ const char *p;
+
+ url = g_new0 (CamelURL, 1);
+
+ /* See RFC1808 for details. IF YOU CHANGE ANYTHING IN THIS
+ * FUNCTION, RUN tests/misc/url AFTERWARDS.
+ */
+
+ /* Find fragment. RFC 1808 2.4.1 */
+ end = hash = strchr (url_string, '#');
+ if (hash) {
+ if (hash[1]) {
+ url->fragment = g_strdup (hash + 1);
+ camel_url_decode (url->fragment);
+ }
+ } else
+ end = url_string + strlen (url_string);
+
+ /* Find protocol: initial [a-z+.-]* substring until ":" */
+ p = url_string;
+ while (p < end && (isalnum ((unsigned char)*p) ||
+ *p == '.' || *p == '+' || *p == '-'))
+ p++;
+
+ if (p > url_string && *p == ':') {
+ url->protocol = g_strndup (url_string, p - url_string);
+ camel_strdown (url->protocol);
+ url_string = p + 1;
+ }
+
+ if (!*url_string && !base)
+ return url;
+
+ /* Check for authority */
+ if (strncmp (url_string, "//", 2) == 0) {
+ url_string += 2;
+
+ slash = url_string + strcspn (url_string, "/#");
+ at = strchr (url_string, '@');
+ if (at && at < slash) {
+ colon = strchr (url_string, ':');
+ if (colon && colon < at) {
+ url->passwd = g_strndup (colon + 1,
+ at - colon - 1);
+ camel_url_decode (url->passwd);
+ } else {
+ url->passwd = NULL;
+ colon = at;
+ }
+
+ semi = strchr(url_string, ';');
+ if (semi && semi < colon &&
+ !strncasecmp (semi, ";auth=", 6)) {
+ url->authmech = g_strndup (semi + 6,
+ colon - semi - 6);
+ camel_url_decode (url->authmech);
+ } else {
+ url->authmech = NULL;
+ semi = colon;
+ }
+
+ url->user = g_strndup (url_string, semi - url_string);
+ camel_url_decode (url->user);
+ url_string = at + 1;
+ } else
+ url->user = url->passwd = url->authmech = NULL;
+
+ /* Find host and port. */
+ colon = strchr (url_string, ':');
+ if (colon && colon < slash) {
+ url->host = g_strndup (url_string, colon - url_string);
+ url->port = strtoul (colon + 1, NULL, 10);
+ } else {
+ url->host = g_strndup (url_string, slash - url_string);
+ camel_url_decode (url->host);
+ url->port = 0;
+ }
+
+ url_string = slash;
+ }
+
+ /* Find query */
+ question = memchr (url_string, '?', end - url_string);
+ if (question) {
+ if (question[1]) {
+ url->query = g_strndup (question + 1,
+ end - (question + 1));
+ camel_url_decode (url->query);
+ }
+ end = question;
+ }
+
+ /* Find parameters */
+ semi = memchr (url_string, ';', end - url_string);
+ if (semi) {
+ if (semi[1]) {
+ const char *cur, *p, *eq;
+ char *name, *value;
+
+ for (cur = semi + 1; cur < end; cur = p + 1) {
+ p = memchr (cur, ';', end - cur);
+ if (!p)
+ p = end;
+ eq = memchr (cur, '=', p - cur);
+ if (eq) {
+ name = g_strndup (cur, eq - cur);
+ value = g_strndup (eq + 1, p - (eq + 1));
+ camel_url_decode (value);
+ } else {
+ name = g_strndup (cur, p - cur);
+ value = g_strdup ("");
+ }
+ camel_url_decode (name);
+ g_datalist_set_data_full (&url->params, name,
+ value, g_free);
+ g_free (name);
+ }
+ }
+ end = semi;
+ }
+
+ if (end != url_string) {
+ url->path = g_strndup (url_string, end - url_string);
+ camel_url_decode (url->path);
+ }
+
+ /* Apply base URL. Again, this is spelled out in RFC 1808. */
+ if (base && !url->protocol && url->host)
+ url->protocol = g_strdup (base->protocol);
+ else if (base && !url->protocol) {
+ if (!url->user && !url->authmech && !url->passwd &&
+ !url->host && !url->port && !url->path &&
+ !url->params && !url->query && !url->fragment)
+ url->fragment = g_strdup (base->fragment);
+
+ url->protocol = g_strdup (base->protocol);
+ url->user = g_strdup (base->user);
+ url->authmech = g_strdup (base->authmech);
+ url->passwd = g_strdup (base->passwd);
+ url->host = g_strdup (base->host);
+ url->port = base->port;
+
+ if (!url->path) {
+ url->path = g_strdup (base->path);
+ if (!url->params) {
+ g_datalist_foreach (&base->params, copy_param,
+ &url->params);
+ if (!url->query)
+ url->query = g_strdup (base->query);
+ }
+ } else if (*url->path != '/') {
+ char *newpath, *last, *p, *q;
+
+ last = strrchr (base->path, '/');
+ if (last) {
+ newpath = g_strdup_printf ("%.*s/%s",
+ (int)(last - base->path),
+ base->path,
+ url->path);
+ } else
+ newpath = g_strdup_printf ("/%s", url->path);
+
+ /* Remove "./" where "." is a complete segment. */
+ for (p = newpath + 1; *p; ) {
+ if (*(p - 1) == '/' &&
+ *p == '.' && *(p + 1) == '/')
+ memmove (p, p + 2, strlen (p + 2) + 1);
+ else
+ p++;
+ }
+ /* Remove "." at end. */
+ if (p > newpath + 2 &&
+ *(p - 1) == '.' && *(p - 2) == '/')
+ *(p - 1) = '\0';
+ /* Remove "<segment>/../" where <segment> != ".." */
+ for (p = newpath + 1; *p; ) {
+ if (!strncmp (p, "../", 3)) {
+ p += 3;
+ continue;
+ }
+ q = strchr (p + 1, '/');
+ if (!q)
+ break;
+ if (strncmp (q, "/../", 4) != 0) {
+ p = q + 1;
+ continue;
+ }
+ memmove (p, q + 4, strlen (q + 4) + 1);
+ p = newpath + 1;
+ }
+ /* Remove "<segment>/.." at end */
+ q = strrchr (newpath, '/');
+ if (q && !strcmp (q, "/..")) {
+ p = q - 1;
+ while (p > newpath && *p != '/')
+ p--;
+ if (strncmp (p, "/../", 4) != 0)
+ *(p + 1) = 0;
+ }
+ g_free (url->path);
+ url->path = newpath;
+ }
+ }
+
+ return url;
+}
+
+static void
+copy_param (GQuark key_id, gpointer data, gpointer user_data)
+{
+ GData **copy = user_data;
+
+ g_datalist_id_set_data_full (copy, key_id, g_strdup (data), g_free);
+}
+
+/**
+ * camel_url_new:
+ * @url_string: a URL
+ * @ex: a CamelException
+ *
+ * Parses an absolute URL.
+ *
+ * Return value: a CamelURL, or %NULL.
+ **/
+CamelURL *
+camel_url_new (const char *url_string, CamelException *ex)
+{
+ CamelURL *url = camel_url_new_with_base (NULL, url_string);
+
+ if (!url->protocol) {
+ camel_url_free (url);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
+ _("Could not parse URL `%s'"),
+ url_string);
+ return NULL;
+ }
+ return url;
+}
+
+/**
+ * camel_url_to_string:
+ * @url: a CamelURL
+ * @flags: additional translation options.
+ *
+ * Return value: a string representing @url, which the caller must free.
+ **/
+char *
+camel_url_to_string (CamelURL *url, guint32 flags)
+{
+ GString *str;
+ char *return_result;
+
+ /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN
+ * tests/misc/url AFTERWARD.
+ */
+
+ str = g_string_sized_new (20);
+
+ if (url->protocol)
+ g_string_append_printf (str, "%s:", url->protocol);
+
+ if (url->host) {
+ g_string_append (str, "//");
+ if (url->user) {
+ append_url_encoded (str, url->user, ":;@/");
+ if (url->authmech && *url->authmech) {
+ g_string_append (str, ";auth=");
+ append_url_encoded (str, url->authmech, ":@/");
+ }
+ if (url->passwd && !(flags & CAMEL_URL_HIDE_PASSWORD)) {
+ g_string_append_c (str, ':');
+ append_url_encoded (str, url->passwd, "@/");
+ }
+ g_string_append_c (str, '@');
+ }
+ append_url_encoded (str, url->host, ":/");
+ if (url->port)
+ g_string_append_printf (str, ":%d", url->port);
+ if (!url->path && (url->params || url->query || url->fragment))
+ g_string_append_c (str, '/');
+ }
+
+ if (url->path)
+ append_url_encoded (str, url->path, ";?");
+ if (url->params && !(flags & CAMEL_URL_HIDE_PARAMS))
+ g_datalist_foreach (&url->params, output_param, str);
+ if (url->query) {
+ g_string_append_c (str, '?');
+ append_url_encoded (str, url->query, NULL);
+ }
+ if (url->fragment) {
+ g_string_append_c (str, '#');
+ append_url_encoded (str, url->fragment, NULL);
+ }
+
+ return_result = str->str;
+ g_string_free (str, FALSE);
+
+ return return_result;
+}
+
+static void
+output_param (GQuark key_id, gpointer data, gpointer user_data)
+{
+ GString *str = user_data;
+
+ g_string_append_c (str, ';');
+ append_url_encoded (str, g_quark_to_string (key_id), "?=");
+ if (*(char *)data) {
+ g_string_append_c (str, '=');
+ append_url_encoded (str, data, "?");
+ }
+}
+
+/**
+ * camel_url_free:
+ * @url: a CamelURL
+ *
+ * Frees @url
+ **/
+void
+camel_url_free (CamelURL *url)
+{
+ if (url) {
+ if (url->passwd)
+ memset(url->passwd, 0, strlen(url->passwd));
+ if (url->user)
+ memset(url->user, 0, strlen(url->user));
+ if (url->host)
+ memset(url->host, 0, strlen(url->host));
+ g_free (url->protocol);
+ g_free (url->user);
+ g_free (url->authmech);
+ g_free (url->passwd);
+ g_free (url->host);
+ g_free (url->path);
+ g_datalist_clear (&url->params);
+ g_free (url->query);
+ g_free (url->fragment);
+
+ g_free (url);
+ }
+}
+
+
+#define DEFINE_CAMEL_URL_SET(part) \
+void \
+camel_url_set_##part (CamelURL *url, const char *part) \
+{ \
+ g_free (url->part); \
+ url->part = g_strdup (part); \
+}
+
+DEFINE_CAMEL_URL_SET (protocol)
+DEFINE_CAMEL_URL_SET (user)
+DEFINE_CAMEL_URL_SET (authmech)
+DEFINE_CAMEL_URL_SET (passwd)
+DEFINE_CAMEL_URL_SET (host)
+DEFINE_CAMEL_URL_SET (path)
+DEFINE_CAMEL_URL_SET (query)
+DEFINE_CAMEL_URL_SET (fragment)
+
+void
+camel_url_set_port (CamelURL *url, int port)
+{
+ url->port = port;
+}
+
+void
+camel_url_set_param (CamelURL *url, const char *name, const char *value)
+{
+ g_datalist_set_data_full (&url->params, name, value ? g_strdup (value) : NULL, g_free);
+}
+
+const char *
+camel_url_get_param (CamelURL *url, const char *name)
+{
+ return g_datalist_get_data (&url->params, name);
+}
+
+/* From RFC 2396 2.4.3, the characters that should always be encoded */
+static const char url_encoded_char[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 - 0x0f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 - 0x1f */
+ 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ' ' - '/' */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* '0' - '?' */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* '@' - 'O' */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 'P' - '_' */
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* '`' - 'o' */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* 'p' - 0x7f */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static void
+append_url_encoded (GString *str, const char *in, const char *extra_enc_chars)
+{
+ const unsigned char *s = (const unsigned char *)in;
+
+ while (*s) {
+ if (url_encoded_char[*s] ||
+ (extra_enc_chars && strchr (extra_enc_chars, *s)))
+ g_string_append_printf (str, "%%%02x", (int)*s++);
+ else
+ g_string_append_c (str, *s++);
+ }
+}
+
+/**
+ * camel_url_encode:
+ * @part: a URL part
+ * @escape_extra: additional characters beyond " \"%#<>{}|\^[]`"
+ * to escape (or %NULL)
+ *
+ * This %-encodes the given URL part and returns the escaped version
+ * in allocated memory, which the caller must free when it is done.
+ **/
+char *
+camel_url_encode (const char *part, const char *escape_extra)
+{
+ GString *str;
+ char *encoded;
+
+ str = g_string_new (NULL);
+ append_url_encoded (str, part, escape_extra);
+ encoded = str->str;
+ g_string_free (str, FALSE);
+
+ return encoded;
+}
+
+/**
+ * camel_url_decode:
+ * @part: a URL part
+ *
+ * %-decodes the passed-in URL *in place*. The decoded version is
+ * never longer than the encoded version, so there does not need to
+ * be any additional space at the end of the string.
+ */
+void
+camel_url_decode (char *part)
+{
+ unsigned char *s, *d;
+
+#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10)
+
+ s = d = (unsigned char *)part;
+ do {
+ if (*s == '%' && isxdigit(s[1]) && isxdigit(s[2])) {
+ *d++ = (XDIGIT (s[1]) << 4) + XDIGIT (s[2]);
+ s += 2;
+ } else
+ *d++ = *s;
+ } while (*s++);
+}
+
+
+guint
+camel_url_hash (const void *v)
+{
+ const CamelURL *u = v;
+ guint hash = 0;
+
+#define ADD_HASH(s) if (s) hash ^= g_str_hash (s);
+
+ ADD_HASH (u->protocol);
+ ADD_HASH (u->user);
+ ADD_HASH (u->authmech);
+ ADD_HASH (u->host);
+ ADD_HASH (u->path);
+ ADD_HASH (u->query);
+ hash ^= u->port;
+
+ return hash;
+}
+
+static int
+check_equal (char *s1, char *s2)
+{
+ if (s1 == NULL) {
+ if (s2 == NULL)
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ if (s2 == NULL)
+ return FALSE;
+
+ return strcmp (s1, s2) == 0;
+}
+
+int
+camel_url_equal(const void *v, const void *v2)
+{
+ const CamelURL *u1 = v, *u2 = v2;
+
+ return check_equal(u1->protocol, u2->protocol)
+ && check_equal(u1->user, u2->user)
+ && check_equal(u1->authmech, u2->authmech)
+ && check_equal(u1->host, u2->host)
+ && check_equal(u1->path, u2->path)
+ && check_equal(u1->query, u2->query)
+ && u1->port == u2->port;
+}
+
+CamelURL *
+camel_url_copy(const CamelURL *in)
+{
+ CamelURL *out;
+
+ out = g_malloc(sizeof(*out));
+ out->protocol = g_strdup(in->protocol);
+ out->user = g_strdup(in->user);
+ out->authmech = g_strdup(in->authmech);
+ out->passwd = g_strdup(in->passwd);
+ out->host = g_strdup(in->host);
+ out->port = in->port;
+ out->path = g_strdup(in->path);
+ out->params = NULL;
+ if (in->params)
+ g_datalist_foreach(&((CamelURL *)in)->params, copy_param, &out->params);
+ out->query = g_strdup(in->query);
+ out->fragment = g_strdup(in->fragment);
+
+ return out;
+}
diff --git a/camel/camel-vee-folder.c b/camel/camel-vee-folder.c
new file mode 100644
index 0000000000..75beefa288
--- /dev/null
+++ b/camel/camel-vee-folder.c
@@ -0,0 +1,1789 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <pthread.h>
+
+#include "camel-exception.h"
+#include "camel-vee-folder.h"
+#include "camel-store.h"
+#include "camel-folder-summary.h"
+#include "camel-mime-message.h"
+#include "camel-folder-search.h"
+
+#include "camel-session.h"
+#include "camel-vee-store.h" /* for open flags */
+#include "camel-private.h"
+#include "camel-debug.h"
+
+#include "e-util/md5-utils.h"
+
+#if defined (DOEPOOLV) || defined (DOESTRV)
+#include "e-util/e-memory.h"
+#endif
+
+#define d(x)
+#define dd(x) (camel_debug("vfolder")?(x):0)
+
+#define _PRIVATE(o) (((CamelVeeFolder *)(o))->priv)
+
+static void vee_refresh_info(CamelFolder *folder, CamelException *ex);
+
+static void vee_sync (CamelFolder *folder, gboolean expunge, CamelException *ex);
+static void vee_expunge (CamelFolder *folder, CamelException *ex);
+
+static void vee_freeze(CamelFolder *folder);
+static void vee_thaw(CamelFolder *folder);
+
+static CamelMimeMessage *vee_get_message (CamelFolder *folder, const gchar *uid, CamelException *ex);
+static void vee_append_message(CamelFolder *folder, CamelMimeMessage *message, const CamelMessageInfo *info, char **appended_uid, CamelException *ex);
+static void vee_transfer_messages_to(CamelFolder *source, GPtrArray *uids, CamelFolder *dest, GPtrArray **transferred_uids, gboolean delete_originals, CamelException *ex);
+
+static GPtrArray *vee_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex);
+static GPtrArray *vee_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex);
+
+static gboolean vee_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
+static void vee_set_message_user_flag (CamelFolder *folder, const char *uid, const char *name, gboolean value);
+static void vee_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value);
+static void vee_rename(CamelFolder *folder, const char *new);
+
+static void camel_vee_folder_class_init (CamelVeeFolderClass *klass);
+static void camel_vee_folder_init (CamelVeeFolder *obj);
+static void camel_vee_folder_finalise (CamelObject *obj);
+
+static int vee_folder_build_folder(CamelVeeFolder *vf, CamelFolder *source, CamelException *ex);
+static void vee_folder_remove_folder(CamelVeeFolder *vf, CamelFolder *source, int killun);
+
+static void folder_changed(CamelFolder *sub, CamelFolderChangeInfo *changes, CamelVeeFolder *vf);
+static void subfolder_deleted(CamelFolder *f, void *event_data, CamelVeeFolder *vf);
+static void subfolder_renamed(CamelFolder *f, void *event_data, CamelVeeFolder *vf);
+
+static void folder_changed_remove_uid(CamelFolder *sub, const char *uid, const char hash[8], int keep, CamelVeeFolder *vf);
+
+static CamelFolderClass *camel_vee_folder_parent;
+
+CamelType
+camel_vee_folder_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_folder_get_type (), "CamelVeeFolder",
+ sizeof (CamelVeeFolder),
+ sizeof (CamelVeeFolderClass),
+ (CamelObjectClassInitFunc) camel_vee_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_vee_folder_init,
+ (CamelObjectFinalizeFunc) camel_vee_folder_finalise);
+ }
+
+ return type;
+}
+
+static void
+camel_vee_folder_class_init (CamelVeeFolderClass *klass)
+{
+ CamelFolderClass *folder_class = (CamelFolderClass *) klass;
+
+ camel_vee_folder_parent = CAMEL_FOLDER_CLASS(camel_type_get_global_classfuncs (camel_folder_get_type ()));
+
+ folder_class->refresh_info = vee_refresh_info;
+ folder_class->sync = vee_sync;
+ folder_class->expunge = vee_expunge;
+
+ folder_class->get_message = vee_get_message;
+ folder_class->append_message = vee_append_message;
+ folder_class->transfer_messages_to = vee_transfer_messages_to;
+
+ folder_class->search_by_expression = vee_search_by_expression;
+ folder_class->search_by_uids = vee_search_by_uids;
+
+ folder_class->set_message_flags = vee_set_message_flags;
+ folder_class->set_message_user_flag = vee_set_message_user_flag;
+ folder_class->set_message_user_tag = vee_set_message_user_tag;
+
+ folder_class->rename = vee_rename;
+
+ folder_class->freeze = vee_freeze;
+ folder_class->thaw = vee_thaw;
+}
+
+static void
+camel_vee_folder_init (CamelVeeFolder *obj)
+{
+ struct _CamelVeeFolderPrivate *p;
+ CamelFolder *folder = (CamelFolder *)obj;
+
+ p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
+
+ folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY |
+ CAMEL_FOLDER_HAS_SEARCH_CAPABILITY);
+
+ /* FIXME: what to do about user flags if the subfolder doesn't support them? */
+ folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
+ CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_DRAFT |
+ CAMEL_MESSAGE_FLAGGED |
+ CAMEL_MESSAGE_SEEN;
+
+ obj->changes = camel_folder_change_info_new();
+ obj->search = camel_folder_search_new();
+
+ p->summary_lock = g_mutex_new();
+ p->subfolder_lock = g_mutex_new();
+ p->changed_lock = g_mutex_new();
+}
+
+static void
+camel_vee_folder_finalise (CamelObject *obj)
+{
+ CamelVeeFolder *vf = (CamelVeeFolder *)obj;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GList *node;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+
+ /* FIXME: check leaks */
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+
+ if (vf != folder_unmatched) {
+ camel_object_unhook_event((CamelObject *)f, "folder_changed", (CamelObjectEventHookFunc) folder_changed, vf);
+ camel_object_unhook_event((CamelObject *)f, "deleted", (CamelObjectEventHookFunc) subfolder_deleted, vf);
+ camel_object_unhook_event((CamelObject *)f, "renamed", (CamelObjectEventHookFunc) subfolder_renamed, vf);
+ /* this updates the vfolder */
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0)
+ vee_folder_remove_folder(vf, f, FALSE);
+ }
+ camel_object_unref((CamelObject *)f);
+
+ node = g_list_next(node);
+ }
+
+ g_free(vf->expression);
+ g_free(vf->vname);
+
+ g_list_free(p->folders);
+ g_list_free(p->folders_changed);
+
+ camel_folder_change_info_free(vf->changes);
+ camel_object_unref((CamelObject *)vf->search);
+
+ g_mutex_free(p->summary_lock);
+ g_mutex_free(p->subfolder_lock);
+ g_mutex_free(p->changed_lock);
+
+ g_free(p);
+}
+
+void
+camel_vee_folder_construct (CamelVeeFolder *vf, CamelStore *parent_store, const char *full, const char *name, guint32 flags)
+{
+ CamelFolder *folder = (CamelFolder *)vf;
+
+ vf->flags = flags;
+ vf->vname = g_strdup(full);
+ camel_folder_construct(folder, parent_store, full, name);
+
+ /* should CamelVeeMessageInfo be subclassable ..? */
+ folder->summary = camel_folder_summary_new();
+ folder->summary->message_info_size = sizeof(CamelVeeMessageInfo);
+
+ if (CAMEL_IS_VEE_STORE(parent_store))
+ vf->parent_vee_store = (CamelVeeStore *)parent_store;
+}
+
+/**
+ * camel_vee_folder_new:
+ * @parent_store: the parent CamelVeeStore
+ * @full: the full path to the vfolder.
+ * @ex: a CamelException
+ *
+ * Create a new CamelVeeFolder object.
+ *
+ * Return value: A new CamelVeeFolder widget.
+ **/
+CamelFolder *
+camel_vee_folder_new(CamelStore *parent_store, const char *full, guint32 flags)
+{
+ CamelVeeFolder *vf;
+ char *tmp;
+
+ if (CAMEL_IS_VEE_STORE(parent_store) && strcmp(full, CAMEL_UNMATCHED_NAME) == 0) {
+ vf = ((CamelVeeStore *)parent_store)->folder_unmatched;
+ camel_object_ref(vf);
+ } else {
+ const char *name = strrchr(full, '/');
+
+ if (name == NULL)
+ name = full;
+ else
+ name++;
+ vf = (CamelVeeFolder *)camel_object_new(camel_vee_folder_get_type());
+ camel_vee_folder_construct(vf, parent_store, full, name, flags);
+ }
+
+ d(printf("returning folder %s %p, count = %d\n", name, vf, camel_folder_get_message_count((CamelFolder *)vf)));
+
+ tmp = g_strdup_printf("%s/%s.cmeta", ((CamelService *)parent_store)->url->path, full);
+ camel_object_set(vf, NULL, CAMEL_OBJECT_STATE_FILE, tmp, NULL);
+ g_free(tmp);
+ if (camel_object_state_read(vf) == -1) {
+ /* setup defaults: we have none currently */
+ }
+
+ return (CamelFolder *)vf;
+}
+
+void
+camel_vee_folder_set_expression(CamelVeeFolder *vf, const char *query)
+{
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GList *node;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+
+ /* no change, do nothing */
+ if ((vf->expression && query && strcmp(vf->expression, query) == 0)
+ || (vf->expression == NULL && query == NULL)) {
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+ return;
+ }
+
+ g_free(vf->expression);
+ if (query)
+ vf->expression = g_strdup(query);
+
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+
+ if (vee_folder_build_folder(vf, f, NULL) == -1)
+ break;
+
+ node = node->next;
+ }
+
+ CAMEL_VEE_FOLDER_LOCK(vf, changed_lock);
+ g_list_free(p->folders_changed);
+ p->folders_changed = NULL;
+ CAMEL_VEE_FOLDER_UNLOCK(vf, changed_lock);
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+}
+
+/**
+ * camel_vee_folder_add_folder:
+ * @vf: Virtual Folder object
+ * @sub: source CamelFolder to add to @vf
+ *
+ * Adds @sub as a source folder to @vf.
+ **/
+void
+camel_vee_folder_add_folder(CamelVeeFolder *vf, CamelFolder *sub)
+{
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ int i;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+
+ if (vf == (CamelVeeFolder *)sub) {
+ g_warning("Adding a virtual folder to itself as source, ignored");
+ return;
+ }
+
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+
+ /* for normal vfolders we want only unique ones, for unmatched we want them all recorded */
+ if (g_list_find(p->folders, sub) == NULL) {
+ camel_object_ref((CamelObject *)sub);
+ p->folders = g_list_append(p->folders, sub);
+
+ CAMEL_FOLDER_LOCK(vf, change_lock);
+
+ /* update the freeze state of 'sub' to match our freeze state */
+ for (i = 0; i < ((CamelFolder *)vf)->priv->frozen; i++)
+ camel_folder_freeze(sub);
+
+ CAMEL_FOLDER_UNLOCK(vf, change_lock);
+ }
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0 && !CAMEL_IS_VEE_FOLDER(sub) && folder_unmatched != NULL) {
+ struct _CamelVeeFolderPrivate *up = _PRIVATE(folder_unmatched);
+ camel_object_ref((CamelObject *)sub);
+ up->folders = g_list_append(up->folders, sub);
+
+ CAMEL_FOLDER_LOCK(folder_unmatched, change_lock);
+
+ /* update the freeze state of 'sub' to match Unmatched's freeze state */
+ for (i = 0; i < ((CamelFolder *)folder_unmatched)->priv->frozen; i++)
+ camel_folder_freeze(sub);
+
+ CAMEL_FOLDER_UNLOCK(folder_unmatched, change_lock);
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ d(printf("camel_vee_folder_add_folde(%p, %p)\n", vf, sub));
+
+ camel_object_hook_event((CamelObject *)sub, "folder_changed", (CamelObjectEventHookFunc)folder_changed, vf);
+ camel_object_hook_event((CamelObject *)sub, "deleted", (CamelObjectEventHookFunc)subfolder_deleted, vf);
+ camel_object_hook_event((CamelObject *)sub, "renamed", (CamelObjectEventHookFunc)subfolder_renamed, vf);
+
+ vee_folder_build_folder(vf, sub, NULL);
+}
+
+/**
+ * camel_vee_folder_remove_folder:
+ * @vf: Virtual Folder object
+ * @sub: source CamelFolder to remove from @vf
+ *
+ * Removed the source folder, @sub, from the virtual folder, @vf.
+ **/
+void
+camel_vee_folder_remove_folder(CamelVeeFolder *vf, CamelFolder *sub)
+{
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ int killun = FALSE;
+ int i;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+
+ CAMEL_VEE_FOLDER_LOCK(vf, changed_lock);
+ p->folders_changed = g_list_remove(p->folders_changed, sub);
+ CAMEL_VEE_FOLDER_UNLOCK(vf, changed_lock);
+
+ if (g_list_find(p->folders, sub) == NULL) {
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+ return;
+ }
+
+ camel_object_unhook_event((CamelObject *)sub, "folder_changed", (CamelObjectEventHookFunc) folder_changed, vf);
+ camel_object_unhook_event((CamelObject *)sub, "deleted", (CamelObjectEventHookFunc) subfolder_deleted, vf);
+ camel_object_unhook_event((CamelObject *)sub, "renamed", (CamelObjectEventHookFunc) subfolder_renamed, vf);
+
+ p->folders = g_list_remove(p->folders, sub);
+
+ /* undo the freeze state that we have imposed on this source folder */
+ CAMEL_FOLDER_LOCK(vf, change_lock);
+ for (i = 0; i < ((CamelFolder *)vf)->priv->frozen; i++)
+ camel_folder_thaw(sub);
+ CAMEL_FOLDER_UNLOCK(vf, change_lock);
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ if (folder_unmatched != NULL) {
+ struct _CamelVeeFolderPrivate *up = _PRIVATE(folder_unmatched);
+
+ CAMEL_VEE_FOLDER_LOCK(folder_unmatched, subfolder_lock);
+ /* if folder deleted, then blow it away from unmatched always, and remove all refs to it */
+ if (sub->folder_flags & CAMEL_FOLDER_HAS_BEEN_DELETED) {
+ while (g_list_find(up->folders, sub)) {
+ killun = TRUE;
+ up->folders = g_list_remove(up->folders, sub);
+ camel_object_unref((CamelObject *)sub);
+
+ /* undo the freeze state that Unmatched has imposed on this source folder */
+ CAMEL_FOLDER_LOCK(folder_unmatched, change_lock);
+ for (i = 0; i < ((CamelFolder *)folder_unmatched)->priv->frozen; i++)
+ camel_folder_thaw(sub);
+ CAMEL_FOLDER_UNLOCK(folder_unmatched, change_lock);
+ }
+ } else if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0) {
+ if (g_list_find(up->folders, sub) != NULL) {
+ up->folders = g_list_remove(up->folders, sub);
+ camel_object_unref((CamelObject *)sub);
+
+ /* undo the freeze state that Unmatched has imposed on this source folder */
+ CAMEL_FOLDER_LOCK(folder_unmatched, change_lock);
+ for (i = 0; i < ((CamelFolder *)folder_unmatched)->priv->frozen; i++)
+ camel_folder_thaw(sub);
+ CAMEL_FOLDER_UNLOCK(folder_unmatched, change_lock);
+ }
+ if (g_list_find(up->folders, sub) == NULL) {
+ killun = TRUE;
+ }
+ }
+ CAMEL_VEE_FOLDER_UNLOCK(folder_unmatched, subfolder_lock);
+ }
+
+ vee_folder_remove_folder(vf, sub, killun);
+
+ camel_object_unref((CamelObject *)sub);
+}
+
+static void
+remove_folders(CamelFolder *folder, CamelFolder *foldercopy, CamelVeeFolder *vf)
+{
+ camel_vee_folder_remove_folder(vf, folder);
+ camel_object_unref((CamelObject *)folder);
+}
+
+/**
+ * camel_vee_folder_set_folders:
+ * @vf:
+ * @folders:
+ *
+ * Set the whole list of folder sources on a vee folder.
+ **/
+void
+camel_vee_folder_set_folders(CamelVeeFolder *vf, GList *folders)
+{
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GHashTable *remove = g_hash_table_new(NULL, NULL);
+ GList *l;
+ CamelFolder *folder;
+ int changed;
+
+ /* setup a table of all folders we have currently */
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+ l = p->folders;
+ while (l) {
+ g_hash_table_insert(remove, l->data, l->data);
+ camel_object_ref((CamelObject *)l->data);
+ l = l->next;
+ }
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ /* if we already have the folder, ignore it, otherwise add it */
+ l = folders;
+ while (l) {
+ if ((folder = g_hash_table_lookup(remove, l->data))) {
+ g_hash_table_remove(remove, folder);
+ camel_object_unref((CamelObject *)folder);
+
+ /* if this was a changed folder, re-update it while we're here */
+ CAMEL_VEE_FOLDER_LOCK(vf, changed_lock);
+ changed = g_list_find(p->folders_changed, folder) != NULL;
+ if (changed)
+ p->folders_changed = g_list_remove(p->folders_changed, folder);
+ CAMEL_VEE_FOLDER_UNLOCK(vf, changed_lock);
+ if (changed)
+ vee_folder_build_folder(vf, folder, NULL);
+ } else {
+ camel_vee_folder_add_folder(vf, l->data);
+ }
+ l = l->next;
+ }
+
+ /* then remove any we still have */
+ g_hash_table_foreach(remove, (GHFunc)remove_folders, vf);
+ g_hash_table_destroy(remove);
+}
+
+/**
+ * camel_vee_folder_hash_folder:
+ * @folder:
+ * @:
+ *
+ * Create a hash string representing the folder name, which should be
+ * unique, and remain static for a given folder.
+ **/
+void
+camel_vee_folder_hash_folder(CamelFolder *folder, char buffer[8])
+{
+ MD5Context ctx;
+ unsigned char digest[16];
+ unsigned int state = 0, save = 0;
+ char *tmp;
+ int i;
+
+ md5_init(&ctx);
+ tmp = camel_service_get_url((CamelService *)folder->parent_store);
+ md5_update(&ctx, tmp, strlen(tmp));
+ g_free(tmp);
+ md5_update(&ctx, folder->full_name, strlen(folder->full_name));
+ md5_final(&ctx, digest);
+ camel_base64_encode_close(digest, 6, FALSE, buffer, &state, &save);
+
+ for (i=0;i<8;i++) {
+ if (buffer[i] == '+')
+ buffer[i] = '.';
+ if (buffer[i] == '/')
+ buffer[i] = '_';
+ }
+}
+
+/**
+ * camel_vee_folder_get_location:
+ * @vf:
+ * @vinfo:
+ * @realuid: if not NULL, set to the uid of the real message, must be
+ * g_free'd by caller.
+ *
+ * Find the real folder (and uid)
+ *
+ * Return value:
+ **/
+CamelFolder *
+camel_vee_folder_get_location(CamelVeeFolder *vf, const CamelVeeMessageInfo *vinfo, char **realuid)
+{
+ /* locking? yes? no? although the vfolderinfo is valid when obtained
+ the folder in it might not necessarily be so ...? */
+ if (CAMEL_IS_VEE_FOLDER(vinfo->folder)) {
+ CamelFolder *folder;
+ const CamelVeeMessageInfo *vfinfo;
+
+ vfinfo = (CamelVeeMessageInfo *)camel_folder_get_message_info(vinfo->folder, camel_message_info_uid(vinfo)+8);
+ folder = camel_vee_folder_get_location((CamelVeeFolder *)vinfo->folder, vfinfo, realuid);
+ camel_folder_free_message_info(vinfo->folder, (CamelMessageInfo *)vfinfo);
+ return folder;
+ } else {
+ if (realuid)
+ *realuid = g_strdup(camel_message_info_uid(vinfo)+8);
+
+ return vinfo->folder;
+ }
+}
+
+static void vee_refresh_info(CamelFolder *folder, CamelException *ex)
+{
+ CamelVeeFolder *vf = (CamelVeeFolder *)folder;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GList *node, *list;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, changed_lock);
+ list = p->folders_changed;
+ p->folders_changed = NULL;
+ CAMEL_VEE_FOLDER_UNLOCK(vf, changed_lock);
+
+ node = list;
+ while (node) {
+ CamelFolder *f = node->data;
+
+ if (vee_folder_build_folder(vf, f, ex) == -1)
+ break;
+
+ node = node->next;
+ }
+
+ g_list_free(list);
+}
+
+static void
+vee_sync(CamelFolder *folder, gboolean expunge, CamelException *ex)
+{
+ CamelVeeFolder *vf = (CamelVeeFolder *)folder;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GList *node;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+
+ camel_folder_sync(f, expunge, ex);
+ if (camel_exception_is_set(ex)) {
+ char *desc;
+
+ camel_object_get(f, NULL, CAMEL_OBJECT_DESCRIPTION, &desc, NULL);
+ camel_exception_setv(ex, ex->id, _("Error storing `%s': %s"), desc, ex->desc);
+ break;
+ }
+
+ /* auto update vfolders shouldn't need a rebuild */
+ if ((vf->flags & CAMEL_STORE_VEE_FOLDER_AUTO) == 0
+ && vee_folder_build_folder(vf, f, ex) == -1)
+ break;
+
+ node = node->next;
+ }
+
+ if (node == NULL) {
+ CAMEL_VEE_FOLDER_LOCK(vf, changed_lock);
+ g_list_free(p->folders_changed);
+ p->folders_changed = NULL;
+ CAMEL_VEE_FOLDER_UNLOCK(vf, changed_lock);
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ camel_object_state_write(vf);
+}
+
+static void
+vee_expunge (CamelFolder *folder, CamelException *ex)
+{
+ ((CamelFolderClass *)((CamelObject *)folder)->klass)->sync(folder, TRUE, ex);
+}
+
+static CamelMimeMessage *
+vee_get_message(CamelFolder *folder, const char *uid, CamelException *ex)
+{
+ CamelVeeMessageInfo *mi;
+ CamelMimeMessage *msg = NULL;
+
+ mi = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, uid);
+ if (mi) {
+ msg = camel_folder_get_message(mi->folder, camel_message_info_uid(mi)+8, ex);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)mi);
+ } else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("No such message %s in %s"), uid,
+ folder->name);
+ }
+
+ return msg;
+}
+
+static GPtrArray *
+vee_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex)
+{
+ GList *node;
+ GPtrArray *matches, *result = g_ptr_array_new ();
+ char *expr;
+ CamelVeeFolder *vf = (CamelVeeFolder *)folder;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GHashTable *searched = g_hash_table_new(NULL, NULL);
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+
+ if (vf != folder_unmatched)
+ expr = g_strdup_printf ("(and %s %s)", vf->expression, expression);
+ else
+ expr = g_strdup (expression);
+
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+ int i;
+ char hash[8];
+
+ /* make sure we only search each folder once - for unmatched folder to work right */
+ if (g_hash_table_lookup(searched, f) == NULL) {
+ camel_vee_folder_hash_folder(f, hash);
+ /* FIXME: shouldn't ignore search exception */
+ matches = camel_folder_search_by_expression(f, expression, NULL);
+ if (matches) {
+ for (i = 0; i < matches->len; i++) {
+ char *uid = matches->pdata[i], *vuid;
+
+ vuid = g_malloc(strlen(uid)+9);
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+ g_ptr_array_add(result, vuid);
+ }
+ camel_folder_search_free(f, matches);
+ }
+ g_hash_table_insert(searched, f, f);
+ }
+ node = g_list_next(node);
+ }
+
+ g_free(expr);
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ g_hash_table_destroy(searched);
+
+ return result;
+}
+
+static GPtrArray *
+vee_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
+{
+ GList *node;
+ GPtrArray *matches, *result = g_ptr_array_new ();
+ GPtrArray *folder_uids = g_ptr_array_new();
+ char *expr;
+ CamelVeeFolder *vf = (CamelVeeFolder *)folder;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);
+ GHashTable *searched = g_hash_table_new(NULL, NULL);
+
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+
+ expr = g_strdup_printf("(and %s %s)", vf->expression, expression);
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+ int i;
+ char hash[8];
+
+ /* make sure we only search each folder once - for unmatched folder to work right */
+ if (g_hash_table_lookup(searched, f) == NULL) {
+ camel_vee_folder_hash_folder(f, hash);
+
+ /* map the vfolder uid's to the source folder uid's first */
+ g_ptr_array_set_size(folder_uids, 0);
+ for (i=0;i<uids->len;i++) {
+ char *uid = uids->pdata[i];
+
+ if (strlen(uid) >= 8 && strncmp(uid, hash, 8) == 0)
+ g_ptr_array_add(folder_uids, uid+8);
+ }
+ if (folder_uids->len > 0) {
+ matches = camel_folder_search_by_uids(f, expression, folder_uids, ex);
+ if (matches) {
+ for (i = 0; i < matches->len; i++) {
+ char *uid = matches->pdata[i], *vuid;
+
+ vuid = g_malloc(strlen(uid)+9);
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+ g_ptr_array_add(result, vuid);
+ }
+ camel_folder_search_free(f, matches);
+ } else {
+ g_warning("Search failed: %s", camel_exception_get_description(ex));
+ }
+ }
+ g_hash_table_insert(searched, f, f);
+ }
+ node = g_list_next(node);
+ }
+
+ g_free(expr);
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ g_hash_table_destroy(searched);
+ g_ptr_array_free(folder_uids, 0);
+
+ return result;
+}
+
+static gboolean
+vee_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
+{
+ CamelVeeMessageInfo *mi;
+ int res = FALSE;
+
+ mi = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, uid);
+ if (mi) {
+ res = camel_folder_set_message_flags(mi->folder, camel_message_info_uid(mi) + 8, flags, set);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)mi);
+ res = res || ((CamelFolderClass *)camel_vee_folder_parent)->set_message_flags(folder, uid, flags, set);
+ }
+
+ return res;
+}
+
+static void
+vee_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value)
+{
+ CamelVeeMessageInfo *mi;
+
+ mi = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, uid);
+ if (mi) {
+ camel_folder_set_message_user_flag(mi->folder, camel_message_info_uid(mi) + 8, name, value);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)mi);
+ ((CamelFolderClass *)camel_vee_folder_parent)->set_message_user_flag(folder, uid, name, value);
+ }
+}
+
+static void
+vee_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value)
+{
+ CamelVeeMessageInfo *mi;
+
+ mi = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, uid);
+ if (mi) {
+ camel_folder_set_message_user_tag(mi->folder, camel_message_info_uid(mi) + 8, name, value);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)mi);
+ ((CamelFolderClass *)camel_vee_folder_parent)->set_message_user_tag(folder, uid, name, value);
+ }
+}
+
+static void
+vee_append_message(CamelFolder *folder, CamelMimeMessage *message, const CamelMessageInfo *info, char **appended_uid, CamelException *ex)
+{
+ camel_exception_set(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot copy or move messages into a Virtual Folder"));
+}
+
+static void
+vee_transfer_messages_to (CamelFolder *folder, GPtrArray *uids, CamelFolder *dest, GPtrArray **transferred_uids, gboolean delete_originals, CamelException *ex)
+{
+ camel_exception_set(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot copy or move messages into a Virtual Folder"));
+}
+
+static void vee_rename(CamelFolder *folder, const char *new)
+{
+ CamelVeeFolder *vf = (CamelVeeFolder *)folder;
+
+ g_free(vf->vname);
+ vf->vname = g_strdup(new);
+
+ ((CamelFolderClass *)camel_vee_folder_parent)->rename(folder, new);
+}
+
+/* ********************************************************************** *
+ utility functions */
+
+/* must be called with summary_lock held */
+static CamelVeeMessageInfo *
+vee_folder_add_info(CamelVeeFolder *vf, CamelFolder *f, CamelMessageInfo *info, const char hash[8])
+{
+ CamelVeeMessageInfo *mi;
+ char *vuid;
+ const char *uid;
+ CamelFolder *folder = (CamelFolder *)vf;
+ CamelMessageInfo *dinfo;
+
+ uid = camel_message_info_uid(info);
+ vuid = alloca(strlen(uid)+9);
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+ dinfo = camel_folder_summary_uid(folder->summary, vuid);
+ if (dinfo) {
+ d(printf("w:clash, we already have '%s' in summary\n", vuid));
+ camel_folder_summary_info_free(folder->summary, dinfo);
+ return NULL;
+ }
+
+ d(printf("adding vuid %s to %s\n", vuid, vf->vname));
+
+ mi = (CamelVeeMessageInfo *)camel_folder_summary_info_new(folder->summary);
+ camel_message_info_dup_to(info, (CamelMessageInfo *)mi);
+#ifdef DOEPOOLV
+ mi->info.strings = e_poolv_set(mi->info.strings, CAMEL_MESSAGE_INFO_UID, vuid, FALSE);
+#elif defined (DOESTRV)
+ mi->info.strings = e_strv_set_ref(mi->info.strings, CAMEL_MESSAGE_INFO_UID, vuid);
+ mi->info.strings = e_strv_pack(mi->info.strings);
+#else
+ g_free(mi->info.uid);
+ mi->info.uid = g_strdup(vuid);
+#endif
+ mi->folder = f;
+ camel_folder_summary_add(folder->summary, (CamelMessageInfo *)mi);
+
+ return mi;
+}
+
+/* must be called with summary_lock held */
+static CamelVeeMessageInfo *
+vee_folder_add_uid(CamelVeeFolder *vf, CamelFolder *f, const char *inuid, const char hash[8])
+{
+ CamelMessageInfo *info;
+ CamelVeeMessageInfo *mi = NULL;
+
+ info = camel_folder_get_message_info(f, inuid);
+ if (info) {
+ mi = vee_folder_add_info(vf, f, info, hash);
+ camel_folder_free_message_info(f, info);
+ }
+ return mi;
+}
+
+static void
+vee_folder_remove_folder(CamelVeeFolder *vf, CamelFolder *source, int killun)
+{
+ int i, count, n, still = FALSE, start, last;
+ char *oldkey;
+ CamelFolder *folder = (CamelFolder *)vf;
+ char hash[8];
+ /*struct _CamelVeeFolderPrivate *p = _PRIVATE(vf);*/
+ CamelFolderChangeInfo *vf_changes = NULL, *unmatched_changes = NULL;
+ void *oldval;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+ GHashTable *unmatched_uids = vf->parent_vee_store ? vf->parent_vee_store->unmatched_uids : NULL;
+
+ if (vf == folder_unmatched)
+ return;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
+
+ if (folder_unmatched != NULL) {
+ /* check if this folder is still to be part of unmatched */
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0 && !killun) {
+ CAMEL_VEE_FOLDER_LOCK(folder_unmatched, subfolder_lock);
+ still = g_list_find(_PRIVATE(folder_unmatched)->folders, source) != NULL;
+ CAMEL_VEE_FOLDER_UNLOCK(folder_unmatched, subfolder_lock);
+ camel_vee_folder_hash_folder(source, hash);
+ }
+
+ CAMEL_VEE_FOLDER_LOCK(folder_unmatched, summary_lock);
+
+ /* See if we just blow all uid's from this folder away from unmatched, regardless */
+ if (killun) {
+ start = -1;
+ last = -1;
+ count = camel_folder_summary_count(((CamelFolder *)folder_unmatched)->summary);
+ for (i=0;i<count;i++) {
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *)camel_folder_summary_index(((CamelFolder *)folder_unmatched)->summary, i);
+
+ if (mi) {
+ if (mi->folder == source) {
+ camel_folder_change_info_remove_uid(folder_unmatched->changes, camel_message_info_uid(mi));
+ if (last == -1) {
+ last = start = i;
+ } else if (last+1 == i) {
+ last = i;
+ } else {
+ camel_folder_summary_remove_range(((CamelFolder *)folder_unmatched)->summary, start, last);
+ i -= (last-start)+1;
+ start = last = i;
+ }
+ }
+ camel_folder_summary_info_free(((CamelFolder *)folder_unmatched)->summary, (CamelMessageInfo *)mi);
+ }
+ }
+ if (last != -1)
+ camel_folder_summary_remove_range(((CamelFolder *)folder_unmatched)->summary, start, last);
+ }
+ }
+
+ start = -1;
+ last = -1;
+ count = camel_folder_summary_count(folder->summary);
+ for (i=0;i<count;i++) {
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *)camel_folder_summary_index(folder->summary, i);
+ if (mi) {
+ if (mi->folder == source) {
+ const char *uid = camel_message_info_uid(mi);
+
+ camel_folder_change_info_remove_uid(vf->changes, uid);
+
+ if (last == -1) {
+ last = start = i;
+ } else if (last+1 == i) {
+ last = i;
+ } else {
+ camel_folder_summary_remove_range(folder->summary, start, last);
+ i -= (last-start)+1;
+ start = last = i;
+ }
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0 && folder_unmatched != NULL) {
+ if (still) {
+ if (g_hash_table_lookup_extended(unmatched_uids, uid, (void **)&oldkey, &oldval)) {
+ n = GPOINTER_TO_INT (oldval);
+ if (n == 1) {
+ g_hash_table_remove(unmatched_uids, oldkey);
+ if (vee_folder_add_uid(folder_unmatched, source, oldkey+8, hash))
+ camel_folder_change_info_add_uid(folder_unmatched->changes, oldkey);
+ g_free(oldkey);
+ } else {
+ g_hash_table_insert(unmatched_uids, oldkey, GINT_TO_POINTER(n-1));
+ }
+ }
+ } else {
+ if (g_hash_table_lookup_extended(unmatched_uids, camel_message_info_uid(mi), (void **)&oldkey, &oldval)) {
+ g_hash_table_remove(unmatched_uids, oldkey);
+ g_free(oldkey);
+ }
+ }
+ }
+ }
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)mi);
+ }
+ }
+
+ if (last != -1)
+ camel_folder_summary_remove_range(folder->summary, start, last);
+
+ if (folder_unmatched) {
+ if (camel_folder_change_info_changed(folder_unmatched->changes)) {
+ unmatched_changes = folder_unmatched->changes;
+ folder_unmatched->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(folder_unmatched, summary_lock);
+ }
+
+ if (camel_folder_change_info_changed(vf->changes)) {
+ vf_changes = vf->changes;
+ vf->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
+
+ if (unmatched_changes) {
+ camel_object_trigger_event((CamelObject *)folder_unmatched, "folder_changed", unmatched_changes);
+ camel_folder_change_info_free(unmatched_changes);
+ }
+
+ if (vf_changes) {
+ camel_object_trigger_event((CamelObject *)vf, "folder_changed", vf_changes);
+ camel_folder_change_info_free(vf_changes);
+ }
+}
+
+struct _update_data {
+ CamelFolder *source;
+ CamelVeeFolder *vf;
+ char hash[8];
+ CamelVeeFolder *folder_unmatched;
+ GHashTable *unmatched_uids;
+};
+
+static void
+unmatched_check_uid(char *uidin, void *value, struct _update_data *u)
+{
+ char *uid;
+ int n;
+
+ uid = alloca(strlen(uidin)+9);
+ memcpy(uid, u->hash, 8);
+ strcpy(uid+8, uidin);
+ n = GPOINTER_TO_INT(g_hash_table_lookup(u->unmatched_uids, uid));
+ if (n == 0) {
+ if (vee_folder_add_uid(u->folder_unmatched, u->source, uidin, u->hash))
+ camel_folder_change_info_add_uid(u->folder_unmatched->changes, uid);
+ } else {
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *)camel_folder_summary_uid(((CamelFolder *)u->folder_unmatched)->summary, uid);
+ if (mi) {
+ camel_folder_summary_remove(((CamelFolder *)u->folder_unmatched)->summary, (CamelMessageInfo *)mi);
+ camel_folder_change_info_remove_uid(u->folder_unmatched->changes, uid);
+ camel_folder_summary_info_free(((CamelFolder *)u->folder_unmatched)->summary, (CamelMessageInfo *)mi);
+ }
+ }
+}
+
+static void
+folder_added_uid(char *uidin, void *value, struct _update_data *u)
+{
+ CamelVeeMessageInfo *mi;
+ char *oldkey;
+ void *oldval;
+ int n;
+
+ if ( (mi = vee_folder_add_uid(u->vf, u->source, uidin, u->hash)) ) {
+ camel_folder_change_info_add_uid(u->vf->changes, camel_message_info_uid(mi));
+
+ if (!CAMEL_IS_VEE_FOLDER(u->source) && u->unmatched_uids != NULL) {
+ if (g_hash_table_lookup_extended(u->unmatched_uids, camel_message_info_uid(mi), (void **)&oldkey, &oldval)) {
+ n = GPOINTER_TO_INT (oldval);
+ g_hash_table_insert(u->unmatched_uids, oldkey, GINT_TO_POINTER(n+1));
+ } else {
+ g_hash_table_insert(u->unmatched_uids, g_strdup(camel_message_info_uid(mi)), GINT_TO_POINTER(1));
+ }
+ }
+ }
+}
+
+/* build query contents for a single folder */
+static int
+vee_folder_build_folder(CamelVeeFolder *vf, CamelFolder *source, CamelException *ex)
+{
+ GPtrArray *match, *all;
+ GHashTable *allhash, *matchhash;
+ CamelFolder *f = source;
+ CamelFolder *folder = (CamelFolder *)vf;
+ int i, n, count, start, last;
+ struct _update_data u;
+ CamelFolderChangeInfo *vf_changes = NULL, *unmatched_changes = NULL;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+ GHashTable *unmatched_uids = vf->parent_vee_store ? vf->parent_vee_store->unmatched_uids : NULL;
+
+ if (vf == folder_unmatched)
+ return 0;
+
+ /* if we have no expression, or its been cleared, then act as if no matches */
+ if (vf->expression == NULL) {
+ match = g_ptr_array_new();
+ } else {
+ match = camel_folder_search_by_expression(f, vf->expression, ex);
+ if (match == NULL)
+ return -1;
+ }
+
+ u.source = source;
+ u.vf = vf;
+ u.folder_unmatched = folder_unmatched;
+ u.unmatched_uids = unmatched_uids;
+ camel_vee_folder_hash_folder(source, u.hash);
+
+ CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
+
+ /* we build 2 hash tables, one for all uid's not matched, the other for all matched uid's,
+ we just ref the real memory */
+ matchhash = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<match->len;i++)
+ g_hash_table_insert(matchhash, match->pdata[i], GINT_TO_POINTER (1));
+
+ allhash = g_hash_table_new(g_str_hash, g_str_equal);
+ all = camel_folder_get_uids(f);
+ for (i=0;i<all->len;i++)
+ if (g_hash_table_lookup(matchhash, all->pdata[i]) == NULL)
+ g_hash_table_insert(allhash, all->pdata[i], GINT_TO_POINTER (1));
+
+ if (folder_unmatched != NULL)
+ CAMEL_VEE_FOLDER_LOCK(folder_unmatched, summary_lock);
+
+ /* scan, looking for "old" uid's to be removed */
+ start = -1;
+ last = -1;
+ count = camel_folder_summary_count(folder->summary);
+ for (i=0;i<count;i++) {
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *)camel_folder_summary_index(folder->summary, i);
+
+ if (mi) {
+ if (mi->folder == source) {
+ char *uid = (char *)camel_message_info_uid(mi), *oldkey;
+ void *oldval;
+
+ if (g_hash_table_lookup(matchhash, uid+8) == NULL) {
+ if (last == -1) {
+ last = start = i;
+ } else if (last+1 == i) {
+ last = i;
+ } else {
+ camel_folder_summary_remove_range(folder->summary, start, last);
+ i -= (last-start)+1;
+ start = last = i;
+ }
+ camel_folder_change_info_remove_uid(vf->changes, camel_message_info_uid(mi));
+ if (!CAMEL_IS_VEE_FOLDER(source)
+ && unmatched_uids != NULL
+ && g_hash_table_lookup_extended(unmatched_uids, uid, (void **)&oldkey, &oldval)) {
+ n = GPOINTER_TO_INT (oldval);
+ if (n == 1) {
+ g_hash_table_remove(unmatched_uids, oldkey);
+ g_free(oldkey);
+ } else {
+ g_hash_table_insert(unmatched_uids, oldkey, GINT_TO_POINTER(n-1));
+ }
+ }
+ } else {
+ g_hash_table_remove(matchhash, uid+8);
+ }
+ }
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)mi);
+ }
+ }
+ if (last != -1)
+ camel_folder_summary_remove_range(folder->summary, start, last);
+
+ /* now matchhash contains any new uid's, add them, etc */
+ g_hash_table_foreach(matchhash, (GHFunc)folder_added_uid, &u);
+
+ if (folder_unmatched != NULL) {
+ /* scan unmatched, remove any that have vanished, etc */
+ count = camel_folder_summary_count(((CamelFolder *)folder_unmatched)->summary);
+ for (i=0;i<count;i++) {
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *)camel_folder_summary_index(((CamelFolder *)folder_unmatched)->summary, i);
+
+ if (mi) {
+ if (mi->folder == source) {
+ char *uid = (char *)camel_message_info_uid(mi);
+
+ if (g_hash_table_lookup(allhash, uid+8) == NULL) {
+ /* no longer exists at all, just remove it entirely */
+ camel_folder_summary_remove_index(((CamelFolder *)folder_unmatched)->summary, i);
+ camel_folder_change_info_remove_uid(folder_unmatched->changes, camel_message_info_uid(mi));
+ i--;
+ } else {
+ g_hash_table_remove(allhash, uid+8);
+ }
+ }
+ camel_folder_summary_info_free(((CamelFolder *)folder_unmatched)->summary, (CamelMessageInfo *)mi);
+ }
+ }
+
+ /* now allhash contains all potentially new uid's for the unmatched folder, process */
+ if (!CAMEL_IS_VEE_FOLDER(source))
+ g_hash_table_foreach(allhash, (GHFunc)unmatched_check_uid, &u);
+
+ /* copy any changes so we can raise them outside the lock */
+ if (camel_folder_change_info_changed(folder_unmatched->changes)) {
+ unmatched_changes = folder_unmatched->changes;
+ folder_unmatched->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(folder_unmatched, summary_lock);
+ }
+
+ if (camel_folder_change_info_changed(vf->changes)) {
+ vf_changes = vf->changes;
+ vf->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
+
+ g_hash_table_destroy(matchhash);
+ g_hash_table_destroy(allhash);
+ /* if expression not set, we only had a null list */
+ if (vf->expression == NULL)
+ g_ptr_array_free(match, TRUE);
+ else
+ camel_folder_search_free(f, match);
+ camel_folder_free_uids(f, all);
+
+ if (unmatched_changes) {
+ camel_object_trigger_event((CamelObject *)folder_unmatched, "folder_changed", unmatched_changes);
+ camel_folder_change_info_free(unmatched_changes);
+ }
+
+ if (vf_changes) {
+ camel_object_trigger_event((CamelObject *)vf, "folder_changed", vf_changes);
+ camel_folder_change_info_free(vf_changes);
+ }
+
+ return 0;
+}
+
+/*
+
+ (match-folder "folder1" "folder2")
+
+ */
+
+
+/* Hold all these with summary lock and unmatched summary lock held */
+static void
+folder_changed_add_uid(CamelFolder *sub, const char *uid, const char hash[8], CamelVeeFolder *vf)
+{
+ CamelVeeMessageInfo *vinfo;
+ const char *vuid;
+ char *oldkey;
+ void *oldval;
+ int n;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+ GHashTable *unmatched_uids = vf->parent_vee_store ? vf->parent_vee_store->unmatched_uids : NULL;
+
+ vinfo = vee_folder_add_uid(vf, sub, uid, hash);
+ if (vinfo == NULL)
+ return;
+
+ vuid = camel_message_info_uid(vinfo);
+ camel_folder_change_info_add_uid(vf->changes, vuid);
+
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0 && !CAMEL_IS_VEE_FOLDER(sub) && folder_unmatched != NULL) {
+ if (g_hash_table_lookup_extended(unmatched_uids, vuid, (void **)&oldkey, &oldval)) {
+ n = GPOINTER_TO_INT (oldval);
+ g_hash_table_insert(unmatched_uids, oldkey, GINT_TO_POINTER(n+1));
+ } else {
+ g_hash_table_insert(unmatched_uids, g_strdup(vuid), GINT_TO_POINTER (1));
+ }
+ vinfo = (CamelVeeMessageInfo *)camel_folder_get_message_info((CamelFolder *)folder_unmatched, vuid);
+ if (vinfo) {
+ camel_folder_change_info_remove_uid(folder_unmatched->changes, vuid);
+ camel_folder_summary_remove(((CamelFolder *)folder_unmatched)->summary, (CamelMessageInfo *)vinfo);
+ camel_folder_free_message_info((CamelFolder *)folder_unmatched, (CamelMessageInfo *)vinfo);
+ }
+ }
+}
+
+static void
+folder_changed_remove_uid(CamelFolder *sub, const char *uid, const char hash[8], int keep, CamelVeeFolder *vf)
+{
+ CamelFolder *folder = (CamelFolder *)vf;
+ char *vuid, *oldkey;
+ void *oldval;
+ int n;
+ CamelVeeMessageInfo *vinfo;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+ GHashTable *unmatched_uids = vf->parent_vee_store ? vf->parent_vee_store->unmatched_uids : NULL;
+
+ vuid = alloca(strlen(uid)+9);
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+
+ vinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, vuid);
+ if (vinfo) {
+ camel_folder_change_info_remove_uid(vf->changes, vuid);
+ camel_folder_summary_remove(folder->summary, (CamelMessageInfo *)vinfo);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)vinfo);
+ }
+
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0 && !CAMEL_IS_VEE_FOLDER(sub) && folder_unmatched != NULL) {
+ if (keep) {
+ if (g_hash_table_lookup_extended(unmatched_uids, vuid, (void **)&oldkey, &oldval)) {
+ n = GPOINTER_TO_INT (oldval);
+ if (n == 1) {
+ g_hash_table_remove(unmatched_uids, oldkey);
+ if (vee_folder_add_uid(folder_unmatched, sub, uid, hash))
+ camel_folder_change_info_add_uid(folder_unmatched->changes, oldkey);
+ g_free(oldkey);
+ } else {
+ g_hash_table_insert(unmatched_uids, oldkey, GINT_TO_POINTER(n-1));
+ }
+ } else {
+ if (vee_folder_add_uid(folder_unmatched, sub, uid, hash))
+ camel_folder_change_info_add_uid(folder_unmatched->changes, oldkey);
+ }
+ } else {
+ if (g_hash_table_lookup_extended(unmatched_uids, vuid, (void **)&oldkey, &oldval)) {
+ g_hash_table_remove(unmatched_uids, oldkey);
+ g_free(oldkey);
+ }
+
+ vinfo = (CamelVeeMessageInfo *)camel_folder_get_message_info((CamelFolder *)folder_unmatched, vuid);
+ if (vinfo) {
+ camel_folder_change_info_remove_uid(folder_unmatched->changes, vuid);
+ camel_folder_summary_remove_uid(((CamelFolder *)folder_unmatched)->summary, vuid);
+ camel_folder_free_message_info((CamelFolder *)folder_unmatched, (CamelMessageInfo *)vinfo);
+ }
+ }
+ }
+}
+
+static void
+folder_changed_change_uid(CamelFolder *sub, const char *uid, const char hash[8], CamelVeeFolder *vf)
+{
+ char *vuid;
+ CamelVeeMessageInfo *vinfo, *uinfo = NULL;
+ CamelMessageInfo *info;
+ CamelFolder *folder = (CamelFolder *)vf;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+
+ vuid = alloca(strlen(uid)+9);
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+
+ vinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, vuid);
+ if (folder_unmatched != NULL)
+ uinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(((CamelFolder *)folder_unmatched)->summary, vuid);
+ if (vinfo || uinfo) {
+ info = camel_folder_get_message_info(sub, uid);
+ if (info) {
+ if (vinfo) {
+ int changed = FALSE;
+
+ if (vinfo->info.flags != info->flags){
+ vinfo->info.flags = info->flags;
+ changed = TRUE;
+ }
+
+ changed |= camel_flag_list_copy(&vinfo->info.user_flags, &info->user_flags);
+ changed |= camel_tag_list_copy(&vinfo->info.user_tags, &info->user_tags);
+ if (changed)
+ camel_folder_change_info_change_uid(vf->changes, vuid);
+
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)vinfo);
+ }
+
+ if (uinfo) {
+ int changed = FALSE;
+
+ if (uinfo->info.flags != info->flags){
+ uinfo->info.flags = info->flags;
+ changed = TRUE;
+ }
+
+ changed |= camel_flag_list_copy(&uinfo->info.user_flags, &info->user_flags);
+ changed |= camel_tag_list_copy(&uinfo->info.user_tags, &info->user_tags);
+ if (changed)
+ camel_folder_change_info_change_uid(folder_unmatched->changes, vuid);
+
+ camel_folder_summary_info_free(((CamelFolder *)folder_unmatched)->summary, (CamelMessageInfo *)uinfo);
+ }
+
+ camel_folder_free_message_info(sub, info);
+ } else {
+ if (vinfo) {
+ folder_changed_remove_uid(sub, uid, hash, FALSE, vf);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)vinfo);
+ }
+ if (uinfo)
+ camel_folder_summary_info_free(((CamelFolder *)folder_unmatched)->summary, (CamelMessageInfo *)uinfo);
+ }
+ }
+}
+
+struct _folder_changed_msg {
+ CamelSessionThreadMsg msg;
+ CamelFolderChangeInfo *changes;
+ CamelFolder *sub;
+ CamelVeeFolder *vf;
+};
+
+static void
+folder_changed_change(CamelSession *session, CamelSessionThreadMsg *msg)
+{
+ struct _folder_changed_msg *m = (struct _folder_changed_msg *)msg;
+ CamelFolder *sub = m->sub;
+ CamelFolder *folder = (CamelFolder *)m->vf;
+ CamelVeeFolder *vf = m->vf;
+ CamelFolderChangeInfo *changes = m->changes;
+ char *vuid = NULL, hash[8];
+ const char *uid;
+ CamelVeeMessageInfo *vinfo;
+ int i, vuidlen = 0;
+ CamelFolderChangeInfo *vf_changes = NULL, *unmatched_changes = NULL;
+ GPtrArray *matches_added = NULL, /* newly added, that match */
+ *matches_changed = NULL, /* newly changed, that now match */
+ *newchanged = NULL,
+ *changed;
+ GPtrArray *always_changed = NULL;
+ GHashTable *matches_hash;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+ GHashTable *unmatched_uids = vf->parent_vee_store ? vf->parent_vee_store->unmatched_uids : NULL;
+
+ /* Check the folder hasn't beem removed while we weren't watching */
+ CAMEL_VEE_FOLDER_LOCK(vf, subfolder_lock);
+ if (g_list_find(_PRIVATE(vf)->folders, sub) == NULL) {
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+ return;
+ }
+
+ camel_vee_folder_hash_folder(sub, hash);
+
+ /* Lookup anything before we lock anything, to avoid deadlock with build_folder */
+
+ /* Find newly added that match */
+ if (changes->uid_added->len > 0) {
+ dd(printf(" Searching for added matches '%s'\n", vf->expression));
+ matches_added = camel_folder_search_by_uids(sub, vf->expression, changes->uid_added, NULL);
+ }
+
+ /* TODO:
+ In this code around here, we can work out if the search will affect the changes
+ we had, and only re-search against them if they might have */
+
+ /* Search for changed items that newly match, but only if we dont have them */
+ changed = changes->uid_changed;
+ if (changed->len > 0) {
+ dd(printf(" Searching for changed matches '%s'\n", vf->expression));
+
+ if ((vf->flags & CAMEL_STORE_VEE_FOLDER_AUTO) == 0) {
+ newchanged = g_ptr_array_new();
+ always_changed = g_ptr_array_new();
+ for (i=0;i<changed->len;i++) {
+ uid = changed->pdata[i];
+ if (strlen(uid)+9 > vuidlen) {
+ vuidlen = strlen(uid)+64;
+ vuid = g_realloc(vuid, vuidlen);
+ }
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+ vinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, vuid);
+ if (vinfo == NULL) {
+ g_ptr_array_add(newchanged, (char *)uid);
+ } else {
+ g_ptr_array_add(always_changed, (char *)uid);
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)vinfo);
+ }
+ }
+ changed = newchanged;
+ }
+
+ if (changed->len)
+ matches_changed = camel_folder_search_by_uids(sub, vf->expression, changed, NULL);
+ }
+
+ CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
+ if (folder_unmatched != NULL)
+ CAMEL_VEE_FOLDER_LOCK(folder_unmatched, summary_lock);
+
+ dd(printf("Vfolder '%s' subfolder changed '%s'\n", folder->full_name, sub->full_name));
+ dd(printf(" changed %d added %d removed %d\n", changes->uid_changed->len, changes->uid_added->len, changes->uid_removed->len));
+
+ /* Always remove removed uid's, in any case */
+ for (i=0;i<changes->uid_removed->len;i++) {
+ dd(printf(" removing uid '%s'\n", (char *)changes->uid_removed->pdata[i]));
+ folder_changed_remove_uid(sub, changes->uid_removed->pdata[i], hash, FALSE, vf);
+ }
+
+ /* Add any newly matched or to unmatched folder if they dont */
+ if (matches_added) {
+ matches_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<matches_added->len;i++) {
+ dd(printf(" %s", (char *)matches_added->pdata[i]));
+ g_hash_table_insert(matches_hash, matches_added->pdata[i], matches_added->pdata[i]);
+ }
+ for (i=0;i<changes->uid_added->len;i++) {
+ uid = changes->uid_added->pdata[i];
+ if (g_hash_table_lookup(matches_hash, uid)) {
+ dd(printf(" adding uid '%s' [newly matched]\n", (char *)uid));
+ folder_changed_add_uid(sub, uid, hash, vf);
+ } else if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0) {
+ if (strlen(uid)+9 > vuidlen) {
+ vuidlen = strlen(uid)+64;
+ vuid = g_realloc(vuid, vuidlen);
+ }
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+
+ if (!CAMEL_IS_VEE_FOLDER(sub) && folder_unmatched != NULL && g_hash_table_lookup(unmatched_uids, vuid) == NULL) {
+ dd(printf(" adding uid '%s' to Unmatched [newly unmatched]\n", (char *)uid));
+ vinfo = (CamelVeeMessageInfo *)camel_folder_get_message_info((CamelFolder *)folder_unmatched, vuid);
+ if (vinfo == NULL) {
+ if (vee_folder_add_uid(folder_unmatched, sub, uid, hash))
+ camel_folder_change_info_add_uid(folder_unmatched->changes, vuid);
+ } else {
+ camel_folder_free_message_info((CamelFolder *)folder_unmatched, (CamelMessageInfo *)vinfo);
+ }
+ }
+ }
+ }
+ g_hash_table_destroy(matches_hash);
+ }
+
+ /* Change any newly changed */
+ if (always_changed) {
+ for (i=0;i<always_changed->len;i++)
+ folder_changed_change_uid(sub, always_changed->pdata[i], hash, vf);
+ g_ptr_array_free(always_changed, TRUE);
+ }
+
+ /* Change/add/remove any changed */
+ if (matches_changed) {
+ /* If we are auto-updating, then re-check changed uids still match */
+ dd(printf(" Vfolder %supdate\nuids match:", (vf->flags & CAMEL_STORE_VEE_FOLDER_AUTO)?"auto-":""));
+ matches_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i=0;i<matches_changed->len;i++) {
+ dd(printf(" %s", (char *)matches_changed->pdata[i]));
+ g_hash_table_insert(matches_hash, matches_changed->pdata[i], matches_changed->pdata[i]);
+ }
+ dd(printf("\n"));
+ for (i=0;i<changed->len;i++) {
+ uid = changed->pdata[i];
+ if (strlen(uid)+9 > vuidlen) {
+ vuidlen = strlen(uid)+64;
+ vuid = g_realloc(vuid, vuidlen);
+ }
+ memcpy(vuid, hash, 8);
+ strcpy(vuid+8, uid);
+ vinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(folder->summary, vuid);
+ if (vinfo == NULL) {
+ if (g_hash_table_lookup(matches_hash, uid)) {
+ /* A uid we dont have, but now it matches, add it */
+ dd(printf(" adding uid '%s' [newly matched]\n", uid));
+ folder_changed_add_uid(sub, uid, hash, vf);
+ } else {
+ /* A uid we still don't have, just change it (for unmatched) */
+ folder_changed_change_uid(sub, uid, hash, vf);
+ }
+ } else {
+ if ((vf->flags & CAMEL_STORE_VEE_FOLDER_AUTO) == 0
+ || g_hash_table_lookup(matches_hash, uid)) {
+ /* still match, or we're not auto-updating, change event, (if it changed) */
+ dd(printf(" changing uid '%s' [still matches]\n", uid));
+ folder_changed_change_uid(sub, uid, hash, vf);
+ } else {
+ /* No longer matches, remove it, but keep it in unmatched (potentially) */
+ dd(printf(" removing uid '%s' [did match]\n", uid));
+ folder_changed_remove_uid(sub, uid, hash, TRUE, vf);
+ }
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)vinfo);
+ }
+ }
+ g_hash_table_destroy(matches_hash);
+ } else {
+ /* stuff didn't match but it changed - check unmatched folder for changes */
+ for (i=0;i<changed->len;i++)
+ folder_changed_change_uid(sub, changed->pdata[i], hash, vf);
+ }
+
+ if (folder_unmatched != NULL) {
+ if (camel_folder_change_info_changed(folder_unmatched->changes)) {
+ unmatched_changes = folder_unmatched->changes;
+ folder_unmatched->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(folder_unmatched, summary_lock);
+ }
+
+ if (camel_folder_change_info_changed(vf->changes)) {
+ vf_changes = vf->changes;
+ vf->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
+
+ /* Cleanup stuff on our folder */
+ if (matches_added)
+ camel_folder_search_free(sub, matches_added);
+
+ if (matches_changed)
+ camel_folder_search_free(sub, matches_changed);
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, subfolder_lock);
+
+ /* cleanup the rest */
+ if (newchanged)
+ g_ptr_array_free(newchanged, TRUE);
+
+ g_free(vuid);
+
+ if (unmatched_changes) {
+ camel_object_trigger_event((CamelObject *)folder_unmatched, "folder_changed", unmatched_changes);
+ camel_folder_change_info_free(unmatched_changes);
+ }
+
+ if (vf_changes) {
+ /* If not auto-updating, keep track of changed folders for later re-sync */
+ if ((vf->flags & CAMEL_STORE_VEE_FOLDER_AUTO) == 0) {
+ CAMEL_VEE_FOLDER_LOCK(vf, changed_lock);
+ if (g_list_find(vf->priv->folders_changed, sub) != NULL)
+ vf->priv->folders_changed = g_list_prepend(vf->priv->folders_changed, sub);
+ CAMEL_VEE_FOLDER_UNLOCK(vf, changed_lock);
+ }
+
+ camel_object_trigger_event((CamelObject *)vf, "folder_changed", vf_changes);
+ camel_folder_change_info_free(vf_changes);
+ }
+}
+
+static void
+folder_changed_free(CamelSession *session, CamelSessionThreadMsg *msg)
+{
+ struct _folder_changed_msg *m = (struct _folder_changed_msg *)msg;
+
+ camel_folder_change_info_free(m->changes);
+ camel_object_unref((CamelObject *)m->vf);
+ camel_object_unref((CamelObject *)m->sub);
+}
+
+static CamelSessionThreadOps folder_changed_ops = {
+ folder_changed_change,
+ folder_changed_free,
+};
+
+static void
+folder_changed(CamelFolder *sub, CamelFolderChangeInfo *changes, CamelVeeFolder *vf)
+{
+ struct _folder_changed_msg *m;
+ CamelSession *session = ((CamelService *)((CamelFolder *)vf)->parent_store)->session;
+
+ m = camel_session_thread_msg_new(session, &folder_changed_ops, sizeof(*m));
+ m->changes = camel_folder_change_info_new();
+ camel_folder_change_info_cat(m->changes, changes);
+ m->sub = sub;
+ camel_object_ref((CamelObject *)sub);
+ m->vf = vf;
+ camel_object_ref((CamelObject *)vf);
+ camel_session_thread_queue(session, &m->msg, 0);
+}
+
+/* track vanishing folders */
+static void
+subfolder_deleted(CamelFolder *f, void *event_data, CamelVeeFolder *vf)
+{
+ camel_vee_folder_remove_folder(vf, f);
+}
+
+static void
+subfolder_renamed_update(CamelVeeFolder *vf, CamelFolder *sub, char hash[8])
+{
+ int count, i;
+ CamelFolderChangeInfo *changes = NULL;
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+ GHashTable *unmatched_uids = vf->parent_vee_store ? vf->parent_vee_store->unmatched_uids : NULL;
+
+ CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
+
+ count = camel_folder_summary_count(((CamelFolder *)vf)->summary);
+ for (i=0;i<count;i++) {
+ CamelVeeMessageInfo *mi = (CamelVeeMessageInfo *)camel_folder_summary_index(((CamelFolder *)vf)->summary, i);
+ CamelVeeMessageInfo *vinfo;
+
+ if (mi == NULL)
+ continue;
+
+ if (mi->folder == sub) {
+ char *uid = (char *)camel_message_info_uid(mi);
+ char *oldkey;
+ void *oldval;
+
+ camel_folder_change_info_remove_uid(vf->changes, uid);
+ camel_folder_summary_remove(((CamelFolder *)vf)->summary, (CamelMessageInfo *)mi);
+
+ /* works since we always append on the end */
+ i--;
+ count--;
+
+ vinfo = vee_folder_add_uid(vf, sub, uid+8, hash);
+ if (vinfo)
+ camel_folder_change_info_add_uid(vf->changes, camel_message_info_uid(vinfo));
+
+ /* check unmatched uid's table for any matches */
+ if (vf == folder_unmatched
+ && g_hash_table_lookup_extended(unmatched_uids, uid, (void **)&oldkey, &oldval)) {
+ g_hash_table_remove(unmatched_uids, oldkey);
+ g_hash_table_insert(unmatched_uids, g_strdup(camel_message_info_uid(vinfo)), oldval);
+ g_free(oldkey);
+ }
+ }
+
+ camel_folder_summary_info_free(((CamelFolder *)vf)->summary, (CamelMessageInfo *)mi);
+ }
+
+ if (camel_folder_change_info_changed(vf->changes)) {
+ changes = vf->changes;
+ vf->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
+
+ if (changes) {
+ camel_object_trigger_event((CamelObject *)vf, "folder_changed", changes);
+ camel_folder_change_info_free(changes);
+ }
+}
+
+static void
+subfolder_renamed(CamelFolder *f, void *event_data, CamelVeeFolder *vf)
+{
+ char hash[8];
+ CamelVeeFolder *folder_unmatched = vf->parent_vee_store ? vf->parent_vee_store->folder_unmatched : NULL;
+
+ /* TODO: This could probably be done in another thread, tho it is pretty quick/memory bound */
+
+ /* Life just got that little bit harder, if the folder is renamed, it means it breaks all of our uid's.
+ We need to remove the old uid's, fix them up, then release the new uid's, for the uid's that match this folder */
+
+ camel_vee_folder_hash_folder(f, hash);
+
+ subfolder_renamed_update(vf, f, hash);
+ if (folder_unmatched != NULL)
+ subfolder_renamed_update(folder_unmatched, f, hash);
+}
+
+static void
+vee_freeze (CamelFolder *folder)
+{
+ CamelVeeFolder *vfolder = (CamelVeeFolder *)folder;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vfolder);
+ GList *node;
+
+ CAMEL_VEE_FOLDER_LOCK(vfolder, subfolder_lock);
+
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+
+ camel_folder_freeze(f);
+ node = node->next;
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vfolder, subfolder_lock);
+
+ /* call parent implementation */
+ CAMEL_FOLDER_CLASS (camel_vee_folder_parent)->freeze(folder);
+}
+
+static void
+vee_thaw(CamelFolder *folder)
+{
+ CamelVeeFolder *vfolder = (CamelVeeFolder *)folder;
+ struct _CamelVeeFolderPrivate *p = _PRIVATE(vfolder);
+ GList *node;
+
+ CAMEL_VEE_FOLDER_LOCK(vfolder, subfolder_lock);
+
+ node = p->folders;
+ while (node) {
+ CamelFolder *f = node->data;
+
+ camel_folder_thaw(f);
+ node = node->next;
+ }
+
+ CAMEL_VEE_FOLDER_UNLOCK(vfolder, subfolder_lock);
+
+ /* call parent implementation */
+ CAMEL_FOLDER_CLASS (camel_vee_folder_parent)->thaw(folder);
+}
diff --git a/camel/camel-vee-folder.h b/camel/camel-vee-folder.h
new file mode 100644
index 0000000000..fc1a9f3571
--- /dev/null
+++ b/camel/camel-vee-folder.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef _CAMEL_VEE_FOLDER_H
+#define _CAMEL_VEE_FOLDER_H
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#include <glib.h>
+#include <camel/camel-folder.h>
+
+#define CAMEL_VEE_FOLDER(obj) CAMEL_CHECK_CAST (obj, camel_vee_folder_get_type (), CamelVeeFolder)
+#define CAMEL_VEE_FOLDER_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_vee_folder_get_type (), CamelVeeFolderClass)
+#define CAMEL_IS_VEE_FOLDER(obj) CAMEL_CHECK_TYPE (obj, camel_vee_folder_get_type ())
+
+typedef struct _CamelVeeFolder CamelVeeFolder;
+typedef struct _CamelVeeFolderClass CamelVeeFolderClass;
+
+/* our message info includes the parent folder */
+typedef struct _CamelVeeMessageInfo {
+ CamelMessageInfo info;
+ CamelFolder *folder;
+} CamelVeeMessageInfo;
+
+struct _CamelVeeFolder {
+ CamelFolder parent;
+
+ struct _CamelVeeFolderPrivate *priv;
+
+ char *expression; /* query expression */
+ char *vname; /* local name */
+
+ guint32 flags; /* folder open flags */
+
+ CamelFolderChangeInfo *changes;
+ CamelFolderSearch *search;
+
+ /* only set-up if our parent is a vee-store, used also as a flag to
+ * say that this folder is part of the unmatched folder */
+ struct _CamelVeeStore *parent_vee_store;
+};
+
+struct _CamelVeeFolderClass {
+ CamelFolderClass parent_class;
+};
+
+#define CAMEL_UNMATCHED_NAME "UNMATCHED"
+
+CamelType camel_vee_folder_get_type (void);
+CamelFolder *camel_vee_folder_new (CamelStore *parent_store, const char *name, guint32 flags);
+void camel_vee_folder_construct (CamelVeeFolder *vf, CamelStore *parent_store, const char *full, const char *name, guint32 flags);
+
+CamelFolder *camel_vee_folder_get_location(CamelVeeFolder *vf, const CamelVeeMessageInfo *vinfo, char **realuid);
+
+void camel_vee_folder_add_folder (CamelVeeFolder *vf, CamelFolder *sub);
+void camel_vee_folder_remove_folder (CamelVeeFolder *vf, CamelFolder *sub);
+void camel_vee_folder_set_folders (CamelVeeFolder *vf, GList *folders);
+void camel_vee_folder_set_expression (CamelVeeFolder *vf, const char *expr);
+
+void camel_vee_folder_hash_folder (CamelFolder *folder, char buffer[8]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ! _CAMEL_VEE_FOLDER_H */
diff --git a/camel/camel-vee-store.c b/camel/camel-vee-store.c
new file mode 100644
index 0000000000..c3570b50de
--- /dev/null
+++ b/camel/camel-vee-store.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "camel-exception.h"
+#include "camel-vee-store.h"
+#include "camel-vee-folder.h"
+
+#include "camel-private.h"
+
+#include <string.h>
+
+#define d(x)
+
+static CamelFolder *vee_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+static void vee_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
+static void vee_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
+
+static void vee_sync (CamelStore *store, int expunge, CamelException *ex);
+static CamelFolder *vee_get_trash (CamelStore *store, CamelException *ex);
+static CamelFolder *vee_get_junk (CamelStore *store, CamelException *ex);
+
+static CamelFolderInfo *vee_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+
+static void camel_vee_store_class_init (CamelVeeStoreClass *klass);
+static void camel_vee_store_init (CamelVeeStore *obj);
+static void camel_vee_store_finalise (CamelObject *obj);
+
+static CamelStoreClass *camel_vee_store_parent;
+
+CamelType
+camel_vee_store_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_store_get_type (), "CamelVeeStore",
+ sizeof (CamelVeeStore),
+ sizeof (CamelVeeStoreClass),
+ (CamelObjectClassInitFunc) camel_vee_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_vee_store_init,
+ (CamelObjectFinalizeFunc) camel_vee_store_finalise);
+ }
+
+ return type;
+}
+
+static void
+camel_vee_store_class_init (CamelVeeStoreClass *klass)
+{
+ CamelStoreClass *store_class = (CamelStoreClass *) klass;
+
+ camel_vee_store_parent = (CamelStoreClass *)camel_store_get_type();
+
+ /* virtual method overload */
+ store_class->get_folder = vee_get_folder;
+ store_class->rename_folder = vee_rename_folder;
+ store_class->delete_folder = vee_delete_folder;
+ store_class->get_folder_info = vee_get_folder_info;
+ store_class->free_folder_info = camel_store_free_folder_info_full;
+
+ store_class->sync = vee_sync;
+ store_class->get_trash = vee_get_trash;
+ store_class->get_junk = vee_get_junk;
+}
+
+static void
+camel_vee_store_init (CamelVeeStore *obj)
+{
+ CamelStore *store = (CamelStore *)obj;
+
+ /* we dont want a vtrash/vjunk on this one */
+ store->flags &= ~(CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK);
+
+ /* Set up unmatched folder */
+ obj->unmatched_uids = g_hash_table_new (g_str_hash, g_str_equal);
+ obj->folder_unmatched = (CamelVeeFolder *)camel_object_new (camel_vee_folder_get_type ());
+ camel_vee_folder_construct (obj->folder_unmatched, store, CAMEL_UNMATCHED_NAME, _("Unmatched"), CAMEL_STORE_FOLDER_PRIVATE);
+}
+
+static void
+cvs_free_unmatched(void *key, void *value, void *data)
+{
+ g_free(key);
+}
+
+static void
+camel_vee_store_finalise (CamelObject *obj)
+{
+ CamelVeeStore *vstore = (CamelVeeStore *)obj;
+
+ g_hash_table_foreach(vstore->unmatched_uids, cvs_free_unmatched, NULL);
+ g_hash_table_destroy(vstore->unmatched_uids);
+ camel_object_unref(vstore->folder_unmatched);
+}
+
+/**
+ * camel_vee_store_new:
+ *
+ * Create a new CamelVeeStore object.
+ *
+ * Return value: A new CamelVeeStore widget.
+ **/
+CamelVeeStore *
+camel_vee_store_new (void)
+{
+ CamelVeeStore *new = CAMEL_VEE_STORE(camel_object_new(camel_vee_store_get_type ()));
+ return new;
+}
+
+/* flags
+ 1 = delete (0 = add)
+ 2 = noselect
+*/
+#define CHANGE_ADD (0)
+#define CHANGE_DELETE (1)
+#define CHANGE_NOSELECT (2)
+
+static void
+change_folder(CamelStore *store, const char *name, guint32 flags, int count)
+{
+ CamelFolderInfo *fi;
+ const char *tmp;
+ CamelURL *url;
+
+ fi = g_malloc0(sizeof(*fi));
+ fi->full_name = g_strdup(name);
+ tmp = strrchr(name, '/');
+ if (tmp == NULL)
+ tmp = name;
+ else
+ tmp++;
+ fi->name = g_strdup(tmp);
+ url = camel_url_new("vfolder:", 0);
+ camel_url_set_path(url, ((CamelService *)store)->url->path);
+ if (flags & CHANGE_NOSELECT)
+ camel_url_set_param(url, "noselect", "yes");
+ camel_url_set_fragment(url, name);
+ fi->uri = camel_url_to_string(url, 0);
+ camel_url_free(url);
+ /*fi->url = g_strdup_printf("vfolder:%s%s#%s", ((CamelService *)store)->url->path, (flags&CHANGE_NOSELECT)?";noselect=yes":"", name);*/
+ fi->unread = count;
+ fi->flags = CAMEL_FOLDER_VIRTUAL;
+ if (!(flags & CHANGE_DELETE))
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+ camel_object_trigger_event(store, (flags&CHANGE_DELETE)?"folder_deleted":"folder_created", fi);
+ camel_folder_info_free(fi);
+}
+
+static CamelFolder *
+vee_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelVeeFolder *vf;
+ CamelFolder *folder;
+ char *name, *p;
+
+ vf = (CamelVeeFolder *)camel_vee_folder_new(store, folder_name, flags);
+ if ((vf->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0) {
+ /* Check that parents exist, if not, create dummy ones */
+ name = alloca(strlen(vf->vname)+1);
+ strcpy(name, vf->vname);
+ p = name;
+ while ( (p = strchr(p, '/'))) {
+ *p = 0;
+
+ folder = camel_object_bag_reserve(store->folders, name);
+ if (folder == NULL) {
+ /* create a dummy vFolder for this, makes get_folder_info simpler */
+ folder = camel_vee_folder_new(store, name, flags);
+ camel_object_bag_add(store->folders, name, folder);
+ change_folder(store, name, CHANGE_ADD|CHANGE_NOSELECT, 0);
+ /* FIXME: this sort of leaks folder, nobody owns a ref to it but us */
+ } else {
+ camel_object_unref(folder);
+ }
+ *p++='/';
+ }
+
+ change_folder(store, vf->vname, CHANGE_ADD, camel_folder_get_message_count((CamelFolder *)vf));
+ }
+
+ return (CamelFolder *)vf;
+}
+
+static void
+vee_sync(CamelStore *store, int expunge, CamelException *ex)
+{
+ /* noop */;
+}
+
+static CamelFolder *
+vee_get_trash (CamelStore *store, CamelException *ex)
+{
+ return NULL;
+}
+
+static CamelFolder *
+vee_get_junk (CamelStore *store, CamelException *ex)
+{
+ return NULL;
+}
+
+static int
+vee_folder_cmp(const void *ap, const void *bp)
+{
+ return strcmp(((CamelFolder **)ap)[0]->full_name, ((CamelFolder **)bp)[0]->full_name);
+}
+
+static CamelFolderInfo *
+vee_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *info, *res = NULL, *tail;
+ GPtrArray *folders;
+ GHashTable *infos_hash;
+ CamelURL *url;
+ int i;
+
+ d(printf("Get folder info '%s'\n", top?top:"<null>"));
+
+ infos_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ folders = camel_object_bag_list(store->folders);
+ qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), vee_folder_cmp);
+ for (i=0;i<folders->len;i++) {
+ CamelVeeFolder *folder = folders->pdata[i];
+ int add = FALSE;
+ char *name = ((CamelFolder *)folder)->full_name, *pname, *tmp;
+ CamelFolderInfo *pinfo;
+
+ d(printf("folder '%s'\n", name));
+
+ /* check we have to include this one */
+ if (top) {
+ int namelen = strlen(name);
+ int toplen = strlen(top);
+
+ add = ((namelen == toplen
+ && strcmp(name, top) == 0)
+ || ((namelen > toplen)
+ && strncmp(name, top, toplen) == 0
+ && name[toplen] == '/'
+ && ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
+ || strchr(name+toplen+1, '/') == NULL)));
+ } else {
+ add = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
+ || strchr(name, '/') == NULL;
+ }
+
+ d(printf("%sadding '%s'\n", add?"":"not ", name));
+
+ if (add) {
+ /* ensures unread is correct */
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ camel_folder_refresh_info((CamelFolder *)folder, NULL);
+
+ info = g_malloc0(sizeof(*info));
+ url = camel_url_new("vfolder:", NULL);
+ camel_url_set_path(url, ((CamelService *)((CamelFolder *)folder)->parent_store)->url->path);
+ camel_url_set_fragment(url, ((CamelFolder *)folder)->full_name);
+ info->uri = camel_url_to_string(url, 0);
+ camel_url_free(url);
+/*
+ info->url = g_strdup_printf("vfolder:%s#%s", ((CamelService *)((CamelFolder *)folder)->parent_store)->url->path,
+ ((CamelFolder *)folder)->full_name);*/
+ info->full_name = g_strdup(((CamelFolder *)folder)->full_name);
+ info->name = g_strdup(((CamelFolder *)folder)->name);
+ info->unread = camel_folder_get_unread_message_count((CamelFolder *)folder);
+ info->flags = CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_VIRTUAL;
+ g_hash_table_insert(infos_hash, info->full_name, info);
+
+ if (res == NULL)
+ res = info;
+ } else {
+ info = NULL;
+ }
+
+ /* check for parent, if present, update flags and if adding, update parent linkage */
+ pname = g_strdup(((CamelFolder *)folder)->full_name);
+ d(printf("looking up parent of '%s'\n", pname));
+ tmp = strrchr(pname, '/');
+ if (tmp) {
+ *tmp = 0;
+ pinfo = g_hash_table_lookup(infos_hash, pname);
+ } else
+ pinfo = NULL;
+
+ if (pinfo) {
+ pinfo->flags = (pinfo->flags & ~(CAMEL_FOLDER_CHILDREN|CAMEL_FOLDER_NOCHILDREN))|CAMEL_FOLDER_CHILDREN;
+ d(printf("updating parent flags for children '%s' %08x\n", pinfo->full_name, pinfo->flags));
+ tail = pinfo->child;
+ if (tail == NULL)
+ pinfo->child = info;
+ } else if (info != res) {
+ tail = res;
+ } else {
+ tail = NULL;
+ }
+
+ if (info && tail) {
+ while (tail->next)
+ tail = tail->next;
+ tail->next = info;
+ info->parent = pinfo;
+ }
+
+ g_free(pname);
+ camel_object_unref(folder);
+ }
+ g_ptr_array_free(folders, TRUE);
+ g_hash_table_destroy(infos_hash);
+
+ /* and always add UNMATCHED, if scanning from top/etc */
+ if (top == NULL || top[0] == 0 || strncmp(top, CAMEL_UNMATCHED_NAME, strlen(CAMEL_UNMATCHED_NAME)) == 0) {
+ info = g_malloc0(sizeof(*info));
+ url = camel_url_new("vfolder:", NULL);
+ camel_url_set_path(url, ((CamelService *)store)->url->path);
+ camel_url_set_fragment(url, CAMEL_UNMATCHED_NAME);
+ info->uri = camel_url_to_string(url, 0);
+ camel_url_free(url);
+ /*info->url = g_strdup_printf("vfolder:%s#%s", ((CamelService *)store)->url->path, CAMEL_UNMATCHED_NAME);*/
+ info->full_name = g_strdup(CAMEL_UNMATCHED_NAME);
+ info->name = g_strdup(_("Unmatched"));
+ info->unread = -1;
+ info->flags = CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_VIRTUAL;
+
+ if (res == NULL)
+ res = info;
+ else {
+ tail = res;
+ while (tail->next)
+ tail = tail->next;
+ tail->next = info;
+ }
+ }
+
+ return res;
+}
+
+static void
+vee_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ if (strcmp(folder_name, CAMEL_UNMATCHED_NAME) == 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot delete folder: %s: Invalid operation"), folder_name);
+ return;
+ }
+
+ folder = camel_object_bag_get(store->folders, folder_name);
+ if (folder) {
+ char *statefile;
+
+ camel_object_get(folder, NULL, CAMEL_OBJECT_STATE_FILE, &statefile, NULL);
+ if (statefile) {
+ unlink(statefile);
+ camel_object_free(folder, CAMEL_OBJECT_STATE_FILE, statefile);
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, NULL, NULL);
+ }
+
+ if ((((CamelVeeFolder *)folder)->flags & CAMEL_STORE_FOLDER_PRIVATE) == 0) {
+ /* what about now-empty parents? ignore? */
+ change_folder(store, folder_name, CHANGE_DELETE, -1);
+ }
+
+ camel_object_unref(folder);
+ } else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot delete folder: %s: No such folder"), folder_name);
+ }
+}
+
+static void
+vee_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
+{
+ CamelFolder *folder, *oldfolder;
+ char *p, *name;
+
+ d(printf("vee rename folder '%s' '%s'\n", old, new));
+
+ if (strcmp(old, CAMEL_UNMATCHED_NAME) == 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot rename folder: %s: Invalid operation"), old);
+ return;
+ }
+
+ /* See if it exists, for vfolders, all folders are in the folders hash */
+ oldfolder = camel_object_bag_get(store->folders, old);
+ if (oldfolder == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot rename folder: %s: No such folder"), old);
+ return;
+ }
+
+ /* Check that new parents exist, if not, create dummy ones */
+ name = alloca(strlen(new)+1);
+ strcpy(name, new);
+ p = name;
+ while ( (p = strchr(p, '/'))) {
+ *p = 0;
+
+ folder = camel_object_bag_reserve(store->folders, name);
+ if (folder == NULL) {
+ /* create a dummy vFolder for this, makes get_folder_info simpler */
+ folder = camel_vee_folder_new(store, name, ((CamelVeeFolder *)oldfolder)->flags);
+ camel_object_bag_add(store->folders, name, folder);
+ change_folder(store, name, CHANGE_ADD|CHANGE_NOSELECT, 0);
+ /* FIXME: this sort of leaks folder, nobody owns a ref to it but us */
+ } else {
+ camel_object_unref(folder);
+ }
+ *p++='/';
+ }
+
+ camel_object_unref(oldfolder);
+}
diff --git a/camel/camel-vee-store.h b/camel/camel-vee-store.h
new file mode 100644
index 0000000000..a5a9864a56
--- /dev/null
+++ b/camel/camel-vee-store.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef _CAMEL_VEE_STORE_H
+#define _CAMEL_VEE_STORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#include <glib.h>
+#include <camel/camel-store.h>
+
+#define CAMEL_VEE_STORE(obj) CAMEL_CHECK_CAST (obj, camel_vee_store_get_type (), CamelVeeStore)
+#define CAMEL_VEE_STORE_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_vee_store_get_type (), CamelVeeStoreClass)
+#define CAMEL_IS_VEE_STORE(obj) CAMEL_CHECK_TYPE (obj, camel_vee_store_get_type ())
+
+typedef struct _CamelVeeStore CamelVeeStore;
+typedef struct _CamelVeeStoreClass CamelVeeStoreClass;
+
+/* open mode for folder, vee folder auto-update */
+#define CAMEL_STORE_VEE_FOLDER_AUTO (1<<16)
+
+struct _CamelVeeStore {
+ CamelStore parent;
+
+ /* Unmatched folder, set up in camel_vee_store_init */
+ struct _CamelVeeFolder *folder_unmatched;
+ GHashTable *unmatched_uids;
+};
+
+struct _CamelVeeStoreClass {
+ CamelStoreClass parent_class;
+};
+
+CamelType camel_vee_store_get_type (void);
+CamelVeeStore *camel_vee_store_new (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ! _CAMEL_VEE_STORE_H */
diff --git a/camel/camel-vtrash-folder.c b/camel/camel-vtrash-folder.c
new file mode 100644
index 0000000000..e0da46f7c3
--- /dev/null
+++ b/camel/camel-vtrash-folder.c
@@ -0,0 +1,230 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+
+#include "camel-exception.h"
+#include "camel-vtrash-folder.h"
+#include "camel-store.h"
+#include "camel-vee-store.h"
+#include "camel-mime-message.h"
+
+#include <string.h>
+
+/* Returns the class for a CamelFolder */
+#define CF_CLASS(so) ((CamelFolderClass *)((CamelObject *)(so))->klass)
+
+static struct {
+ const char *full_name;
+ const char *name;
+ const char *expr;
+ guint32 bit;
+ guint32 flags;
+ const char *error_copy;
+} vdata[] = {
+ { CAMEL_VTRASH_NAME, N_("Trash"), "(match-all (system-flag \"Deleted\"))", CAMEL_MESSAGE_DELETED, CAMEL_FOLDER_IS_TRASH,
+ N_("Cannot copy messages to the Trash folder") },
+ { CAMEL_VJUNK_NAME, N_("Junk"), "(match-all (system-flag \"Junk\"))", CAMEL_MESSAGE_JUNK, CAMEL_FOLDER_IS_JUNK,
+ N_("Cannot copy messages to the Junk folder") },
+};
+
+static CamelVeeFolderClass *camel_vtrash_folder_parent;
+
+static void vtrash_append_message (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex);
+static void vtrash_transfer_messages_to (CamelFolder *folder, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals, CamelException *ex);
+
+static void
+camel_vtrash_folder_class_init (CamelVTrashFolderClass *klass)
+{
+ CamelFolderClass *folder_class = (CamelFolderClass *) klass;
+
+ camel_vtrash_folder_parent = CAMEL_VEE_FOLDER_CLASS(camel_vee_folder_get_type());
+
+ folder_class->append_message = vtrash_append_message;
+ folder_class->transfer_messages_to = vtrash_transfer_messages_to;
+}
+
+static void
+camel_vtrash_folder_init (CamelVTrashFolder *vtrash)
+{
+ /*CamelFolder *folder = CAMEL_FOLDER (vtrash);*/
+}
+
+CamelType
+camel_vtrash_folder_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_vee_folder_get_type (),
+ "CamelVTrashFolder",
+ sizeof (CamelVTrashFolder),
+ sizeof (CamelVTrashFolderClass),
+ (CamelObjectClassInitFunc) camel_vtrash_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_vtrash_folder_init,
+ NULL);
+ }
+
+ return type;
+}
+
+/**
+ * camel_vtrash_folder_new:
+ * @parent_store: the parent CamelVeeStore
+ * @type: type of vfolder, CAMEL_VTRASH_FOLDER_TRASH or CAMEL_VTRASH_FOLDER_JUNK currently.
+ * @ex: a CamelException
+ *
+ * Create a new CamelVeeFolder object.
+ *
+ * Return value: A new CamelVeeFolder widget.
+ **/
+CamelFolder *
+camel_vtrash_folder_new (CamelStore *parent_store, enum _camel_vtrash_folder_t type)
+{
+ CamelVTrashFolder *vtrash;
+
+ g_assert(type < CAMEL_VTRASH_FOLDER_LAST);
+
+ vtrash = (CamelVTrashFolder *)camel_object_new(camel_vtrash_folder_get_type());
+ camel_vee_folder_construct(CAMEL_VEE_FOLDER (vtrash), parent_store, vdata[type].full_name, _(vdata[type].name),
+ CAMEL_STORE_FOLDER_PRIVATE|CAMEL_STORE_FOLDER_CREATE|CAMEL_STORE_VEE_FOLDER_AUTO);
+
+ ((CamelFolder *)vtrash)->folder_flags |= vdata[type].flags;
+ camel_vee_folder_set_expression((CamelVeeFolder *)vtrash, vdata[type].expr);
+ vtrash->bit = vdata[type].bit;
+ vtrash->type = type;
+
+ return (CamelFolder *)vtrash;
+}
+
+static void
+vtrash_append_message (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex)
+{
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _(vdata[((CamelVTrashFolder *)folder)->type].error_copy));
+}
+
+struct _transfer_data {
+ CamelFolder *folder;
+ CamelFolder *dest;
+ GPtrArray *uids;
+ gboolean delete;
+};
+
+static void
+transfer_messages(CamelFolder *folder, struct _transfer_data *md, CamelException *ex)
+{
+ int i;
+
+ if (!camel_exception_is_set (ex))
+ camel_folder_transfer_messages_to(md->folder, md->uids, md->dest, NULL, md->delete, ex);
+
+ for (i=0;i<md->uids->len;i++)
+ g_free(md->uids->pdata[i]);
+ g_ptr_array_free(md->uids, TRUE);
+ camel_object_unref((CamelObject *)md->folder);
+ g_free(md);
+}
+
+static void
+vtrash_transfer_messages_to (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals, CamelException *ex)
+{
+ CamelVeeMessageInfo *mi;
+ int i;
+ GHashTable *batch = NULL;
+ const char *tuid;
+ struct _transfer_data *md;
+ guint32 sbit = ((CamelVTrashFolder *)source)->bit;
+
+ /* This is a special case of transfer_messages_to: Either the
+ * source or the destination is a vtrash folder (but not both
+ * since a store should never have more than one).
+ */
+
+ if (transferred_uids)
+ *transferred_uids = NULL;
+
+ if (CAMEL_IS_VTRASH_FOLDER (dest)) {
+ /* Copy to trash is meaningless. */
+ if (!delete_originals) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _(vdata[((CamelVTrashFolder *)dest)->type].error_copy));
+ return;
+ }
+
+ /* Move to trash is the same as setting the message flag */
+ for (i = 0; i < uids->len; i++)
+ camel_folder_set_message_flags(source, uids->pdata[i], ((CamelVTrashFolder *)dest)->bit, ~0);
+ return;
+ }
+
+ /* Moving/Copying from the trash to the original folder = undelete.
+ * Moving/Copying from the trash to a different folder = move/copy.
+ *
+ * Need to check this uid by uid, but we batch up the copies.
+ */
+
+ for (i = 0; i < uids->len; i++) {
+ mi = (CamelVeeMessageInfo *)camel_folder_get_message_info (source, uids->pdata[i]);
+ if (mi == NULL) {
+ g_warning ("Cannot find uid %s in source folder during transfer", (char *) uids->pdata[i]);
+ continue;
+ }
+
+ if (dest == mi->folder) {
+ /* Just unset the flag on the original message */
+ camel_folder_set_message_flags (source, uids->pdata[i], sbit, 0);
+ } else {
+ if (batch == NULL)
+ batch = g_hash_table_new(NULL, NULL);
+ md = g_hash_table_lookup(batch, mi->folder);
+ if (md == NULL) {
+ md = g_malloc0(sizeof(*md));
+ md->folder = mi->folder;
+ camel_object_ref((CamelObject *)md->folder);
+ md->uids = g_ptr_array_new();
+ md->dest = dest;
+ g_hash_table_insert(batch, mi->folder, md);
+ }
+
+ tuid = uids->pdata[i];
+ if (strlen(tuid)>8)
+ tuid += 8;
+ g_ptr_array_add(md->uids, g_strdup(tuid));
+ }
+ camel_folder_free_message_info (source, (CamelMessageInfo *)mi);
+ }
+
+ if (batch) {
+ g_hash_table_foreach(batch, (GHFunc)transfer_messages, ex);
+ g_hash_table_destroy(batch);
+ }
+}
diff --git a/camel/providers/groupwise/camel-gw-listener.c b/camel/providers/groupwise/camel-gw-listener.c
new file mode 100644
index 0000000000..9da66d6b6d
--- /dev/null
+++ b/camel/providers/groupwise/camel-gw-listener.c
@@ -0,0 +1,889 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors :
+ *
+ * Sivaiah Nallagatla <snallagatla@novell.com>
+ *
+ * Copyright 2003, Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "camel-gw-listener.h"
+#include <string.h>
+#include "camel-i18n.h"
+#include <e-gw-connection.h>
+#include <e-passwords.h>
+#include "widgets/misc/e-error.h"
+
+/*stores some info about all currently existing groupwise accounts
+ list of GwAccountInfo structures */
+
+static GList *groupwise_accounts = NULL;
+
+struct _CamelGwListenerPrivate {
+ GConfClient *gconf_client;
+ /* we get notification about mail account changes form this object */
+ EAccountList *account_list;
+};
+
+struct _GwAccountInfo {
+ char *uid;
+ char *name;
+ char *source_url;
+};
+
+typedef struct _GwAccountInfo GwAccountInfo;
+
+#define GROUPWISE_URI_PREFIX "groupwise://"
+#define GROUPWISE_PREFIX_LENGTH 12
+
+#define PARENT_TYPE G_TYPE_OBJECT
+
+static GObjectClass *parent_class = NULL;
+
+static void dispose (GObject *object);
+static void finalize (GObject *object);
+
+
+static void
+camel_gw_listener_class_init (CamelGwListenerClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_ref (PARENT_TYPE);
+ object_class = G_OBJECT_CLASS (class);
+
+ /* virtual method override */
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+}
+
+static void
+camel_gw_listener_init (CamelGwListener *config_listener, CamelGwListenerClass *class)
+{
+ config_listener->priv = g_new0 (CamelGwListenerPrivate, 1);
+}
+
+static void
+dispose (GObject *object)
+{
+ CamelGwListener *config_listener = CAMEL_GW_LISTENER (object);
+
+ g_object_unref (config_listener->priv->gconf_client);
+ g_object_unref (config_listener->priv->account_list);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ CamelGwListener *config_listener = CAMEL_GW_LISTENER (object);
+ GList *list;
+ GwAccountInfo *info;
+
+ if (config_listener->priv) {
+ g_free (config_listener->priv);
+ }
+
+ for ( list = g_list_first (groupwise_accounts); list ; list = g_list_next (list) ) {
+
+ info = (GwAccountInfo *) (list->data);
+
+ if (info) {
+
+ g_free (info->uid);
+ g_free (info->name);
+ g_free (info->source_url);
+ g_free (info);
+ }
+ }
+
+ g_list_free (groupwise_accounts);
+}
+
+/*determines whehter the passed in account is groupwise or not by looking at source url */
+
+static gboolean
+is_groupwise_account (EAccount *account)
+{
+ if (account->source->url != NULL) {
+ return (strncmp (account->source->url, GROUPWISE_URI_PREFIX, GROUPWISE_PREFIX_LENGTH ) == 0);
+ } else {
+ return FALSE;
+ }
+}
+
+/* looks up for an existing groupwise account info in the groupwise_accounts list based on uid */
+
+static GwAccountInfo*
+lookup_account_info (const char *key)
+{
+ GList *list;
+ GwAccountInfo *info ;
+ int found = 0;
+
+ if (!key)
+ return NULL;
+
+ info = NULL;
+
+ for (list = g_list_first (groupwise_accounts); list; list = g_list_next (list)) {
+ info = (GwAccountInfo *) (list->data);
+ found = (strcmp (info->uid, key) == 0);
+ if (found)
+ break;
+ }
+ if (found)
+ return info;
+ return NULL;
+}
+
+#define CALENDAR_SOURCES "/apps/evolution/calendar/sources"
+#define TASKS_SOURCES "/apps/evolution/tasks/sources"
+#define SELECTED_CALENDARS "/apps/evolution/calendar/display/selected_calendars"
+#define SELECTED_TASKS "/apps/evolution/calendar/tasks/selected_tasks"
+
+static void
+add_esource (const char *conf_key, const char *group_name, const char* source_name, const char *username, const char* relative_uri, const char *soap_port, const char *use_ssl)
+{
+ ESourceList *source_list;
+ ESourceGroup *group;
+ ESource *source;
+ GConfClient* client;
+ GSList *ids, *temp ;
+ char *source_selection_key;
+
+ client = gconf_client_get_default();
+ source_list = e_source_list_new_for_gconf (client, conf_key);
+
+ group = e_source_group_new (group_name, GROUPWISE_URI_PREFIX);
+ if ( !e_source_list_add_group (source_list, group, -1))
+ return;
+
+ source = e_source_new (source_name, relative_uri);
+ e_source_set_property (source, "auth", "1");
+ e_source_set_property (source, "username", username);
+ e_source_set_property (source, "port", soap_port);
+ e_source_set_property (source, "auth-domain", "Groupwise");
+ e_source_set_property (source, "use_ssl", use_ssl);
+ e_source_group_add_source (group, source, -1);
+ e_source_list_sync (source_list, NULL);
+
+ if (!strcmp (conf_key, CALENDAR_SOURCES))
+ source_selection_key = SELECTED_CALENDARS;
+ else if (!strcmp (conf_key, TASKS_SOURCES))
+ source_selection_key = SELECTED_TASKS;
+ else source_selection_key = NULL;
+ if (source_selection_key) {
+ ids = gconf_client_get_list (client, source_selection_key , GCONF_VALUE_STRING, NULL);
+ ids = g_slist_append (ids, g_strdup (e_source_peek_uid (source)));
+ gconf_client_set_list (client, source_selection_key, GCONF_VALUE_STRING, ids, NULL);
+ temp = ids;
+ for (; temp != NULL; temp = g_slist_next (temp))
+ g_free (temp->data);
+ g_slist_free (ids);
+ }
+
+ g_object_unref (source);
+ g_object_unref (group);
+ g_object_unref (source_list);
+ g_object_unref (client);
+}
+
+
+static void
+remove_esource (const char *conf_key, const char *group_name, char* source_name, const char* relative_uri)
+{
+ ESourceList *list;
+ ESourceGroup *group;
+ ESource *source;
+ GSList *groups;
+ GSList *sources;
+ gboolean found_group;
+ GConfClient* client;
+ GSList *ids;
+ GSList *node_tobe_deleted;
+ char *source_selection_key;
+
+ client = gconf_client_get_default();
+ list = e_source_list_new_for_gconf (client, conf_key);
+ groups = e_source_list_peek_groups (list);
+
+ found_group = FALSE;
+
+ for ( ; groups != NULL && !found_group; groups = g_slist_next (groups)) {
+
+ group = E_SOURCE_GROUP (groups->data);
+
+ if (strcmp (e_source_group_peek_name (group), group_name) == 0 &&
+ strcmp (e_source_group_peek_base_uri (group), GROUPWISE_URI_PREFIX ) == 0) {
+
+ sources = e_source_group_peek_sources (group);
+
+ for( ; sources != NULL; sources = g_slist_next (sources)) {
+
+ source = E_SOURCE (sources->data);
+
+ if (strcmp (e_source_peek_relative_uri (source), relative_uri) == 0) {
+
+ if (!strcmp (conf_key, CALENDAR_SOURCES))
+ source_selection_key = SELECTED_CALENDARS;
+ else if (!strcmp (conf_key, TASKS_SOURCES))
+ source_selection_key = SELECTED_TASKS;
+ else source_selection_key = NULL;
+ if (source_selection_key) {
+ ids = gconf_client_get_list (client, source_selection_key ,
+ GCONF_VALUE_STRING, NULL);
+ node_tobe_deleted = g_slist_find_custom (ids, e_source_peek_uid (source), (GCompareFunc) strcmp);
+ if (node_tobe_deleted) {
+ g_free (node_tobe_deleted->data);
+ ids = g_slist_delete_link (ids, node_tobe_deleted);
+ }
+ gconf_client_set_list (client, source_selection_key,
+ GCONF_VALUE_STRING, ids, NULL);
+
+ }
+ e_source_list_remove_group (list, group);
+ e_source_list_sync (list, NULL);
+ found_group = TRUE;
+ break;
+
+ }
+ }
+
+ }
+
+
+ }
+
+ g_object_unref (list);
+ g_object_unref (client);
+
+}
+
+/* looks up for e-source with having same info as old_account_info and changes its values passed in new values */
+
+static void
+modify_esource (const char* conf_key, GwAccountInfo *old_account_info, const char* new_group_name, const char *username, const char* new_relative_uri, const char *soap_port, const char *use_ssl)
+{
+ ESourceList *list;
+ ESourceGroup *group;
+ ESource *source;
+ GSList *groups;
+ GSList *sources;
+ char *old_relative_uri;
+ CamelURL *url;
+ gboolean found_group;
+ GConfClient* client;
+ const char *poa_address;
+
+ url = camel_url_new (old_account_info->source_url, NULL);
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+ old_relative_uri = g_strdup_printf ("%s@%s/", url->user, poa_address);
+ client = gconf_client_get_default ();
+ list = e_source_list_new_for_gconf (client, conf_key);
+ groups = e_source_list_peek_groups (list);
+
+ found_group = FALSE;
+
+ for ( ; groups != NULL && !found_group; groups = g_slist_next (groups)) {
+
+ group = E_SOURCE_GROUP (groups->data);
+
+ if (strcmp (e_source_group_peek_name (group), old_account_info->name) == 0 &&
+ strcmp (e_source_group_peek_base_uri (group), GROUPWISE_URI_PREFIX) == 0) {
+
+ sources = e_source_group_peek_sources (group);
+
+ for ( ; sources != NULL; sources = g_slist_next (sources)) {
+
+ source = E_SOURCE (sources->data);
+
+ if (strcmp (e_source_peek_relative_uri (source), old_relative_uri) == 0) {
+
+ e_source_group_set_name (group, new_group_name);
+ e_source_set_relative_uri (source, new_relative_uri);
+ e_source_set_property (source, "username", username);
+ e_source_set_property (source, "port", soap_port);
+ e_source_set_property (source, "use_ssl", use_ssl);
+ e_source_list_sync (list, NULL);
+ found_group = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ g_object_unref (list);
+ g_object_unref (client);
+ camel_url_free (url);
+ g_free (old_relative_uri);
+
+}
+/* add sources for calendar and tasks if the account added is groupwise account
+ adds the new account info to groupwise_accounts list */
+
+static void
+add_calendar_tasks_sources (GwAccountInfo *info)
+{
+ CamelURL *url;
+ char *relative_uri;
+ const char *soap_port;
+ const char * use_ssl;
+ const char *poa_address;
+
+ url = camel_url_new (info->source_url, NULL);
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+ soap_port = camel_url_get_param (url, "soap_port");
+
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7181";
+
+ use_ssl = camel_url_get_param (url, "soap_ssl");
+ if (use_ssl)
+ use_ssl = "always";
+ else
+ use_ssl = NULL;
+
+ relative_uri = g_strdup_printf ("%s@%s/", url->user, poa_address);
+ add_esource ("/apps/evolution/calendar/sources", info->name, _("Calendar"), url->user, relative_uri, soap_port, use_ssl);
+ add_esource ("/apps/evolution/tasks/sources", info->name, _("Tasks"), url->user, relative_uri, soap_port, use_ssl);
+
+ camel_url_free (url);
+ g_free (relative_uri);
+
+}
+
+/* removes calendar and tasks sources if the account removed is groupwise account
+ removes the the account info from groupwise_account list */
+
+static void
+remove_calendar_tasks_sources (GwAccountInfo *info)
+{
+ CamelURL *url;
+ char *relative_uri;
+ const char *soap_port;
+ const char *poa_address;
+
+ url = camel_url_new (info->source_url, NULL);
+
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+
+ soap_port = camel_url_get_param (url, "soap_port");
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7181";
+
+ relative_uri = g_strdup_printf ("%s@%s/", url->user, poa_address);
+ remove_esource ("/apps/evolution/calendar/sources", info->name, _("Calendar"), relative_uri);
+ remove_esource ("/apps/evolution/tasks/sources", info->name, _("Checklist"), relative_uri);
+ camel_url_free (url);
+ g_free (relative_uri);
+
+}
+
+static GList*
+get_addressbook_names_from_server (char *source_url)
+{
+ char *key;
+ EGwConnection *cnc;
+ char *password;
+ GList *book_list;
+ int status;
+ const char *soap_port;
+ CamelURL *url;
+ gboolean remember;
+ char *failed_auth;
+ char *prompt;
+ char *uri;
+ const char *use_ssl;
+ const char *poa_address;
+ guint32 flags = E_PASSWORDS_REMEMBER_FOREVER;
+
+ url = camel_url_new (source_url, NULL);
+ if (url == NULL) {
+ return NULL;
+ }
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return NULL;
+
+ soap_port = camel_url_get_param (url, "soap_port");
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7181";
+ use_ssl = camel_url_get_param (url, "soap_ssl");
+ if(use_ssl)
+ use_ssl = "always";
+ key = g_strdup_printf ("groupwise://%s@%s/", url->user, poa_address);
+ if (use_ssl)
+ uri = g_strdup_printf ("https://%s:%s/soap", poa_address, soap_port);
+ else
+ uri = g_strdup_printf ("http://%s:%s/soap", poa_address, soap_port);
+
+ failed_auth = "";
+ cnc = NULL;
+ do {
+ prompt = g_strdup_printf (_("%sEnter password for %s (user %s)"),
+ failed_auth, poa_address, url->user);
+
+ password = e_passwords_ask_password (prompt, "Groupwise", key, prompt,
+ E_PASSWORDS_REMEMBER_FOREVER|E_PASSWORDS_SECRET, &remember,
+ NULL);
+ g_free (prompt);
+
+ if (!password)
+ break;
+ cnc = e_gw_connection_new (uri, url->user, password);
+ failed_auth = _("Failed to authenticate.\n");
+ flags |= E_PASSWORDS_REPROMPT;
+ } while (cnc == NULL);
+
+ if (E_IS_GW_CONNECTION(cnc)) {
+ book_list = NULL;
+ status = e_gw_connection_get_address_book_list (cnc, &book_list);
+ if (status == E_GW_CONNECTION_STATUS_OK)
+ return book_list;
+
+
+ }
+ e_error_run (NULL, "mail:gw-accountsetup-error", poa_address, NULL);
+ return NULL;
+}
+
+
+static gboolean
+add_addressbook_sources (EAccount *account)
+{
+ CamelURL *url;
+ ESourceList *list;
+ ESourceGroup *group;
+ ESource *source;
+ char *base_uri;
+ const char *soap_port;
+ GList *books_list, *temp_list;
+ GConfClient* client;
+ const char* use_ssl;
+ const char *poa_address;
+
+
+ url = camel_url_new (account->source->url, NULL);
+ if (url == NULL) {
+ return FALSE;
+ }
+
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return FALSE;
+
+ soap_port = camel_url_get_param (url, "soap_port");
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7181";
+ use_ssl = camel_url_get_param (url, "soap_ssl");
+ base_uri = g_strdup_printf ("groupwise://%s@%s", url->user, poa_address);
+ client = gconf_client_get_default ();
+ list = e_source_list_new_for_gconf (client, "/apps/evolution/addressbook/sources" );
+ group = e_source_group_new (account->name, base_uri);
+ books_list = get_addressbook_names_from_server (account->source->url);
+ temp_list = books_list;
+ if (!temp_list)
+ return FALSE;
+ for (; temp_list != NULL; temp_list = g_list_next (temp_list)) {
+ const char *book_name = e_gw_container_get_name (E_GW_CONTAINER(temp_list->data));
+ source = e_source_new (book_name, g_strconcat (";",book_name, NULL));
+ e_source_set_property (source, "auth", "plain/password");
+ e_source_set_property (source, "auth-domain", "Groupwise");
+ e_source_set_property (source, "port", soap_port);
+ e_source_set_property(source, "user", url->user);
+
+ if (!e_gw_container_get_is_writable (E_GW_CONTAINER(temp_list->data)))
+ e_source_set_property (source, "completion", "true");
+ if (e_gw_container_get_is_frequent_contacts (E_GW_CONTAINER(temp_list->data)))
+ e_source_set_property (source, "completion", "true");
+ e_source_set_property (source, "use_ssl", use_ssl);
+ e_source_group_add_source (group, source, -1);
+ g_object_unref (source);
+ g_object_unref (E_GW_CONTAINER(temp_list->data));
+
+ }
+
+ g_list_free (books_list);
+
+
+ e_source_list_add_group (list, group, -1);
+ e_source_list_sync (list, NULL);
+ g_object_unref (group);
+ g_object_unref (list);
+ g_object_unref (client);
+ g_free (base_uri);
+
+ return TRUE;
+}
+
+static void
+modify_addressbook_sources ( EAccount *account, GwAccountInfo *existing_account_info )
+{
+ CamelURL *url;
+ ESourceList *list;
+ ESourceGroup *group;
+ GSList *groups;
+ gboolean found_group;
+ gboolean delete_group;
+ char *old_base_uri;
+ char *new_base_uri;
+ const char *soap_port;
+ const char *use_ssl;
+ GSList *sources;
+ ESource *source;
+ GConfClient *client;
+ const char *poa_address;
+
+ url = camel_url_new (existing_account_info->source_url, NULL);
+ if (url == NULL) {
+ return;
+ }
+
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+
+ old_base_uri = g_strdup_printf ("groupwise://%s@%s", url->user, poa_address);
+ camel_url_free (url);
+
+ url = camel_url_new (account->source->url, NULL);
+ if (url == NULL)
+ return ;
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+ new_base_uri = g_strdup_printf ("groupwise://%s@%s", url->user, poa_address);
+ soap_port = camel_url_get_param (url, "soap_port");
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7181";
+ use_ssl = camel_url_get_param (url, "soap_ssl");
+
+ client = gconf_client_get_default ();
+ list = e_source_list_new_for_gconf (client, "/apps/evolution/addressbook/sources" );
+ groups = e_source_list_peek_groups (list);
+ delete_group = FALSE;
+ if (strcmp (old_base_uri, new_base_uri) != 0)
+ delete_group = TRUE;
+ group = NULL;
+ found_group = FALSE;
+ for ( ; groups != NULL && !found_group; groups = g_slist_next (groups)) {
+
+ group = E_SOURCE_GROUP (groups->data);
+ if ( strcmp ( e_source_group_peek_base_uri(group), old_base_uri) == 0 && strcmp (e_source_group_peek_name (group), existing_account_info->name) == 0) {
+ found_group = TRUE;
+ if (!delete_group) {
+ e_source_group_set_name (group, account->name);
+ sources = e_source_group_peek_sources (group);
+ for (; sources != NULL; sources = g_slist_next (sources)) {
+ source = E_SOURCE (sources->data);
+ e_source_set_property (source, "port", soap_port);
+ e_source_set_property (source, "use_ssl", use_ssl);
+ }
+
+ e_source_list_sync (list, NULL);
+ }
+
+ }
+ }
+ if (found_group && delete_group) {
+ e_source_list_remove_group (list, group);
+ e_source_list_sync (list, NULL);
+ g_object_unref (list);
+ list = NULL;
+ add_addressbook_sources (account);
+ }
+ g_free (old_base_uri);
+ if (list)
+ g_object_unref (list);
+ camel_url_free (url);
+ g_object_unref (client);
+
+
+}
+
+static void
+remove_addressbook_sources (GwAccountInfo *existing_account_info)
+{
+ ESourceList *list;
+ ESourceGroup *group;
+ GSList *groups;
+ gboolean found_group;
+ CamelURL *url;
+ char *base_uri;
+ const char *soap_port;
+ GConfClient *client;
+ const char *poa_address;
+
+ url = camel_url_new (existing_account_info->source_url, NULL);
+ if (url == NULL) {
+ return;
+ }
+
+ poa_address = camel_url_get_param (url, "poa");
+ if (!poa_address || strlen (poa_address) ==0)
+ return;
+
+ soap_port = camel_url_get_param (url, "soap_port");
+ if (!soap_port || strlen (soap_port) == 0)
+ soap_port = "7181";
+ base_uri = g_strdup_printf ("groupwise://%s@%s", url->user, poa_address);
+ client = gconf_client_get_default ();
+ list = e_source_list_new_for_gconf (client, "/apps/evolution/addressbook/sources" );
+ groups = e_source_list_peek_groups (list);
+
+ found_group = FALSE;
+
+ for ( ; groups != NULL && !found_group; groups = g_slist_next (groups)) {
+
+ group = E_SOURCE_GROUP (groups->data);
+ if ( strcmp ( e_source_group_peek_base_uri (group), base_uri) == 0 && strcmp (e_source_group_peek_name (group), existing_account_info->name) == 0) {
+
+ e_source_list_remove_group (list, group);
+ e_source_list_sync (list, NULL);
+ found_group = TRUE;
+
+ }
+ }
+ g_object_unref (list);
+ g_object_unref (client);
+ g_free (base_uri);
+ camel_url_free (url);
+
+
+}
+
+
+
+static void
+account_added (EAccountList *account_listener, EAccount *account)
+{
+
+ GwAccountInfo *info;
+ gboolean status;
+
+ if (!is_groupwise_account (account))
+ return;
+
+ info = g_new0 (GwAccountInfo, 1);
+ info->uid = g_strdup (account->uid);
+ info->name = g_strdup (account->name);
+ info->source_url = g_strdup (account->source->url);
+ status = add_addressbook_sources (account);
+ if (status)
+ add_calendar_tasks_sources (info);
+ groupwise_accounts = g_list_append (groupwise_accounts, info);
+
+}
+
+static void
+account_removed (EAccountList *account_listener, EAccount *account)
+{
+ GwAccountInfo *info;
+
+ if (!is_groupwise_account (account))
+ return;
+
+ info = lookup_account_info (account->uid);
+ if (info == NULL) {
+ return;
+ }
+
+ remove_calendar_tasks_sources (info);
+ remove_addressbook_sources (info);
+ groupwise_accounts = g_list_remove (groupwise_accounts, info);
+ g_free (info->uid);
+ g_free (info->name);
+ g_free (info->source_url);
+ g_free (info);
+
+
+}
+
+
+static void
+account_changed (EAccountList *account_listener, EAccount *account)
+{
+ gboolean is_gw_account;
+ CamelURL *old_url, *new_url;
+ char *relative_uri;
+ const char *old_soap_port, *new_soap_port;
+ GwAccountInfo *existing_account_info;
+ const char *old_use_ssl, *new_use_ssl;
+ gboolean old_ssl, new_ssl;
+ const char *old_poa_address, *new_poa_address;
+
+ is_gw_account = is_groupwise_account (account);
+
+ existing_account_info = lookup_account_info (account->uid);
+
+ if (existing_account_info == NULL && is_gw_account) {
+
+ if (!account->enabled)
+ return;
+
+ /* some account of other type is changed to Groupwise */
+ account_added (account_listener, account);
+
+ } else if ( existing_account_info != NULL && !is_gw_account) {
+
+ /*Groupwise account is changed to some other type */
+ remove_calendar_tasks_sources (existing_account_info);
+ remove_addressbook_sources (existing_account_info);
+ groupwise_accounts = g_list_remove (groupwise_accounts, existing_account_info);
+ g_free (existing_account_info->uid);
+ g_free (existing_account_info->name);
+ g_free (existing_account_info->source_url);
+ g_free (existing_account_info);
+
+ } else if ( existing_account_info != NULL && is_gw_account ) {
+
+ if (!account->enabled) {
+ account_removed (account_listener, account);
+ return;
+ }
+ old_ssl = new_ssl = FALSE;
+ /* some info of groupwise account is changed . update the sources with new info if required */
+ old_url = camel_url_new (existing_account_info->source_url, NULL);
+ old_poa_address = camel_url_get_param (old_url, "poa");
+ old_soap_port = camel_url_get_param (old_url, "soap_port");
+ old_use_ssl = camel_url_get_param (old_url, "soap_ssl");
+ if (old_use_ssl)
+ old_ssl = TRUE;
+ new_url = camel_url_new (account->source->url, NULL);
+
+ new_poa_address = camel_url_get_param (new_url, "poa");
+ if (!new_poa_address || strlen (new_poa_address) ==0)
+ return;
+ new_soap_port = camel_url_get_param (new_url, "soap_port");
+ if (!new_soap_port || strlen (new_soap_port) == 0)
+ new_soap_port = "7181";
+
+ new_use_ssl = camel_url_get_param (new_url, "soap_ssl");
+ if (new_use_ssl){
+ new_use_ssl = "always";
+ new_ssl = TRUE;
+ }
+
+ if ((old_poa_address && strcmp (old_poa_address, new_poa_address))
+ || (old_soap_port && strcmp (old_soap_port, new_soap_port))
+ || strcmp (old_url->user, new_url->user)
+ || ( old_ssl ^ new_ssl)) {
+
+ account_removed (account_listener, account);
+ account_added (account_listener, account);
+ } else if (strcmp (existing_account_info->name, account->name)) {
+
+ relative_uri = g_strdup_printf ("%s@%s/", new_url->user, new_poa_address);
+ modify_esource ("/apps/evolution/calendar/sources", existing_account_info, account->name, new_url->user, relative_uri, new_soap_port, new_use_ssl);
+ modify_esource ("/apps/evolution/tasks/sources", existing_account_info, account->name, new_url->user, relative_uri, new_soap_port, new_use_ssl);
+ modify_addressbook_sources (account, existing_account_info);
+ g_free (relative_uri);
+
+ }
+
+ g_free (existing_account_info->name);
+ g_free (existing_account_info->source_url);
+ existing_account_info->name = g_strdup (account->name);
+ existing_account_info->source_url = g_strdup (account->source->url);
+ camel_url_free (old_url);
+ camel_url_free (new_url);
+ }
+
+
+}
+
+
+
+static void
+camel_gw_listener_construct (CamelGwListener *config_listener)
+{
+ EIterator *iter;
+ EAccount *account;
+ GwAccountInfo *info ;
+
+ config_listener->priv->account_list = e_account_list_new (config_listener->priv->gconf_client);
+
+ for ( iter = e_list_get_iterator (E_LIST ( config_listener->priv->account_list) ) ; e_iterator_is_valid (iter); e_iterator_next (iter) ) {
+
+ account = E_ACCOUNT (e_iterator_get (iter));
+ if ( is_groupwise_account (account) && account->enabled) {
+
+ info = g_new0 (GwAccountInfo, 1);
+ info->uid = g_strdup (account->uid);
+ info->name = g_strdup (account->name);
+ info->source_url = g_strdup (account->source->url);
+ groupwise_accounts = g_list_append (groupwise_accounts, info);
+
+ }
+
+ }
+ g_signal_connect (config_listener->priv->account_list, "account_added", G_CALLBACK (account_added), NULL);
+ g_signal_connect (config_listener->priv->account_list, "account_changed", G_CALLBACK (account_changed), NULL);
+ g_signal_connect (config_listener->priv->account_list, "account_removed", G_CALLBACK (account_removed), NULL);
+
+
+}
+
+GType
+camel_gw_listener_get_type (void)
+{
+ static GType camel_gw_listener_type = 0;
+
+ if (!camel_gw_listener_type) {
+ static GTypeInfo info = {
+ sizeof (CamelGwListenerClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) camel_gw_listener_class_init,
+ NULL, NULL,
+ sizeof (CamelGwListener),
+ 0,
+ (GInstanceInitFunc) camel_gw_listener_init
+ };
+ camel_gw_listener_type = g_type_register_static (PARENT_TYPE, "CamelGwListener", &info, 0);
+ }
+
+ return camel_gw_listener_type;
+}
+
+CamelGwListener*
+camel_gw_listener_new ()
+{
+ CamelGwListener *config_listener;
+
+ config_listener = g_object_new (CAMEL_TYPE_GW_LISTENER, NULL);
+ config_listener->priv->gconf_client = gconf_client_get_default();
+
+ camel_gw_listener_construct (config_listener);
+
+ return config_listener;
+
+}
+
+
diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c
new file mode 100644
index 0000000000..207ce64be6
--- /dev/null
+++ b/camel/providers/imap/camel-imap-command.c
@@ -0,0 +1,847 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-command.c: IMAP command sending/parsing routines */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2000, 2001 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "camel-imap-command.h"
+#include "camel-imap-utils.h"
+#include "camel-imap-folder.h"
+#include "camel-imap-store.h"
+#include "camel-imap-store-summary.h"
+#include "camel-imap-private.h"
+#include <camel/camel-exception.h>
+#include <camel/camel-private.h>
+#include <camel/camel-utf8.h>
+#include <camel/camel-session.h>
+#include <camel/camel-debug.h>
+
+extern int camel_verbose_debug;
+
+static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ const char *cmd, CamelException *ex);
+CamelImapResponse *imap_read_response (CamelImapStore *store,
+ CamelException *ex);
+static char *imap_read_untagged (CamelImapStore *store, char *line,
+ CamelException *ex);
+static char *imap_command_strdup_vprintf (CamelImapStore *store,
+ const char *fmt, va_list ap);
+static char *imap_command_strdup_printf (CamelImapStore *store,
+ const char *fmt, ...);
+
+/**
+ * camel_imap_command:
+ * @store: the IMAP store
+ * @folder: The folder to perform the operation in (or %NULL if not
+ * relevant).
+ * @ex: a CamelException
+ * @fmt: a sort of printf-style format string, followed by arguments
+ *
+ * This function calls camel_imap_command_start() to send the
+ * command, then reads the complete response to it using
+ * camel_imap_command_response() and returns a CamelImapResponse
+ * structure.
+ *
+ * As a special case, if @fmt is %NULL, it will just select @folder
+ * and return the response from doing so.
+ *
+ * See camel_imap_command_start() for details on @fmt.
+ *
+ * On success, the store's connect_lock will be locked. It will be freed
+ * when you call camel_imap_response_free. (The lock is recursive, so
+ * callers can grab and release it themselves if they need to run
+ * multiple commands atomically.)
+ *
+ * Return value: %NULL if an error occurred (in which case @ex will
+ * be set). Otherwise, a CamelImapResponse describing the server's
+ * response, which the caller must free with camel_imap_response_free().
+ **/
+CamelImapResponse *
+camel_imap_command (CamelImapStore *store, CamelFolder *folder,
+ CamelException *ex, const char *fmt, ...)
+{
+ va_list ap;
+ char *cmd;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ if (fmt) {
+ va_start (ap, fmt);
+ cmd = imap_command_strdup_vprintf (store, fmt, ap);
+ va_end (ap);
+ } else {
+ camel_object_ref(folder);
+ if (store->current_folder)
+ camel_object_unref(store->current_folder);
+ store->current_folder = folder;
+ cmd = imap_command_strdup_printf (store, "SELECT %F", folder->full_name);
+ }
+
+ if (!imap_command_start (store, folder, cmd, ex)) {
+ g_free (cmd);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return NULL;
+ }
+ g_free (cmd);
+
+ return imap_read_response (store, ex);
+}
+
+/**
+ * camel_imap_command_start:
+ * @store: the IMAP store
+ * @folder: The folder to perform the operation in (or %NULL if not
+ * relevant).
+ * @ex: a CamelException
+ * @fmt: a sort of printf-style format string, followed by arguments
+ *
+ * This function makes sure that @folder (if non-%NULL) is the
+ * currently-selected folder on @store and then sends the IMAP command
+ * specified by @fmt and the following arguments.
+ *
+ * @fmt can include the following %-escapes ONLY:
+ * %s, %d, %%: as with printf
+ * %S: an IMAP "string" (quoted string or literal)
+ * %F: an IMAP folder name
+ *
+ * %S strings will be passed as literals if the server supports LITERAL+
+ * and quoted strings otherwise. (%S does not support strings that
+ * contain newlines.)
+ *
+ * %F will have the imap store's namespace prepended and then be processed
+ * like %S.
+ *
+ * On success, the store's connect_lock will be locked. It will be
+ * freed when %CAMEL_IMAP_RESPONSE_TAGGED or %CAMEL_IMAP_RESPONSE_ERROR
+ * is returned from camel_imap_command_response(). (The lock is
+ * recursive, so callers can grab and release it themselves if they
+ * need to run multiple commands atomically.)
+ *
+ * Return value: %TRUE if the command was sent successfully, %FALSE if
+ * an error occurred (in which case @ex will be set).
+ **/
+gboolean
+camel_imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ CamelException *ex, const char *fmt, ...)
+{
+ va_list ap;
+ char *cmd;
+ gboolean ok;
+
+ va_start (ap, fmt);
+ cmd = imap_command_strdup_vprintf (store, fmt, ap);
+ va_end (ap);
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ ok = imap_command_start (store, folder, cmd, ex);
+ g_free (cmd);
+
+ if (!ok)
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return ok;
+}
+
+static gboolean
+imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ const char *cmd, CamelException *ex)
+{
+ ssize_t nwritten;
+
+ /* Check for current folder */
+ if (folder && folder != store->current_folder) {
+ CamelImapResponse *response;
+ CamelException internal_ex;
+
+ response = camel_imap_command (store, folder, ex, NULL);
+ if (!response)
+ return FALSE;
+ camel_exception_init (&internal_ex);
+ camel_imap_folder_selected (folder, response, &internal_ex);
+ camel_imap_response_free (store, response);
+ if (camel_exception_is_set (&internal_ex)) {
+ camel_exception_xfer (ex, &internal_ex);
+ return FALSE;
+ }
+ }
+
+ /* Send the command */
+ if (camel_verbose_debug) {
+ const char *mask;
+
+ if (!strncmp ("LOGIN \"", cmd, 7))
+ mask = "LOGIN \"xxx\" xxx";
+ else if (!strncmp ("LOGIN {", cmd, 7))
+ mask = "LOGIN {N+}\r\nxxx {N+}\r\nxxx";
+ else if (!strncmp ("LOGIN ", cmd, 6))
+ mask = "LOGIN xxx xxx";
+ else
+ mask = cmd;
+
+ fprintf (stderr, "sending : %c%.5d %s\r\n", store->tag_prefix, store->command, mask);
+ }
+
+ nwritten = camel_stream_printf (store->ostream, "%c%.5d %s\r\n",
+ store->tag_prefix, store->command++, cmd);
+
+ if (nwritten == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Operation cancelled"));
+ else
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ g_strerror (errno));
+
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * camel_imap_command_continuation:
+ * @store: the IMAP store
+ * @cmd: buffer containing the response/request data
+ * @cmdlen: command length
+ * @ex: a CamelException
+ *
+ * This method is for sending continuing responses to the IMAP server
+ * after camel_imap_command() or camel_imap_command_response() returns
+ * a continuation response.
+ *
+ * This function assumes you have an exclusive lock on the imap stream.
+ *
+ * Return value: as for camel_imap_command(). On failure, the store's
+ * connect_lock will be released.
+ **/
+CamelImapResponse *
+camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
+ size_t cmdlen, CamelException *ex)
+{
+ if (!camel_imap_store_connected (store, ex))
+ return NULL;
+
+ if (camel_stream_write (store->ostream, cmd, cmdlen) == -1 ||
+ camel_stream_write (store->ostream, "\r\n", 2) == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Operation cancelled"));
+ else
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ g_strerror (errno));
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return NULL;
+ }
+
+ return imap_read_response (store, ex);
+}
+
+/**
+ * camel_imap_command_response:
+ * @store: the IMAP store
+ * @response: a pointer to pass back the response data in
+ * @ex: a CamelException
+ *
+ * This reads a single tagged, untagged, or continuation response from
+ * @store into *@response. The caller must free the string when it is
+ * done with it.
+ *
+ * Return value: One of %CAMEL_IMAP_RESPONSE_CONTINUATION,
+ * %CAMEL_IMAP_RESPONSE_UNTAGGED, %CAMEL_IMAP_RESPONSE_TAGGED, or
+ * %CAMEL_IMAP_RESPONSE_ERROR. If either of the last two, @store's
+ * command lock will be unlocked.
+ **/
+CamelImapResponseType
+camel_imap_command_response (CamelImapStore *store, char **response,
+ CamelException *ex)
+{
+ CamelImapResponseType type;
+ char *respbuf;
+
+ if (camel_imap_store_readline (store, &respbuf, ex) < 0) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return CAMEL_IMAP_RESPONSE_ERROR;
+ }
+
+ switch (*respbuf) {
+ case '*':
+ if (!strncasecmp (respbuf, "* BYE", 5)) {
+ /* Connection was lost, no more data to fetch */
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Server unexpectedly disconnected: %s"),
+ _("Unknown error")); /* g_strerror (104)); FIXME after 1.0 is released */
+ store->connected = FALSE;
+ g_free (respbuf);
+ respbuf = NULL;
+ type = CAMEL_IMAP_RESPONSE_ERROR;
+ break;
+ }
+
+ /* Read the rest of the response. */
+ type = CAMEL_IMAP_RESPONSE_UNTAGGED;
+ respbuf = imap_read_untagged (store, respbuf, ex);
+ if (!respbuf)
+ type = CAMEL_IMAP_RESPONSE_ERROR;
+ else if (!g_ascii_strncasecmp (respbuf, "* OK [ALERT]", 12)
+ || !g_ascii_strncasecmp (respbuf, "* NO [ALERT]", 12)
+ || !g_ascii_strncasecmp (respbuf, "* BAD [ALERT]", 13)) {
+ char *msg;
+
+ /* for imap ALERT codes, account user@host */
+ /* we might get a ']' from a BAD response since we +12, but who cares? */
+ msg = g_strdup_printf(_("Alert from IMAP server %s@%s:\n%s"),
+ ((CamelService *)store)->url->user, ((CamelService *)store)->url->host, respbuf+12);
+ camel_session_alert_user(((CamelService *)store)->session, CAMEL_SESSION_ALERT_WARNING, msg, FALSE);
+ g_free(msg);
+ }
+
+ break;
+ case '+':
+ type = CAMEL_IMAP_RESPONSE_CONTINUATION;
+ break;
+ default:
+ type = CAMEL_IMAP_RESPONSE_TAGGED;
+ break;
+ }
+ *response = respbuf;
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR ||
+ type == CAMEL_IMAP_RESPONSE_TAGGED)
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ return type;
+}
+
+CamelImapResponse *
+imap_read_response (CamelImapStore *store, CamelException *ex)
+{
+ CamelImapResponse *response;
+ CamelImapResponseType type;
+ char *respbuf, *p;
+
+ /* Get another lock so that when we reach the tagged
+ * response and camel_imap_command_response unlocks,
+ * we're still locked. This lock is owned by response
+ * and gets unlocked when response is freed.
+ */
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ response = g_new0 (CamelImapResponse, 1);
+ if (store->current_folder && camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
+ response->folder = store->current_folder;
+ camel_object_ref (CAMEL_OBJECT (response->folder));
+ }
+
+ response->untagged = g_ptr_array_new ();
+ while ((type = camel_imap_command_response (store, &respbuf, ex))
+ == CAMEL_IMAP_RESPONSE_UNTAGGED)
+ g_ptr_array_add (response->untagged, respbuf);
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR) {
+ camel_imap_response_free_without_processing (store, response);
+ return NULL;
+ }
+
+ response->status = respbuf;
+
+ /* Check for OK or continuation response. */
+ if (*respbuf == '+')
+ return response;
+ p = strchr (respbuf, ' ');
+ if (p && !strncasecmp (p, " OK", 3))
+ return response;
+
+ /* We should never get BAD, or anything else but +, OK, or NO
+ * for that matter.
+ */
+ if (!p || strncasecmp (p, " NO", 3) != 0) {
+ g_warning ("Unexpected response from IMAP server: %s",
+ respbuf);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Unexpected response from IMAP "
+ "server: %s"), respbuf);
+ camel_imap_response_free_without_processing (store, response);
+ return NULL;
+ }
+
+ p += 3;
+ if (!*p++)
+ p = NULL;
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP command failed: %s"),
+ p ? p : _("Unknown error"));
+ camel_imap_response_free_without_processing (store, response);
+ return NULL;
+}
+
+/* Given a line that is the start of an untagged response, read and
+ * return the complete response, which may include an arbitrary number
+ * of literals.
+ */
+static char *
+imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
+{
+ int fulllen, ldigits, nread, i, sexp = 0;
+ unsigned int length;
+ GPtrArray *data;
+ GString *str;
+ char *end, *p, *s, *d;
+
+ p = strrchr (line, '{');
+ if (!p)
+ return line;
+
+ data = g_ptr_array_new ();
+ fulllen = 0;
+
+ while (1) {
+ str = g_string_new (line);
+ g_free (line);
+ fulllen += str->len;
+ g_ptr_array_add (data, str);
+
+ p = strrchr (str->str, '{');
+ if (!p)
+ break;
+
+ /* HACK ALERT: We scan the non-literal part of the string, looking for possible s expression braces.
+ This assumes we're getting s-expressions, which we should be.
+ This is so if we get a blank line after a literal, in an s-expression, we can keep going, since
+ we do no other parsing at this level.
+ TODO: handle quoted strings? */
+ for (s=str->str; s<p; s++) {
+ if (*s == '(')
+ sexp++;
+ else if (*s == ')')
+ sexp--;
+ }
+
+ length = strtoul (p + 1, &end, 10);
+ if (*end != '}' || *(end + 1) || end == p + 1 || length >= UINT_MAX - 2)
+ break;
+ ldigits = end - (p + 1);
+
+ /* Read the literal */
+ str = g_string_sized_new (length + 2);
+ str->str[0] = '\n';
+ nread = camel_stream_read (store->istream, str->str + 1, length);
+ if (nread == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Operation cancelled"));
+ else
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ g_strerror (errno));
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ g_string_free (str, TRUE);
+ goto lose;
+ }
+ if (nread < length) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Server response ended too soon."));
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ g_string_free (str, TRUE);
+ goto lose;
+ }
+ str->str[length + 1] = '\0';
+
+ if (camel_debug("imap")) {
+ printf("Literal: -->");
+ fwrite(str->str+1, 1, length, stdout);
+ printf("<--\n");
+ }
+
+ /* Fix up the literal, turning CRLFs into LF. Also, if
+ * we find any embedded NULs, strip them. This is
+ * dubious, but:
+ * - The IMAP grammar says you can't have NULs here
+ * anyway, so this will not affect our behavior
+ * against any completely correct server.
+ * - WU-imapd 12.264 (at least) will cheerily pass
+ * NULs along if they are embedded in the message
+ */
+
+ s = d = str->str + 1;
+ end = str->str + 1 + length;
+ while (s < end) {
+ while (s < end && *s == '\0') {
+ s++;
+ length--;
+ }
+ if (*s == '\r' && *(s + 1) == '\n') {
+ s++;
+ length--;
+ }
+ *d++ = *s++;
+ }
+ *d = '\0';
+ str->len = length + 1;
+
+ /* p points to the "{" in the line that starts the
+ * literal. The length of the CR-less response must be
+ * less than or equal to the length of the response
+ * with CRs, therefore overwriting the old value with
+ * the new value cannot cause an overrun. However, we
+ * don't want it to be shorter either, because then the
+ * GString's length would be off...
+ */
+ sprintf (p, "{%0*d}", ldigits, length);
+
+ fulllen += str->len;
+ g_ptr_array_add (data, str);
+
+ /* Read the next line. */
+ do {
+ if (camel_imap_store_readline (store, &line, ex) < 0)
+ goto lose;
+
+ /* MAJOR HACK ALERT, gropuwise sometimes sends an extra blank line after literals, check that here
+ But only do it if we're inside an sexpression */
+ if (line[0] == 0 && sexp > 0)
+ g_warning("Server sent empty line after a literal, assuming in error");
+ } while (line[0] == 0 && sexp > 0);
+ }
+
+ /* Now reassemble the data. */
+ p = line = g_malloc (fulllen + 1);
+ for (i = 0; i < data->len; i++) {
+ str = data->pdata[i];
+ memcpy (p, str->str, str->len);
+ p += str->len;
+ g_string_free (str, TRUE);
+ }
+ *p = '\0';
+ g_ptr_array_free (data, TRUE);
+ return line;
+
+ lose:
+ for (i = 0; i < data->len; i++)
+ g_string_free (data->pdata[i], TRUE);
+ g_ptr_array_free (data, TRUE);
+ return NULL;
+}
+
+
+/**
+ * camel_imap_response_free:
+ * @store: the CamelImapStore the response is from
+ * @response: a CamelImapResponse
+ *
+ * Frees all of the data in @response and processes any untagged
+ * EXPUNGE and EXISTS responses in it. Releases @store's connect_lock.
+ **/
+void
+camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
+{
+ int i, number, exists = 0;
+ GArray *expunged = NULL;
+ char *resp, *p;
+
+ if (!response)
+ return;
+
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i];
+
+ if (response->folder) {
+ /* Check if it's something we need to handle. */
+ number = strtoul (resp + 2, &p, 10);
+ if (!g_ascii_strcasecmp (p, " EXISTS")) {
+ exists = number;
+ } else if (!strcasecmp (p, " EXPUNGE")) {
+ if (!expunged) {
+ expunged = g_array_new (FALSE, FALSE,
+ sizeof (int));
+ }
+ g_array_append_val (expunged, number);
+ }
+ }
+ g_free (resp);
+ }
+
+ g_ptr_array_free (response->untagged, TRUE);
+ g_free (response->status);
+
+ if (response->folder) {
+ if (exists > 0 || expunged) {
+ /* Update the summary */
+ camel_imap_folder_changed (response->folder,
+ exists, expunged, NULL);
+ if (expunged)
+ g_array_free (expunged, TRUE);
+ }
+
+ camel_object_unref (CAMEL_OBJECT (response->folder));
+ }
+
+ g_free (response);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+/**
+ * camel_imap_response_free_without_processing:
+ * @store: the CamelImapStore the response is from.
+ * @response: a CamelImapResponse:
+ *
+ * Frees all of the data in @response without processing any untagged
+ * responses. Releases @store's command lock.
+ **/
+void
+camel_imap_response_free_without_processing (CamelImapStore *store,
+ CamelImapResponse *response)
+{
+ if (!response)
+ return;
+
+ if (response->folder) {
+ camel_object_unref (CAMEL_OBJECT (response->folder));
+ response->folder = NULL;
+ }
+ camel_imap_response_free (store, response);
+}
+
+/**
+ * camel_imap_response_extract:
+ * @store: the store the response came from
+ * @response: the response data returned from camel_imap_command
+ * @type: the response type to extract
+ * @ex: a CamelException
+ *
+ * This checks that @response contains a single untagged response of
+ * type @type and returns just that response data. If @response
+ * doesn't contain the right information, the function will set @ex
+ * and return %NULL. Either way, @response will be freed and the
+ * store's connect_lock released.
+ *
+ * Return value: the desired response string, which the caller must free.
+ **/
+char *
+camel_imap_response_extract (CamelImapStore *store,
+ CamelImapResponse *response,
+ const char *type,
+ CamelException *ex)
+{
+ int len = strlen (type), i;
+ char *resp;
+
+ len = strlen (type);
+
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i];
+ /* Skip "* ", and initial sequence number, if present */
+ strtoul (resp + 2, &resp, 10);
+ if (*resp == ' ')
+ resp = (char *) imap_next_word (resp);
+
+ if (!strncasecmp (resp, type, len))
+ break;
+ }
+
+ if (i < response->untagged->len) {
+ resp = response->untagged->pdata[i];
+ g_ptr_array_remove_index (response->untagged, i);
+ } else {
+ resp = NULL;
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("IMAP server response did not contain "
+ "%s information"), type);
+ }
+
+ camel_imap_response_free (store, response);
+ return resp;
+}
+
+/**
+ * camel_imap_response_extract_continuation:
+ * @store: the store the response came from
+ * @response: the response data returned from camel_imap_command
+ * @ex: a CamelException
+ *
+ * This checks that @response contains a continuation response, and
+ * returns just that data. If @response doesn't contain a continuation
+ * response, the function will set @ex, release @store's connect_lock,
+ * and return %NULL. Either way, @response will be freed.
+ *
+ * Return value: the desired response string, which the caller must free.
+ **/
+char *
+camel_imap_response_extract_continuation (CamelImapStore *store,
+ CamelImapResponse *response,
+ CamelException *ex)
+{
+ char *status;
+
+ if (response->status && *response->status == '+') {
+ status = response->status;
+ response->status = NULL;
+ camel_imap_response_free (store, response);
+ return status;
+ }
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Unexpected OK response from IMAP server: %s"),
+ response->status);
+ camel_imap_response_free (store, response);
+ return NULL;
+}
+
+static char *
+imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
+ va_list ap)
+{
+ GPtrArray *args;
+ const char *p, *start;
+ char *out, *outptr, *string;
+ int num, len, i, arglen;
+
+ args = g_ptr_array_new ();
+
+ /* Determine the length of the data */
+ len = strlen (fmt);
+ p = start = fmt;
+ while (*p) {
+ p = strchr (start, '%');
+ if (!p)
+ break;
+
+ switch (*++p) {
+ case 'd':
+ num = va_arg (ap, int);
+ g_ptr_array_add (args, GINT_TO_POINTER (num));
+ start = p + 1;
+ len += 10;
+ break;
+ case 's':
+ string = va_arg (ap, char *);
+ g_ptr_array_add (args, string);
+ start = p + 1;
+ len += strlen (string);
+ break;
+ case 'S':
+ case 'F':
+ string = va_arg (ap, char *);
+ if (*p == 'F') {
+ /* NB: this is freed during output */
+ char *s = camel_imap_store_summary_full_from_path(store->summary, string);
+ string = s?s:camel_utf8_utf7(string);
+ }
+ arglen = strlen (string);
+ g_ptr_array_add (args, string);
+ if (imap_is_atom (string)) {
+ len += arglen;
+ } else {
+ if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS)
+ len += arglen + 15;
+ else
+ len += arglen * 2;
+ }
+ start = p + 1;
+ break;
+ case '%':
+ start = p;
+ break;
+ default:
+ g_warning ("camel-imap-command is not printf. I don't "
+ "know what '%%%c' means.", *p);
+ start = *p ? p + 1 : p;
+ break;
+ }
+ }
+
+ /* Now write out the string */
+ outptr = out = g_malloc (len + 1);
+ p = start = fmt;
+ i = 0;
+ while (*p) {
+ p = strchr (start, '%');
+ if (!p) {
+ strcpy (outptr, start);
+ break;
+ } else {
+ strncpy (outptr, start, p - start);
+ outptr += p - start;
+ }
+
+ switch (*++p) {
+ case 'd':
+ num = GPOINTER_TO_INT (args->pdata[i++]);
+ outptr += sprintf (outptr, "%d", num);
+ break;
+
+ case 's':
+ string = args->pdata[i++];
+ outptr += sprintf (outptr, "%s", string);
+ break;
+ case 'S':
+ case 'F':
+ string = args->pdata[i++];
+ if (imap_is_atom (string)) {
+ outptr += sprintf (outptr, "%s", string);
+ } else {
+ if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS) {
+ outptr += sprintf (outptr, "{%d+}\r\n%s", (int)strlen(string), string);
+ } else {
+ char *quoted = imap_quote_string (string);
+
+ outptr += sprintf (outptr, "%s", quoted);
+ g_free (quoted);
+ }
+ }
+
+ if (*p == 'F')
+ g_free (string);
+ break;
+ default:
+ *outptr++ = '%';
+ *outptr++ = *p;
+ }
+
+ start = *p ? p + 1 : p;
+ }
+
+ return out;
+}
+
+static char *
+imap_command_strdup_printf (CamelImapStore *store, const char *fmt, ...)
+{
+ va_list ap;
+ char *result;
+
+ va_start (ap, fmt);
+ result = imap_command_strdup_vprintf (store, fmt, ap);
+ va_end (ap);
+
+ return result;
+}
diff --git a/camel/providers/imap/camel-imap-folder.c b/camel/providers/imap/camel-imap-folder.c
new file mode 100644
index 0000000000..0132f4e536
--- /dev/null
+++ b/camel/providers/imap/camel-imap-folder.c
@@ -0,0 +1,2814 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-folder.c: class for an imap folder */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 2000, 2001 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "e-util/e-path.h"
+#include "e-util/e-time-utils.h"
+
+#include "camel-imap-folder.h"
+#include "camel-imap-command.h"
+#include "camel-imap-message-cache.h"
+#include "camel-imap-private.h"
+#include "camel-imap-search.h"
+#include "camel-imap-store.h"
+#include "camel-imap-summary.h"
+#include "camel-imap-utils.h"
+#include "camel-imap-wrapper.h"
+#include "camel-data-wrapper.h"
+#include "camel-disco-diary.h"
+#include "camel-exception.h"
+#include "camel-mime-filter-crlf.h"
+#include "camel-mime-filter-from.h"
+#include "camel-mime-message.h"
+#include "camel-mime-utils.h"
+#include "camel-multipart.h"
+#include "camel-multipart-signed.h"
+#include "camel-multipart-encrypted.h"
+#include "camel-operation.h"
+#include "camel-session.h"
+#include "camel-stream-buffer.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+#include "camel-stream.h"
+#include "camel-private.h"
+#include "camel-string-utils.h"
+#include "camel-file-utils.h"
+#include "camel-debug.h"
+
+#define d(x)
+
+/* set to -1 for infinite size (suggested max command-line length is
+ * 1000 octets (see rfc2683), so we should keep the uid-set length to
+ * something under that so that our command-lines don't exceed 1000
+ * octets) */
+#define UID_SET_LIMIT (768)
+
+
+#define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(o)))
+static CamelDiscoFolderClass *disco_folder_class = NULL;
+
+static void imap_finalize (CamelObject *object);
+static int imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args);
+
+static void imap_rescan (CamelFolder *folder, int exists, CamelException *ex);
+static void imap_refresh_info (CamelFolder *folder, CamelException *ex);
+static void imap_sync_online (CamelFolder *folder, CamelException *ex);
+static void imap_sync_offline (CamelFolder *folder, CamelException *ex);
+static void imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, CamelException *ex);
+static void imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, CamelException *ex);
+static void imap_expunge_uids_resyncing (CamelFolder *folder, GPtrArray *uids, CamelException *ex);
+static void imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid, CamelException *ex);
+static void imap_rename (CamelFolder *folder, const char *new);
+
+/* message manipulation */
+static CamelMimeMessage *imap_get_message (CamelFolder *folder, const gchar *uid,
+ CamelException *ex);
+static void imap_append_online (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex);
+static void imap_append_offline (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex);
+static void imap_append_resyncing (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex);
+
+static void imap_transfer_online (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals,
+ CamelException *ex);
+static void imap_transfer_offline (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals,
+ CamelException *ex);
+static void imap_transfer_resyncing (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals,
+ CamelException *ex);
+
+/* searching */
+static GPtrArray *imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex);
+static GPtrArray *imap_search_by_uids (CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex);
+static void imap_search_free (CamelFolder *folder, GPtrArray *uids);
+
+static void imap_thaw (CamelFolder *folder);
+
+static CamelObjectClass *parent_class;
+
+static GData *parse_fetch_response (CamelImapFolder *imap_folder, char *msg_att);
+
+static void
+camel_imap_folder_class_init (CamelImapFolderClass *camel_imap_folder_class)
+{
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_imap_folder_class);
+ CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_imap_folder_class);
+
+ disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ()));
+
+ /* virtual method overload */
+ ((CamelObjectClass *)camel_imap_folder_class)->getv = imap_getv;
+
+ camel_folder_class->get_message = imap_get_message;
+ camel_folder_class->rename = imap_rename;
+ camel_folder_class->search_by_expression = imap_search_by_expression;
+ camel_folder_class->search_by_uids = imap_search_by_uids;
+ camel_folder_class->search_free = imap_search_free;
+ camel_folder_class->thaw = imap_thaw;
+
+ camel_disco_folder_class->refresh_info_online = imap_refresh_info;
+ camel_disco_folder_class->sync_online = imap_sync_online;
+ camel_disco_folder_class->sync_offline = imap_sync_offline;
+ /* We don't sync flags at resync time: the online code will
+ * deal with it eventually.
+ */
+ camel_disco_folder_class->sync_resyncing = imap_sync_offline;
+ camel_disco_folder_class->expunge_uids_online = imap_expunge_uids_online;
+ camel_disco_folder_class->expunge_uids_offline = imap_expunge_uids_offline;
+ camel_disco_folder_class->expunge_uids_resyncing = imap_expunge_uids_resyncing;
+ camel_disco_folder_class->append_online = imap_append_online;
+ camel_disco_folder_class->append_offline = imap_append_offline;
+ camel_disco_folder_class->append_resyncing = imap_append_resyncing;
+ camel_disco_folder_class->transfer_online = imap_transfer_online;
+ camel_disco_folder_class->transfer_offline = imap_transfer_offline;
+ camel_disco_folder_class->transfer_resyncing = imap_transfer_resyncing;
+ camel_disco_folder_class->cache_message = imap_cache_message;
+}
+
+static void
+camel_imap_folder_init (gpointer object, gpointer klass)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (object);
+ CamelFolder *folder = CAMEL_FOLDER (object);
+
+ folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED |
+ CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN;
+
+ folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY |
+ CAMEL_FOLDER_HAS_SEARCH_CAPABILITY);
+
+ imap_folder->priv = g_malloc0(sizeof(*imap_folder->priv));
+#ifdef ENABLE_THREADS
+ imap_folder->priv->search_lock = e_mutex_new(E_MUTEX_SIMPLE);
+ imap_folder->priv->cache_lock = e_mutex_new(E_MUTEX_REC);
+#endif
+
+ imap_folder->need_rescan = TRUE;
+}
+
+CamelType
+camel_imap_folder_get_type (void)
+{
+ static CamelType camel_imap_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_imap_folder_type == CAMEL_INVALID_TYPE) {
+ parent_class = camel_disco_folder_get_type();
+ camel_imap_folder_type =
+ camel_type_register (parent_class, "CamelImapFolder",
+ sizeof (CamelImapFolder),
+ sizeof (CamelImapFolderClass),
+ (CamelObjectClassInitFunc) camel_imap_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap_folder_init,
+ (CamelObjectFinalizeFunc) imap_finalize);
+ }
+
+ return camel_imap_folder_type;
+}
+
+CamelFolder *
+camel_imap_folder_new (CamelStore *parent, const char *folder_name,
+ const char *folder_dir, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (parent);
+ CamelFolder *folder;
+ CamelImapFolder *imap_folder;
+ const char *short_name;
+ char *summary_file, *state_file;
+
+ if (camel_mkdir (folder_dir, S_IRWXU) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not create directory %s: %s"),
+ folder_dir, g_strerror (errno));
+ return NULL;
+ }
+
+ folder = CAMEL_FOLDER (camel_object_new (camel_imap_folder_get_type ()));
+ short_name = strrchr (folder_name, '/');
+ if (short_name)
+ short_name++;
+ else
+ short_name = folder_name;
+ camel_folder_construct (folder, parent, folder_name, short_name);
+
+ summary_file = g_strdup_printf ("%s/summary", folder_dir);
+ folder->summary = camel_imap_summary_new (summary_file);
+ g_free (summary_file);
+ if (!folder->summary) {
+ camel_object_unref (CAMEL_OBJECT (folder));
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not load summary for %s"),
+ folder_name);
+ return NULL;
+ }
+
+ /* set/load persistent state */
+ state_file = g_strdup_printf ("%s/cmeta", folder_dir);
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state_file, NULL);
+ g_free(state_file);
+ camel_object_state_read(folder);
+
+ imap_folder = CAMEL_IMAP_FOLDER (folder);
+ imap_folder->cache = camel_imap_message_cache_new (folder_dir, folder->summary, ex);
+ if (!imap_folder->cache) {
+ camel_object_unref (CAMEL_OBJECT (folder));
+ return NULL;
+ }
+
+ if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
+ if ((imap_store->parameters & IMAP_PARAM_FILTER_INBOX))
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+ if ((imap_store->parameters & IMAP_PARAM_FILTER_JUNK))
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+ } else {
+ if ((imap_store->parameters & (IMAP_PARAM_FILTER_JUNK|IMAP_PARAM_FILTER_JUNK_INBOX)) == (IMAP_PARAM_FILTER_JUNK))
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+ }
+
+ imap_folder->search = camel_imap_search_new(folder_dir);
+
+ return folder;
+}
+
+/* Called with the store's connect_lock locked */
+void
+camel_imap_folder_selected (CamelFolder *folder, CamelImapResponse *response,
+ CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ CamelImapSummary *imap_summary = CAMEL_IMAP_SUMMARY (folder->summary);
+ unsigned long exists = 0, validity = 0, val, uid;
+ CamelMessageInfo *info;
+ guint32 perm_flags = 0;
+ GData *fetch_data;
+ int i, count;
+ char *resp;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (folder->parent_store, connect_lock);
+
+ count = camel_folder_summary_count (folder->summary);
+
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i] + 2;
+ if (!strncasecmp (resp, "FLAGS ", 6) && !perm_flags) {
+ resp += 6;
+ folder->permanent_flags = imap_parse_flag_list (&resp);
+ } else if (!strncasecmp (resp, "OK [PERMANENTFLAGS ", 19)) {
+ resp += 19;
+
+ /* workaround for broken IMAP servers that send "* OK [PERMANENTFLAGS ()] Permanent flags"
+ * even tho they do allow storing flags. *Sigh* So many fucking broken IMAP servers out there. */
+ if ((perm_flags = imap_parse_flag_list (&resp)) != 0)
+ folder->permanent_flags = perm_flags;
+ } else if (!strncasecmp (resp, "OK [UIDVALIDITY ", 16)) {
+ validity = strtoul (resp + 16, NULL, 10);
+ } else if (isdigit ((unsigned char)*resp)) {
+ unsigned long num = strtoul (resp, &resp, 10);
+
+ if (!strncasecmp (resp, " EXISTS", 7)) {
+ exists = num;
+ /* Remove from the response so nothing
+ * else tries to interpret it.
+ */
+ g_free (response->untagged->pdata[i]);
+ g_ptr_array_remove_index (response->untagged, i--);
+ }
+ }
+ }
+
+ if (camel_strstrcase (response->status, "OK [READ-ONLY]"))
+ imap_folder->read_only = TRUE;
+
+ if (camel_disco_store_status (CAMEL_DISCO_STORE (folder->parent_store)) == CAMEL_DISCO_STORE_RESYNCING) {
+ if (validity != imap_summary->validity) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_SUMMARY_INVALID,
+ _("Folder was destroyed and recreated on server."));
+ return;
+ }
+
+ /* FIXME: find missing UIDs ? */
+ return;
+ }
+
+ if (!imap_summary->validity)
+ imap_summary->validity = validity;
+ else if (validity != imap_summary->validity) {
+ imap_summary->validity = validity;
+ camel_folder_summary_clear (folder->summary);
+ CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock);
+ camel_imap_message_cache_clear (imap_folder->cache);
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ imap_folder->need_rescan = FALSE;
+ camel_imap_folder_changed (folder, exists, NULL, ex);
+ return;
+ }
+
+ /* If we've lost messages, we have to rescan everything */
+ if (exists < count)
+ imap_folder->need_rescan = TRUE;
+ else if (count != 0 && !imap_folder->need_rescan) {
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+
+ /* Similarly, if the UID of the highest message we
+ * know about has changed, then that indicates that
+ * messages have been both added and removed, so we
+ * have to rescan to find the removed ones. (We pass
+ * NULL for the folder since we know that this folder
+ * is selected, and we don't want camel_imap_command
+ * to worry about it.)
+ */
+ response = camel_imap_command (store, NULL, ex, "FETCH %d UID", count);
+ if (!response)
+ return;
+ uid = 0;
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i];
+ val = strtoul (resp + 2, &resp, 10);
+ if (val == 0)
+ continue;
+ if (!g_ascii_strcasecmp (resp, " EXISTS")) {
+ /* Another one?? */
+ exists = val;
+ continue;
+ }
+ if (uid != 0 || val != count || strncasecmp (resp, " FETCH (", 8) != 0)
+ continue;
+
+ fetch_data = parse_fetch_response (imap_folder, resp + 7);
+ uid = strtoul (g_datalist_get_data (&fetch_data, "UID"), NULL, 10);
+ g_datalist_clear (&fetch_data);
+ }
+ camel_imap_response_free_without_processing (store, response);
+
+ info = camel_folder_summary_index (folder->summary, count - 1);
+ val = strtoul (camel_message_info_uid (info), NULL, 10);
+ camel_folder_summary_info_free (folder->summary, info);
+ if (uid == 0 || uid != val)
+ imap_folder->need_rescan = TRUE;
+ }
+
+ /* Now rescan if we need to */
+ if (imap_folder->need_rescan) {
+ imap_rescan (folder, exists, ex);
+ return;
+ }
+
+ /* If we don't need to rescan completely, but new messages
+ * have been added, find out about them.
+ */
+ if (exists > count)
+ camel_imap_folder_changed (folder, exists, NULL, ex);
+
+ /* And we're done. */
+}
+
+static void
+imap_finalize (CamelObject *object)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (object);
+
+ if (imap_folder->search)
+ camel_object_unref (CAMEL_OBJECT (imap_folder->search));
+ if (imap_folder->cache)
+ camel_object_unref (CAMEL_OBJECT (imap_folder->cache));
+
+#ifdef ENABLE_THREADS
+ e_mutex_destroy(imap_folder->priv->search_lock);
+ e_mutex_destroy(imap_folder->priv->cache_lock);
+#endif
+ g_free(imap_folder->priv);
+}
+
+static int
+imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ CamelFolder *folder = (CamelFolder *)object;
+ int i, count=0;
+ guint32 tag;
+
+ for (i=0;i<args->argc;i++) {
+ CamelArgGet *arg = &args->argv[i];
+
+ tag = arg->tag;
+
+ switch (tag & CAMEL_ARG_TAG) {
+ /* CamelObject args */
+ case CAMEL_OBJECT_ARG_DESCRIPTION:
+ if (folder->description == NULL) {
+ CamelURL *uri = ((CamelService *)folder->parent_store)->url;
+
+ /* what if the full name doesn't incclude /'s? does it matter? */
+ folder->description = g_strdup_printf("%s@%s:%s", uri->user, uri->host, folder->full_name);
+ }
+ *arg->ca_str = folder->description;
+ break;
+ default:
+ count++;
+ continue;
+ }
+
+ arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+ }
+
+ if (count)
+ return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
+
+ return 0;
+}
+
+static void
+imap_rename (CamelFolder *folder, const char *new)
+{
+ CamelImapFolder *imap_folder = (CamelImapFolder *)folder;
+ CamelImapStore *imap_store = (CamelImapStore *)folder->parent_store;
+ char *folder_dir, *summary_path, *state_file;
+ char *folders;
+
+ folders = g_strconcat (imap_store->storage_path, "/folders", NULL);
+ folder_dir = e_path_to_physical (folders, new);
+ g_free (folders);
+ summary_path = g_strdup_printf("%s/summary", folder_dir);
+
+ CAMEL_IMAP_FOLDER_LOCK (folder, cache_lock);
+ camel_imap_message_cache_set_path(imap_folder->cache, folder_dir);
+ CAMEL_IMAP_FOLDER_UNLOCK (folder, cache_lock);
+
+ camel_folder_summary_set_filename(folder->summary, summary_path);
+
+ state_file = g_strdup_printf ("%s/cmeta", folder_dir);
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state_file, NULL);
+ g_free(state_file);
+
+ g_free(summary_path);
+ g_free(folder_dir);
+
+ ((CamelFolderClass *)disco_folder_class)->rename(folder, new);
+}
+
+static void
+imap_refresh_info (CamelFolder *folder, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ CamelImapResponse *response;
+
+ if (camel_disco_store_status (CAMEL_DISCO_STORE (imap_store)) == CAMEL_DISCO_STORE_OFFLINE)
+ return;
+
+ if (camel_folder_is_frozen (folder)) {
+ imap_folder->need_refresh = TRUE;
+ return;
+ }
+
+ /* If the folder isn't selected, select it (which will force
+ * a rescan if one is needed).
+ * Also, if this is the INBOX, some servers (cryus) wont tell
+ * us with a NOOP of new messages, so force a reselect which
+ * should do it. */
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+ if (imap_store->current_folder != folder
+ || g_ascii_strcasecmp(folder->full_name, "INBOX") == 0) {
+ response = camel_imap_command (imap_store, folder, ex, NULL);
+ if (response) {
+ camel_imap_folder_selected (folder, response, ex);
+ camel_imap_response_free (imap_store, response);
+ }
+ } else if (imap_folder->need_rescan) {
+ /* Otherwise, if we need a rescan, do it, and if not, just do
+ * a NOOP to give the server a chance to tell us about new
+ * messages.
+ */
+ imap_rescan (folder, camel_folder_summary_count (folder->summary), ex);
+ } else {
+#if 0
+ /* on some servers need to CHECKpoint INBOX to recieve new messages?? */
+ /* rfc2060 suggests this, but havent seen a server that requires it */
+ if (g_ascii_strcasecmp(folder->full_name, "INBOX") == 0) {
+ response = camel_imap_command (imap_store, folder, ex, "CHECK");
+ camel_imap_response_free (imap_store, response);
+ }
+#endif
+ response = camel_imap_command (imap_store, folder, ex, "NOOP");
+ camel_imap_response_free (imap_store, response);
+ }
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+}
+
+/* Called with the store's connect_lock locked */
+static void
+imap_rescan (CamelFolder *folder, int exists, CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ struct {
+ char *uid;
+ guint32 flags;
+ } *new;
+ char *resp;
+ CamelImapResponseType type;
+ int i, seq, summary_len, summary_got;
+ CamelMessageInfo *info;
+ CamelImapMessageInfo *iinfo;
+ GArray *removed;
+ gboolean ok;
+ CamelFolderChangeInfo *changes = NULL;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+ imap_folder->need_rescan = FALSE;
+
+ summary_len = camel_folder_summary_count (folder->summary);
+ if (summary_len == 0) {
+ if (exists)
+ camel_imap_folder_changed (folder, exists, NULL, ex);
+ return;
+ }
+
+ /* Check UIDs and flags of all messages we already know of. */
+ camel_operation_start (NULL, _("Scanning for changed messages"));
+ info = camel_folder_summary_index (folder->summary, summary_len - 1);
+ ok = camel_imap_command_start (store, folder, ex,
+ "UID FETCH 1:%s (FLAGS)",
+ camel_message_info_uid (info));
+ camel_folder_summary_info_free (folder->summary, info);
+ if (!ok) {
+ camel_operation_end (NULL);
+ return;
+ }
+
+ new = g_malloc0 (summary_len * sizeof (*new));
+ summary_got = 0;
+ while ((type = camel_imap_command_response (store, &resp, ex)) == CAMEL_IMAP_RESPONSE_UNTAGGED) {
+ GData *data;
+ char *uid;
+ guint32 flags;
+
+ data = parse_fetch_response (imap_folder, resp);
+ g_free (resp);
+ if (!data)
+ continue;
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ uid = g_datalist_get_data (&data, "UID");
+ flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS"));
+
+ if (!uid || !seq || seq > summary_len) {
+ g_datalist_clear (&data);
+ continue;
+ }
+
+ camel_operation_progress (NULL, ++summary_got * 100 / summary_len);
+ new[seq - 1].uid = g_strdup (uid);
+ new[seq - 1].flags = flags;
+ g_datalist_clear (&data);
+ }
+
+ camel_operation_end (NULL);
+ if (type == CAMEL_IMAP_RESPONSE_ERROR) {
+ for (i = 0; i < summary_len && new[i].uid; i++)
+ g_free (new[i].uid);
+ g_free (new);
+ return;
+ }
+
+ /* Free the final tagged response */
+ g_free (resp);
+
+ /* If we find a UID in the summary that doesn't correspond to
+ * the UID in the folder, then either: (a) it's a real UID,
+ * but the message was deleted on the server, or (b) it's a
+ * fake UID, and needs to be removed from the summary in order
+ * to sync up with the server. So either way, we remove it
+ * from the summary.
+ */
+ removed = g_array_new (FALSE, FALSE, sizeof (int));
+ for (i = 0; i < summary_len && new[i].uid; i++) {
+ info = camel_folder_summary_index (folder->summary, i);
+ iinfo = (CamelImapMessageInfo *)info;
+
+ if (strcmp (camel_message_info_uid (info), new[i].uid) != 0) {
+ camel_folder_summary_info_free(folder->summary, info);
+ seq = i + 1;
+ g_array_append_val (removed, seq);
+ i--;
+ summary_len--;
+ continue;
+ }
+
+ /* Update summary flags */
+ if (new[i].flags != iinfo->server_flags) {
+ guint32 server_set, server_cleared;
+
+ server_set = new[i].flags & ~iinfo->server_flags;
+ server_cleared = iinfo->server_flags & ~new[i].flags;
+
+ info->flags = (info->flags | server_set) & ~server_cleared;
+ iinfo->server_flags = new[i].flags;
+
+ if (changes == NULL)
+ changes = camel_folder_change_info_new();
+ camel_folder_change_info_change_uid(changes, new[i].uid);
+ }
+
+ camel_folder_summary_info_free (folder->summary, info);
+ g_free (new[i].uid);
+ }
+
+ if (changes) {
+ camel_object_trigger_event(CAMEL_OBJECT (folder), "folder_changed", changes);
+ camel_folder_change_info_free(changes);
+ }
+
+ seq = i + 1;
+
+ /* Free remaining memory. */
+ while (i < summary_len && new[i].uid)
+ g_free (new[i++].uid);
+ g_free (new);
+
+ /* Remove any leftover cached summary messages. (Yes, we
+ * repeatedly add the same number to the removed array.
+ * See RFC2060 7.4.1)
+ */
+
+ for (i = seq; i <= summary_len; i++)
+ g_array_append_val (removed, seq);
+
+ /* And finally update the summary. */
+ camel_imap_folder_changed (folder, exists, removed, ex);
+ g_array_free (removed, TRUE);
+}
+
+/* the max number of chars that an unsigned 32-bit int can be is 10 chars plus 1 for a possible : */
+#define UID_SET_FULL(setlen, maxlen) (maxlen > 0 ? setlen + 11 >= maxlen : FALSE)
+
+/* Find all messages in @folder with flags matching @flags and @mask.
+ * If no messages match, returns %NULL. Otherwise, returns an array of
+ * CamelMessageInfo and sets *@set to a message set corresponding the
+ * UIDs of the matched messages (up to @UID_SET_LIMIT bytes). The
+ * caller must free the infos, the array, and the set string.
+ */
+static GPtrArray *
+get_matching (CamelFolder *folder, guint32 flags, guint32 mask, char **set)
+{
+ GPtrArray *matches;
+ CamelMessageInfo *info;
+ int i, max, range;
+ GString *gset;
+
+ matches = g_ptr_array_new ();
+ gset = g_string_new ("");
+ max = camel_folder_summary_count (folder->summary);
+ range = -1;
+ for (i = 0; i < max && !UID_SET_FULL (gset->len, UID_SET_LIMIT); i++) {
+ info = camel_folder_summary_index (folder->summary, i);
+ if (!info)
+ continue;
+ if ((info->flags & mask) != flags) {
+ camel_folder_summary_info_free (folder->summary, info);
+ if (range != -1) {
+ if (range != i - 1) {
+ info = matches->pdata[matches->len - 1];
+ g_string_append_printf (gset, ":%s", camel_message_info_uid (info));
+ }
+ range = -1;
+ }
+ continue;
+ }
+
+ g_ptr_array_add (matches, info);
+ if (range != -1)
+ continue;
+ range = i;
+ if (gset->len)
+ g_string_append_c (gset, ',');
+ g_string_append_printf (gset, "%s", camel_message_info_uid (info));
+ }
+
+ if (range != -1 && range != max - 1) {
+ info = matches->pdata[matches->len - 1];
+ g_string_append_printf (gset, ":%s", camel_message_info_uid (info));
+ }
+
+ if (matches->len) {
+ *set = gset->str;
+ g_string_free (gset, FALSE);
+ return matches;
+ } else {
+ *set = NULL;
+ g_string_free (gset, TRUE);
+ g_ptr_array_free (matches, TRUE);
+ return NULL;
+ }
+}
+
+static void
+imap_sync_offline (CamelFolder *folder, CamelException *ex)
+{
+ camel_folder_summary_save (folder->summary);
+}
+
+static void
+imap_sync_online (CamelFolder *folder, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response = NULL;
+ CamelMessageInfo *info;
+ CamelException local_ex;
+ GPtrArray *matches;
+ char *set, *flaglist;
+ gboolean unset;
+ int i, j, max;
+
+ if (((CamelImapFolder *)folder)->read_only) {
+ imap_sync_offline (folder, ex);
+ return;
+ }
+
+ camel_exception_init (&local_ex);
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ /* Find a message with changed flags, find all of the other
+ * messages like it, sync them as a group, mark them as
+ * updated, and continue.
+ */
+ max = camel_folder_summary_count (folder->summary);
+ for (i = 0; i < max; i++) {
+ if (!(info = camel_folder_summary_index (folder->summary, i)))
+ continue;
+
+ if (!(info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
+ camel_folder_summary_info_free (folder->summary, info);
+ continue;
+ }
+
+ /* Note: Cyrus is broken and will not accept an
+ empty-set of flags so... if this is true then we
+ want to unset the previously set flags.*/
+ unset = !(info->flags & folder->permanent_flags);
+
+ /* Note: get_matching() uses UID_SET_LIMIT to limit
+ the size of the uid-set string. We don't have to
+ loop here to flush all the matching uids because
+ they will be scooped up later by our parent loop (I
+ think?). -- Jeff */
+ matches = get_matching (folder, info->flags & (folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED),
+ folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED, &set);
+ camel_folder_summary_info_free (folder->summary, info);
+ if (matches == NULL)
+ continue;
+
+ /* FIXME: since we don't know the previously set flags,
+ if unset is TRUE then just unset all the flags? */
+ flaglist = imap_create_flag_list (unset ? folder->permanent_flags : info->flags & folder->permanent_flags);
+
+ /* Note: to `unset' flags, use -FLAGS.SILENT (<flag list>) */
+ response = camel_imap_command (store, folder, &local_ex,
+ "UID STORE %s %sFLAGS.SILENT %s",
+ set, unset ? "-" : "", flaglist);
+ g_free (set);
+ g_free (flaglist);
+
+ if (response)
+ camel_imap_response_free (store, response);
+
+ if (!camel_exception_is_set (&local_ex)) {
+ for (j = 0; j < matches->len; j++) {
+ info = matches->pdata[j];
+ info->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
+ ((CamelImapMessageInfo *) info)->server_flags =
+ info->flags & CAMEL_IMAP_SERVER_FLAGS;
+ }
+ camel_folder_summary_touch (folder->summary);
+ }
+
+ for (j = 0; j < matches->len; j++) {
+ info = matches->pdata[j];
+ camel_folder_summary_info_free (folder->summary, info);
+ }
+ g_ptr_array_free (matches, TRUE);
+
+ /* We unlock here so that other threads can have a chance to grab the connect_lock */
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ /* check for an exception */
+ if (camel_exception_is_set (&local_ex)) {
+ camel_exception_xfer (ex, &local_ex);
+ return;
+ }
+
+ /* Re-lock the connect_lock */
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ }
+
+ /* Save the summary */
+ imap_sync_offline (folder, ex);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static int
+uid_compar (const void *va, const void *vb)
+{
+ const char **sa = (const char **)va, **sb = (const char **)vb;
+ unsigned long a, b;
+
+ a = strtoul (*sa, NULL, 10);
+ b = strtoul (*sb, NULL, 10);
+ if (a < b)
+ return -1;
+ else if (a == b)
+ return 0;
+ else
+ return 1;
+}
+
+static void
+imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
+{
+ CamelFolderChangeInfo *changes;
+ int i;
+
+ qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
+
+ changes = camel_folder_change_info_new ();
+
+ for (i = 0; i < uids->len; i++) {
+ camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]);
+ camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
+ /* We intentionally don't remove it from the cache because
+ * the cached data may be useful in replaying a COPY later.
+ */
+ }
+ camel_folder_summary_save (folder->summary);
+
+ camel_disco_diary_log (CAMEL_DISCO_STORE (folder->parent_store)->diary,
+ CAMEL_DISCO_DIARY_FOLDER_EXPUNGE, folder, uids);
+
+ camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", changes);
+ camel_folder_change_info_free (changes);
+}
+
+static void
+imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response;
+ int uid = 0;
+ char *set;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0) {
+ ((CamelFolderClass *)CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, 0, ex);
+ if (camel_exception_is_set(ex)) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+ }
+
+ qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
+
+ while (uid < uids->len) {
+ set = imap_uid_array_to_set (folder->summary, uids, uid, UID_SET_LIMIT, &uid);
+ response = camel_imap_command (store, folder, ex,
+ "UID STORE %s +FLAGS.SILENT (\\Deleted)",
+ set);
+ if (response)
+ camel_imap_response_free (store, response);
+ if (camel_exception_is_set (ex)) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ g_free (set);
+ return;
+ }
+
+ if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) {
+ response = camel_imap_command (store, folder, ex,
+ "UID EXPUNGE %s", set);
+ } else
+ response = camel_imap_command (store, folder, ex, "EXPUNGE");
+
+ if (response)
+ camel_imap_response_free (store, response);
+ }
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static void
+imap_expunge_uids_resyncing (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ GPtrArray *keep_uids, *mark_uids;
+ CamelImapResponse *response;
+ char *result;
+
+ if (imap_folder->read_only)
+ return;
+
+ if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) {
+ imap_expunge_uids_online (folder, uids, ex);
+ return;
+ }
+
+ /* If we don't have UID EXPUNGE we need to avoid expunging any
+ * of the wrong messages. So we search for deleted messages,
+ * and any that aren't in our to-expunge list get temporarily
+ * marked un-deleted.
+ */
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ ((CamelFolderClass *)CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, 0, ex);
+ if (camel_exception_is_set(ex)) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+
+ response = camel_imap_command (store, folder, ex, "UID SEARCH DELETED");
+ if (!response) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+ result = camel_imap_response_extract (store, response, "SEARCH", ex);
+ if (!result) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+
+ if (result[8] == ' ') {
+ char *uid, *lasts = NULL;
+ unsigned long euid, kuid;
+ int ei, ki;
+
+ keep_uids = g_ptr_array_new ();
+ mark_uids = g_ptr_array_new ();
+
+ /* Parse SEARCH response */
+ for (uid = strtok_r (result + 9, " ", &lasts); uid; uid = strtok_r (NULL, " ", &lasts))
+ g_ptr_array_add (keep_uids, uid);
+ qsort (keep_uids->pdata, keep_uids->len,
+ sizeof (void *), uid_compar);
+
+ /* Fill in "mark_uids", empty out "keep_uids" as needed */
+ for (ei = ki = 0; ei < uids->len; ei++) {
+ euid = strtoul (uids->pdata[ei], NULL, 10);
+
+ for (kuid = 0; ki < keep_uids->len; ki++) {
+ kuid = strtoul (keep_uids->pdata[ki], NULL, 10);
+
+ if (kuid >= euid)
+ break;
+ }
+
+ if (euid == kuid)
+ g_ptr_array_remove_index (keep_uids, ki);
+ else
+ g_ptr_array_add (mark_uids, uids->pdata[ei]);
+ }
+ } else {
+ /* Empty SEARCH result, meaning nothing is marked deleted
+ * on server.
+ */
+
+ keep_uids = NULL;
+ mark_uids = uids;
+ }
+
+ /* Unmark messages to be kept */
+
+ if (keep_uids) {
+ char *uidset;
+ int uid = 0;
+
+ while (uid < keep_uids->len) {
+ uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
+
+ response = camel_imap_command (store, folder, ex,
+ "UID STORE %s -FLAGS.SILENT (\\Deleted)",
+ uidset);
+
+ g_free (uidset);
+
+ if (!response) {
+ g_ptr_array_free (keep_uids, TRUE);
+ g_ptr_array_free (mark_uids, TRUE);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+ camel_imap_response_free (store, response);
+ }
+ }
+
+ /* Mark any messages that still need to be marked */
+ if (mark_uids) {
+ char *uidset;
+ int uid = 0;
+
+ while (uid < mark_uids->len) {
+ uidset = imap_uid_array_to_set (folder->summary, mark_uids, uid, UID_SET_LIMIT, &uid);
+
+ response = camel_imap_command (store, folder, ex,
+ "UID STORE %s +FLAGS.SILENT (\\Deleted)",
+ uidset);
+
+ g_free (uidset);
+
+ if (!response) {
+ g_ptr_array_free (keep_uids, TRUE);
+ g_ptr_array_free (mark_uids, TRUE);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+ camel_imap_response_free (store, response);
+ }
+
+ if (mark_uids != uids)
+ g_ptr_array_free (mark_uids, TRUE);
+ }
+
+ /* Do the actual expunging */
+ response = camel_imap_command (store, folder, ex, "EXPUNGE");
+ if (response)
+ camel_imap_response_free (store, response);
+
+ /* And fix the remaining messages if we mangled them */
+ if (keep_uids) {
+ char *uidset;
+ int uid = 0;
+
+ while (uid < keep_uids->len) {
+ uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
+
+ /* Don't pass ex if it's already been set */
+ response = camel_imap_command (store, folder,
+ camel_exception_is_set (ex) ? NULL : ex,
+ "UID STORE %s +FLAGS.SILENT (\\Deleted)",
+ uidset);
+
+ g_free (uidset);
+ if (response)
+ camel_imap_response_free (store, response);
+ }
+
+ g_ptr_array_free (keep_uids, TRUE);
+ }
+
+ /* now we can free this, now that we're done with keep_uids */
+ g_free (result);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static gchar *
+get_temp_uid (void)
+{
+ gchar *res;
+
+ static int counter = 0;
+ G_LOCK_DEFINE_STATIC (lock);
+
+ G_LOCK (lock);
+ res = g_strdup_printf ("tempuid-%lx-%d",
+ (unsigned long) time (NULL),
+ counter++);
+ G_UNLOCK (lock);
+
+ return res;
+}
+
+static void
+imap_append_offline (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapMessageCache *cache = CAMEL_IMAP_FOLDER (folder)->cache;
+ CamelFolderChangeInfo *changes;
+ char *uid;
+
+ uid = get_temp_uid ();
+
+ camel_imap_summary_add_offline (folder->summary, uid, message, info);
+ CAMEL_IMAP_FOLDER_LOCK (folder, cache_lock);
+ camel_imap_message_cache_insert_wrapper (cache, uid, "",
+ CAMEL_DATA_WRAPPER (message), ex);
+ CAMEL_IMAP_FOLDER_UNLOCK (folder, cache_lock);
+
+ changes = camel_folder_change_info_new ();
+ camel_folder_change_info_add_uid (changes, uid);
+ camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed",
+ changes);
+ camel_folder_change_info_free (changes);
+
+ camel_disco_diary_log (CAMEL_DISCO_STORE (imap_store)->diary,
+ CAMEL_DISCO_DIARY_FOLDER_APPEND, folder, uid);
+ if (appended_uid)
+ *appended_uid = uid;
+ else
+ g_free (uid);
+}
+
+static CamelImapResponse *
+do_append (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **uid,
+ CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response, *response2;
+ CamelStream *memstream;
+ CamelMimeFilter *crlf_filter;
+ CamelStreamFilter *streamfilter;
+ GByteArray *ba;
+ char *flagstr, *end;
+
+ /* create flag string param */
+ if (info && info->flags)
+ flagstr = imap_create_flag_list (info->flags);
+ else
+ flagstr = NULL;
+
+ /* encode any 8bit parts so we avoid sending embedded nul-chars and such */
+ camel_mime_message_encode_8bit_parts (message);
+
+ /* FIXME: We could avoid this if we knew how big the message was. */
+ memstream = camel_stream_mem_new ();
+ ba = g_byte_array_new ();
+ camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (memstream), ba);
+
+ streamfilter = camel_stream_filter_new_with_stream (memstream);
+ crlf_filter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE,
+ CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
+ camel_stream_filter_add (streamfilter, crlf_filter);
+ camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message),
+ CAMEL_STREAM (streamfilter));
+ camel_object_unref (CAMEL_OBJECT (streamfilter));
+ camel_object_unref (CAMEL_OBJECT (crlf_filter));
+ camel_object_unref (CAMEL_OBJECT (memstream));
+
+ response = camel_imap_command (store, NULL, ex, "APPEND %F%s%s {%d}",
+ folder->full_name, flagstr ? " " : "",
+ flagstr ? flagstr : "", ba->len);
+ g_free (flagstr);
+
+ if (!response) {
+ g_byte_array_free (ba, TRUE);
+ return NULL;
+ }
+
+ if (*response->status != '+') {
+ camel_imap_response_free (store, response);
+ g_byte_array_free (ba, TRUE);
+ return NULL;
+ }
+
+ /* send the rest of our data - the mime message */
+ response2 = camel_imap_command_continuation (store, ba->data, ba->len, ex);
+ g_byte_array_free (ba, TRUE);
+
+ /* free it only after message is sent. This may cause more FETCHes. */
+ camel_imap_response_free (store, response);
+ if (!response2)
+ return response2;
+
+ if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) {
+ *uid = camel_strstrcase (response2->status, "[APPENDUID ");
+ if (*uid)
+ *uid = strchr (*uid + 11, ' ');
+ if (*uid) {
+ *uid = g_strndup (*uid + 1, strcspn (*uid + 1, "]"));
+ /* Make sure it's a number */
+ if (strtoul (*uid, &end, 10) == 0 || *end) {
+ g_free (*uid);
+ *uid = NULL;
+ }
+ }
+ } else
+ *uid = NULL;
+
+ return response2;
+}
+
+static void
+imap_append_online (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response;
+ char *uid;
+ int count;
+
+ count = camel_folder_summary_count (folder->summary);
+ response = do_append (folder, message, info, &uid, ex);
+ if (!response)
+ return;
+
+ if (uid) {
+ /* Cache first, since freeing response may trigger a
+ * summary update that will want this information.
+ */
+ CAMEL_IMAP_FOLDER_LOCK (folder, cache_lock);
+ camel_imap_message_cache_insert_wrapper (
+ CAMEL_IMAP_FOLDER (folder)->cache, uid,
+ "", CAMEL_DATA_WRAPPER (message), ex);
+ CAMEL_IMAP_FOLDER_UNLOCK (folder, cache_lock);
+ if (appended_uid)
+ *appended_uid = uid;
+ else
+ g_free (uid);
+ } else if (appended_uid)
+ *appended_uid = NULL;
+
+ camel_imap_response_free (store, response);
+
+ /* Make sure a "folder_changed" is emitted. */
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ if (store->current_folder != folder ||
+ camel_folder_summary_count (folder->summary) == count)
+ imap_refresh_info (folder, ex);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static void
+imap_append_resyncing (CamelFolder *folder, CamelMimeMessage *message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response;
+ char *uid;
+
+ response = do_append (folder, message, info, &uid, ex);
+ if (!response)
+ return;
+
+ if (uid) {
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ const char *olduid = camel_message_info_uid (info);
+
+ CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock);
+ camel_imap_message_cache_copy (imap_folder->cache, olduid,
+ imap_folder->cache, uid, ex);
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+
+ if (appended_uid)
+ *appended_uid = uid;
+ else
+ g_free (uid);
+ } else if (appended_uid)
+ *appended_uid = NULL;
+
+ camel_imap_response_free (store, response);
+}
+
+
+static void
+imap_transfer_offline (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store);
+ CamelImapMessageCache *sc = CAMEL_IMAP_FOLDER (source)->cache;
+ CamelImapMessageCache *dc = CAMEL_IMAP_FOLDER (dest)->cache;
+ CamelFolderChangeInfo *changes;
+ CamelMimeMessage *message;
+ CamelMessageInfo *mi;
+ char *uid, *destuid;
+ int i;
+
+ /* We grab the store's command lock first, and then grab the
+ * source and destination cache_locks. This way we can't
+ * deadlock in the case where we're simultaneously also trying
+ * to copy messages in the other direction from another thread.
+ */
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ CAMEL_IMAP_FOLDER_LOCK (source, cache_lock);
+ CAMEL_IMAP_FOLDER_LOCK (dest, cache_lock);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ if (transferred_uids) {
+ *transferred_uids = g_ptr_array_new ();
+ g_ptr_array_set_size (*transferred_uids, uids->len);
+ }
+
+ changes = camel_folder_change_info_new ();
+
+ for (i = 0; i < uids->len; i++) {
+ uid = uids->pdata[i];
+
+ destuid = get_temp_uid ();
+
+ mi = camel_folder_summary_uid (source->summary, uid);
+ g_return_if_fail (mi != NULL);
+
+ message = camel_folder_get_message (source, uid, NULL);
+
+ if (message) {
+ camel_imap_summary_add_offline (dest->summary, destuid, message, mi);
+ camel_object_unref (CAMEL_OBJECT (message));
+ } else
+ camel_imap_summary_add_offline_uncached (dest->summary, destuid, mi);
+
+ camel_imap_message_cache_copy (sc, uid, dc, destuid, ex);
+ camel_folder_summary_info_free (source->summary, mi);
+
+ camel_folder_change_info_add_uid (changes, destuid);
+ if (transferred_uids)
+ (*transferred_uids)->pdata[i] = destuid;
+ else
+ g_free (destuid);
+
+ if (delete_originals)
+ camel_folder_delete_message (source, uid);
+ }
+
+ CAMEL_IMAP_FOLDER_UNLOCK (dest, cache_lock);
+ CAMEL_IMAP_FOLDER_UNLOCK (source, cache_lock);
+
+ camel_object_trigger_event (CAMEL_OBJECT (dest), "folder_changed", changes);
+ camel_folder_change_info_free (changes);
+
+ camel_disco_diary_log (CAMEL_DISCO_STORE (store)->diary,
+ CAMEL_DISCO_DIARY_FOLDER_TRANSFER,
+ source, dest, uids, delete_originals);
+}
+
+static void
+handle_copyuid (CamelImapResponse *response, CamelFolder *source,
+ CamelFolder *destination)
+{
+ CamelImapMessageCache *scache = CAMEL_IMAP_FOLDER (source)->cache;
+ CamelImapMessageCache *dcache = CAMEL_IMAP_FOLDER (destination)->cache;
+ char *validity, *srcset, *destset;
+ GPtrArray *src, *dest;
+ int i;
+
+ validity = camel_strstrcase (response->status, "[COPYUID ");
+ if (!validity)
+ return;
+ validity += 9;
+ if (strtoul (validity, NULL, 10) !=
+ CAMEL_IMAP_SUMMARY (destination->summary)->validity)
+ return;
+
+ srcset = strchr (validity, ' ');
+ if (!srcset++)
+ goto lose;
+ destset = strchr (srcset, ' ');
+ if (!destset++)
+ goto lose;
+
+ src = imap_uid_set_to_array (source->summary, srcset);
+ dest = imap_uid_set_to_array (destination->summary, destset);
+
+ if (src && dest && src->len == dest->len) {
+ /* We don't have to worry about deadlocking on the
+ * cache locks here, because we've got the store's
+ * command lock too, so no one else could be here.
+ */
+ CAMEL_IMAP_FOLDER_LOCK (source, cache_lock);
+ CAMEL_IMAP_FOLDER_LOCK (destination, cache_lock);
+ for (i = 0; i < src->len; i++) {
+ camel_imap_message_cache_copy (scache, src->pdata[i],
+ dcache, dest->pdata[i],
+ NULL);
+ }
+ CAMEL_IMAP_FOLDER_UNLOCK (source, cache_lock);
+ CAMEL_IMAP_FOLDER_UNLOCK (destination, cache_lock);
+
+ imap_uid_array_free (src);
+ imap_uid_array_free (dest);
+ return;
+ }
+
+ imap_uid_array_free (src);
+ imap_uid_array_free (dest);
+ lose:
+ g_warning ("Bad COPYUID response from server");
+}
+
+static void
+do_copy (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *destination, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store);
+ CamelImapResponse *response;
+ char *uidset;
+ int uid = 0;
+
+ while (uid < uids->len && !camel_exception_is_set (ex)) {
+ uidset = imap_uid_array_to_set (source->summary, uids, uid, UID_SET_LIMIT, &uid);
+
+ response = camel_imap_command (store, source, ex, "UID COPY %s %F",
+ uidset, destination->full_name);
+
+ g_free (uidset);
+
+ if (response && (store->capabilities & IMAP_CAPABILITY_UIDPLUS))
+ handle_copyuid (response, source, destination);
+
+ camel_imap_response_free (store, response);
+ }
+}
+
+static void
+imap_transfer_online (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store);
+ int count, i;
+
+ /* Sync message flags if needed. */
+ imap_sync_online (source, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ count = camel_folder_summary_count (dest->summary);
+
+ qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
+
+ /* Now copy the messages */
+ do_copy (source, uids, dest, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ /* Make the destination notice its new messages */
+ if (store->current_folder != dest ||
+ camel_folder_summary_count (dest->summary) == count)
+ camel_folder_refresh_info (dest, ex);
+
+ if (delete_originals) {
+ for (i = 0; i < uids->len; i++)
+ camel_folder_delete_message (source, uids->pdata[i]);
+ }
+
+ /* FIXME */
+ if (transferred_uids)
+ *transferred_uids = NULL;
+}
+
+static void
+imap_transfer_resyncing (CamelFolder *source, GPtrArray *uids,
+ CamelFolder *dest, GPtrArray **transferred_uids,
+ gboolean delete_originals, CamelException *ex)
+{
+ CamelDiscoDiary *diary = CAMEL_DISCO_STORE (source->parent_store)->diary;
+ GPtrArray *realuids;
+ int first, i;
+ const char *uid;
+ CamelMimeMessage *message;
+ CamelMessageInfo *info;
+
+ qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
+
+ /* This is trickier than append_resyncing, because some of
+ * the messages we are copying may have been copied or
+ * appended into @source while we were offline, in which case
+ * if we don't have UIDPLUS, we won't know their real UIDs,
+ * so we'll have to append them rather than copying.
+ */
+
+ realuids = g_ptr_array_new ();
+
+ i = 0;
+ while (i < uids->len) {
+ /* Skip past real UIDs */
+ for (first = i; i < uids->len; i++) {
+ uid = uids->pdata[i];
+
+ if (!isdigit ((unsigned char)*uid)) {
+ uid = camel_disco_diary_uidmap_lookup (diary, uid);
+ if (!uid)
+ break;
+ }
+ g_ptr_array_add (realuids, (char *)uid);
+
+ if (delete_originals)
+ camel_folder_delete_message (source, uid);
+ }
+
+ /* If we saw any real UIDs, do a COPY */
+ if (i != first) {
+ do_copy (source, realuids, dest, ex);
+ g_ptr_array_set_size (realuids, 0);
+ if (i == uids->len || camel_exception_is_set (ex))
+ break;
+ }
+
+ /* Deal with fake UIDs */
+ while (i < uids->len &&
+ !isdigit (*(unsigned char *)(uids->pdata[i])) &&
+ !camel_exception_is_set (ex)) {
+ uid = uids->pdata[i];
+ message = camel_folder_get_message (source, uid, NULL);
+ if (!message) {
+ /* Message must have been expunged */
+ continue;
+ }
+ info = camel_folder_get_message_info (source, uid);
+ g_return_if_fail (info != NULL);
+
+ imap_append_online (dest, message, info, NULL, ex);
+ camel_folder_free_message_info (source, info);
+ camel_object_unref (CAMEL_OBJECT (message));
+ if (delete_originals)
+ camel_folder_delete_message (source, uid);
+ i++;
+ }
+ }
+
+ g_ptr_array_free (realuids, FALSE);
+
+ /* FIXME */
+ if (transferred_uids)
+ *transferred_uids = NULL;
+}
+
+static GPtrArray *
+imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ GPtrArray *matches;
+
+ /* we could get around this by creating a new search object each time,
+ but i doubt its worth it since any long operation would lock the
+ command channel too */
+ CAMEL_IMAP_FOLDER_LOCK(folder, search_lock);
+
+ camel_folder_search_set_folder (imap_folder->search, folder);
+ matches = camel_folder_search_search(imap_folder->search, expression, NULL, ex);
+
+ CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock);
+
+ return matches;
+}
+
+static GPtrArray *
+imap_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER(folder);
+ GPtrArray *matches;
+
+ if (uids->len == 0)
+ return g_ptr_array_new();
+
+ CAMEL_IMAP_FOLDER_LOCK(folder, search_lock);
+
+ camel_folder_search_set_folder(imap_folder->search, folder);
+ matches = camel_folder_search_search(imap_folder->search, expression, uids, ex);
+
+ CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock);
+
+ return matches;
+}
+
+static void
+imap_search_free (CamelFolder *folder, GPtrArray *uids)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+
+ g_return_if_fail (imap_folder->search);
+
+ CAMEL_IMAP_FOLDER_LOCK(folder, search_lock);
+
+ camel_folder_search_free_result (imap_folder->search, uids);
+
+ CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock);
+}
+
+static CamelMimeMessage *get_message (CamelImapFolder *imap_folder,
+ const char *uid,
+ CamelMessageContentInfo *ci,
+ CamelException *ex);
+
+struct _part_spec_stack {
+ struct _part_spec_stack *parent;
+ int part;
+};
+
+static void
+part_spec_push (struct _part_spec_stack **stack, int part)
+{
+ struct _part_spec_stack *node;
+
+ node = g_new (struct _part_spec_stack, 1);
+ node->parent = *stack;
+ node->part = part;
+
+ *stack = node;
+}
+
+static int
+part_spec_pop (struct _part_spec_stack **stack)
+{
+ struct _part_spec_stack *node;
+ int part;
+
+ g_return_val_if_fail (*stack != NULL, 0);
+
+ node = *stack;
+ *stack = node->parent;
+
+ part = node->part;
+ g_free (node);
+
+ return part;
+}
+
+static char *
+content_info_get_part_spec (CamelMessageContentInfo *ci)
+{
+ struct _part_spec_stack *stack = NULL;
+ CamelMessageContentInfo *node;
+ char *part_spec, *buf;
+ size_t len = 1;
+ int part;
+
+ node = ci;
+ while (node->parent) {
+ CamelMessageContentInfo *child;
+
+ /* FIXME: is this only supposed to apply if 'node' is a multipart? */
+ if (node->parent->parent && camel_content_type_is (node->parent->type, "message", "*")) {
+ node = node->parent;
+ continue;
+ }
+
+ child = node->parent->childs;
+ for (part = 1; child; part++) {
+ if (child == node)
+ break;
+
+ child = child->next;
+ }
+
+ part_spec_push (&stack, part);
+
+ len += 2;
+ while ((part = part / 10))
+ len++;
+
+ node = node->parent;
+ }
+
+ buf = part_spec = g_malloc (len);
+ part_spec[0] = '\0';
+
+ while (stack) {
+ part = part_spec_pop (&stack);
+ buf += sprintf (buf, "%d%s", part, stack ? "." : "");
+ }
+
+ return part_spec;
+}
+
+/* Fetch the contents of the MIME part indicated by @ci, which is part
+ * of message @uid in @folder.
+ */
+static CamelDataWrapper *
+get_content (CamelImapFolder *imap_folder, const char *uid,
+ CamelMimePart *part, CamelMessageContentInfo *ci,
+ int frommsg,
+ CamelException *ex)
+{
+ CamelDataWrapper *content = NULL;
+ CamelStream *stream;
+ char *part_spec;
+
+ part_spec = content_info_get_part_spec (ci);
+
+ d(printf("get content '%s' '%s' (frommsg = %d)\n", part_spec, camel_content_type_format(ci->type), frommsg));
+
+ /* There are three cases: multipart/signed, multipart, message/rfc822, and "other" */
+ if (camel_content_type_is (ci->type, "multipart", "signed")) {
+ CamelMultipartSigned *body_mp;
+ char *spec;
+ int ret;
+
+ /* Note: because we get the content parts uninterpreted anyway, we could potentially
+ just use the normalmultipart code, except that multipart/signed wont let you yet! */
+
+ body_mp = camel_multipart_signed_new ();
+ /* need to set this so it grabs the boundary and other info about the signed type */
+ /* we assume that part->content_type is more accurate/full than ci->type */
+ camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
+
+ spec = g_alloca(strlen(part_spec) + 6);
+ if (frommsg)
+ sprintf(spec, part_spec[0] ? "%s.TEXT" : "TEXT", part_spec);
+ else
+ strcpy(spec, part_spec);
+ g_free(part_spec);
+
+ stream = camel_imap_folder_fetch_data (imap_folder, uid, spec, FALSE, ex);
+ if (stream) {
+ ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (body_mp), stream);
+ camel_object_unref (CAMEL_OBJECT (stream));
+ if (ret == -1) {
+ camel_object_unref ((CamelObject *) body_mp);
+ return NULL;
+ }
+ }
+
+ return (CamelDataWrapper *) body_mp;
+ } else if (camel_content_type_is (ci->type, "multipart", "*")) {
+ CamelMultipart *body_mp;
+ char *child_spec;
+ int speclen, num, isdigest;
+
+ if (camel_content_type_is (ci->type, "multipart", "encrypted"))
+ body_mp = (CamelMultipart *) camel_multipart_encrypted_new ();
+ else
+ body_mp = camel_multipart_new ();
+
+ /* need to set this so it grabs the boundary and other info about the multipart */
+ /* we assume that part->content_type is more accurate/full than ci->type */
+ camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
+ isdigest = camel_content_type_is(((CamelDataWrapper *)part)->mime_type, "multipart", "digest");
+
+ speclen = strlen (part_spec);
+ child_spec = g_malloc (speclen + 17); /* dot + 10 + dot + MIME + nul */
+ memcpy (child_spec, part_spec, speclen);
+ if (speclen > 0)
+ child_spec[speclen++] = '.';
+ g_free (part_spec);
+
+ ci = ci->childs;
+ num = 1;
+ while (ci) {
+ sprintf (child_spec + speclen, "%d.MIME", num++);
+ stream = camel_imap_folder_fetch_data (imap_folder, uid, child_spec, FALSE, ex);
+ if (stream) {
+ int ret;
+
+ part = camel_mime_part_new ();
+ ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (part), stream);
+ camel_object_unref (CAMEL_OBJECT (stream));
+ if (ret == -1) {
+ camel_object_unref (CAMEL_OBJECT (part));
+ camel_object_unref (CAMEL_OBJECT (body_mp));
+ g_free (child_spec);
+ return NULL;
+ }
+
+ content = get_content (imap_folder, uid, part, ci, FALSE, ex);
+ }
+
+ if (!stream || !content) {
+ camel_object_unref (CAMEL_OBJECT (body_mp));
+ g_free (child_spec);
+ return NULL;
+ }
+
+ if (camel_debug("imap:folder")) {
+ char *ct = camel_content_type_format(camel_mime_part_get_content_type((CamelMimePart *)part));
+ char *ct2 = camel_content_type_format(ci->type);
+
+ printf("Setting part content type to '%s' contentinfo type is '%s'\n", ct, ct2);
+ g_free(ct);
+ g_free(ct2);
+ }
+
+ /* if we had no content-type header on a multipart/digest sub-part, then we need to
+ treat it as message/rfc822 instead */
+ if (isdigest && camel_medium_get_header((CamelMedium *)part, "content-type") == NULL) {
+ CamelContentType *ct = camel_content_type_new("message", "rfc822");
+
+ camel_data_wrapper_set_mime_type_field(content, ct);
+ camel_content_type_unref(ct);
+ } else {
+ camel_data_wrapper_set_mime_type_field(content, camel_mime_part_get_content_type(part));
+ }
+
+ camel_medium_set_content_object (CAMEL_MEDIUM (part), content);
+ camel_object_unref(content);
+
+ camel_multipart_add_part (body_mp, part);
+ camel_object_unref(part);
+
+ ci = ci->next;
+ }
+
+ g_free (child_spec);
+
+ return (CamelDataWrapper *) body_mp;
+ } else if (camel_content_type_is (ci->type, "message", "rfc822")) {
+ content = (CamelDataWrapper *) get_message (imap_folder, uid, ci->childs, ex);
+ g_free (part_spec);
+ return content;
+ } else {
+ CamelTransferEncoding enc;
+ char *spec;
+
+ /* NB: we need this differently to multipart/signed case above on purpose */
+ spec = g_alloca(strlen(part_spec) + 6);
+ if (frommsg)
+ sprintf(spec, part_spec[0] ? "%s.1" : "1", part_spec);
+ else
+ strcpy(spec, part_spec[0]?part_spec:"1");
+
+ enc = ci->encoding?camel_transfer_encoding_from_string(ci->encoding):CAMEL_TRANSFER_ENCODING_DEFAULT;
+ content = camel_imap_wrapper_new (imap_folder, ci->type, enc, uid, spec, part);
+ g_free (part_spec);
+ return content;
+ }
+}
+
+static CamelMimeMessage *
+get_message (CamelImapFolder *imap_folder, const char *uid,
+ CamelMessageContentInfo *ci,
+ CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (CAMEL_FOLDER (imap_folder)->parent_store);
+ CamelDataWrapper *content;
+ CamelMimeMessage *msg;
+ CamelStream *stream;
+ char *section_text, *part_spec;
+ int ret;
+
+ part_spec = content_info_get_part_spec(ci);
+ d(printf("get message '%s'\n", part_spec));
+ section_text = g_strdup_printf ("%s%s%s", part_spec, *part_spec ? "." : "",
+ store->server_level >= IMAP_LEVEL_IMAP4REV1 ? "HEADER" : "0");
+
+ stream = camel_imap_folder_fetch_data (imap_folder, uid, section_text, FALSE, ex);
+ g_free (section_text);
+ g_free(part_spec);
+ if (!stream)
+ return NULL;
+
+ msg = camel_mime_message_new ();
+ ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream);
+ camel_object_unref (CAMEL_OBJECT (stream));
+ if (ret == -1) {
+ camel_object_unref (CAMEL_OBJECT (msg));
+ return NULL;
+ }
+
+ content = get_content (imap_folder, uid, CAMEL_MIME_PART (msg), ci, TRUE, ex);
+ if (!content) {
+ camel_object_unref (CAMEL_OBJECT (msg));
+ return NULL;
+ }
+
+ if (camel_debug("imap:folder")) {
+ char *ct = camel_content_type_format(camel_mime_part_get_content_type((CamelMimePart *)msg));
+ char *ct2 = camel_content_type_format(ci->type);
+
+ printf("Setting message content type to '%s' contentinfo type is '%s'\n", ct, ct2);
+ g_free(ct);
+ g_free(ct2);
+ }
+
+ camel_data_wrapper_set_mime_type_field(content, camel_mime_part_get_content_type((CamelMimePart *)msg));
+ camel_medium_set_content_object (CAMEL_MEDIUM (msg), content);
+ camel_object_unref (CAMEL_OBJECT (content));
+
+ return msg;
+}
+
+#define IMAP_SMALL_BODY_SIZE 5120
+
+static CamelMimeMessage *
+get_message_simple (CamelImapFolder *imap_folder, const char *uid,
+ CamelStream *stream, CamelException *ex)
+{
+ CamelMimeMessage *msg;
+ int ret;
+
+ if (!stream) {
+ stream = camel_imap_folder_fetch_data (imap_folder, uid, "",
+ FALSE, ex);
+ if (!stream)
+ return NULL;
+ }
+
+ msg = camel_mime_message_new ();
+ ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg),
+ stream);
+ camel_object_unref (CAMEL_OBJECT (stream));
+ if (ret == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Unable to retrieve message: %s"),
+ g_strerror (errno));
+ camel_object_unref (CAMEL_OBJECT (msg));
+ return NULL;
+ }
+
+ return msg;
+}
+
+static gboolean
+content_info_incomplete (CamelMessageContentInfo *ci)
+{
+ if (!ci->type)
+ return TRUE;
+
+ if (camel_content_type_is (ci->type, "multipart", "*")
+ || camel_content_type_is (ci->type, "message", "rfc822")) {
+ if (!ci->childs)
+ return TRUE;
+ for (ci = ci->childs;ci;ci=ci->next)
+ if (content_info_incomplete(ci))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static CamelMimeMessage *
+imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelMessageInfo *mi;
+ CamelMimeMessage *msg = NULL;
+ CamelStream *stream = NULL;
+ int retry;
+
+ mi = camel_folder_summary_uid (folder->summary, uid);
+ if (mi == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s\n %s"), uid, _("No such message"));
+ return NULL;
+ }
+
+ /* If its cached in full, just get it as is, this is only a shortcut,
+ since we get stuff from the cache anyway. It affects a busted connection though. */
+ if ( (stream = camel_imap_folder_fetch_data(imap_folder, uid, "", TRUE, NULL))
+ && (msg = get_message_simple(imap_folder, uid, stream, ex)))
+ goto done;
+
+ /* All this mess is so we silently retry a fetch if we fail with
+ service_unavailable, without an (equivalent) mess of gotos */
+ retry = 0;
+ do {
+ retry++;
+ camel_exception_clear(ex);
+
+ /* If we are online, make sure we're also connected */
+ if (camel_disco_store_status((CamelDiscoStore *)store) == CAMEL_DISCO_STORE_ONLINE
+ && !camel_imap_store_connected(store, ex))
+ goto fail;
+
+ /* If the message is small or only 1 part, or server doesn't do 4v1 (properly) fetch it in one piece. */
+ if (store->server_level < IMAP_LEVEL_IMAP4REV1
+ || store->braindamaged
+ || mi->size < IMAP_SMALL_BODY_SIZE
+ || (!content_info_incomplete(mi->content) && !mi->content->childs)) {
+ msg = get_message_simple (imap_folder, uid, NULL, ex);
+ } else {
+ if (content_info_incomplete (mi->content)) {
+ /* For larger messages, fetch the structure and build a message
+ * with offline parts. (We check mi->content->type rather than
+ * mi->content because camel_folder_summary_info_new always creates
+ * an empty content struct.)
+ */
+ CamelImapResponse *response;
+ GData *fetch_data = NULL;
+ char *body, *found_uid;
+ int i;
+
+ if (camel_disco_store_status (CAMEL_DISCO_STORE (store)) == CAMEL_DISCO_STORE_OFFLINE) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("This message is not currently available"));
+ goto fail;
+ }
+
+ response = camel_imap_command (store, folder, ex, "UID FETCH %s BODY", uid);
+ if (response) {
+ for (i = 0, body = NULL; i < response->untagged->len; i++) {
+ fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
+ if (fetch_data) {
+ found_uid = g_datalist_get_data (&fetch_data, "UID");
+ body = g_datalist_get_data (&fetch_data, "BODY");
+ if (found_uid && body && !strcmp (found_uid, uid))
+ break;
+ g_datalist_clear (&fetch_data);
+ fetch_data = NULL;
+ body = NULL;
+ }
+ }
+
+ if (body)
+ imap_parse_body ((const char **) &body, folder, mi->content);
+
+ if (fetch_data)
+ g_datalist_clear (&fetch_data);
+
+ camel_imap_response_free (store, response);
+ }
+ }
+
+ if (camel_debug_start("imap:folder")) {
+ printf("Folder get message '%s' folder info ->\n", uid);
+ camel_message_info_dump(mi);
+ camel_debug_end();
+ }
+
+ /* FETCH returned OK, but we didn't parse a BODY
+ * response. Courier will return invalid BODY
+ * responses for invalidly MIMEd messages, so
+ * fall back to fetching the entire thing and
+ * let the mailer's "bad MIME" code handle it.
+ */
+ if (content_info_incomplete (mi->content))
+ msg = get_message_simple (imap_folder, uid, NULL, ex);
+ else
+ msg = get_message (imap_folder, uid, mi->content, ex);
+ }
+ } while (msg == NULL
+ && retry < 2
+ && camel_exception_get_id(ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE);
+
+done: /* FIXME, this shouldn't be done this way. */
+ if (msg)
+ camel_medium_set_header (CAMEL_MEDIUM (msg), "X-Evolution-Source", store->base_url);
+fail:
+ camel_folder_summary_info_free (folder->summary, mi);
+
+ return msg;
+}
+
+static void
+imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid,
+ CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (disco_folder);
+ CamelStream *stream;
+
+ stream = camel_imap_folder_fetch_data (imap_folder, uid, "", FALSE, ex);
+ if (stream)
+ camel_object_unref (CAMEL_OBJECT (stream));
+}
+
+/* We pretend that a FLAGS or RFC822.SIZE response is always exactly
+ * 20 bytes long, and a BODY[HEADERS] response is always 2000 bytes
+ * long. Since we know how many of each kind of response we're
+ * expecting, we can find the total (pretend) amount of server traffic
+ * to expect and then count off the responses as we read them to update
+ * the progress bar.
+ */
+#define IMAP_PRETEND_SIZEOF_FLAGS 20
+#define IMAP_PRETEND_SIZEOF_SIZE 20
+#define IMAP_PRETEND_SIZEOF_HEADERS 2000
+
+static char *tm_months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static gboolean
+decode_time (const unsigned char **in, int *hour, int *min, int *sec)
+{
+ register const unsigned char *inptr;
+ int *val, colons = 0;
+
+ *hour = *min = *sec = 0;
+
+ val = hour;
+ for (inptr = *in; *inptr && !isspace ((int) *inptr); inptr++) {
+ if (*inptr == ':') {
+ colons++;
+ switch (colons) {
+ case 1:
+ val = min;
+ break;
+ case 2:
+ val = sec;
+ break;
+ default:
+ return FALSE;
+ }
+ } else if (!isdigit ((int) *inptr))
+ return FALSE;
+ else
+ *val = (*val * 10) + (*inptr - '0');
+ }
+
+ *in = inptr;
+
+ return TRUE;
+}
+
+static time_t
+decode_internaldate (const unsigned char *in)
+{
+ const unsigned char *inptr = in;
+ int hour, min, sec, n;
+ unsigned char *buf;
+ struct tm tm;
+ time_t date;
+
+ memset ((void *) &tm, 0, sizeof (struct tm));
+
+ tm.tm_mday = strtoul (inptr, (char **) &buf, 10);
+ if (buf == inptr || *buf != '-')
+ return (time_t) -1;
+
+ inptr = buf + 1;
+ if (inptr[3] != '-')
+ return (time_t) -1;
+
+ for (n = 0; n < 12; n++) {
+ if (!strncasecmp (inptr, tm_months[n], 3))
+ break;
+ }
+
+ if (n >= 12)
+ return (time_t) -1;
+
+ tm.tm_mon = n;
+
+ inptr += 4;
+
+ n = strtoul (inptr, (char **) &buf, 10);
+ if (buf == inptr || *buf != ' ')
+ return (time_t) -1;
+
+ tm.tm_year = n - 1900;
+
+ inptr = buf + 1;
+ if (!decode_time (&inptr, &hour, &min, &sec))
+ return (time_t) -1;
+
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+
+ n = strtol (inptr, NULL, 10);
+
+ date = e_mktime_utc (&tm);
+
+ /* date is now GMT of the time we want, but not offset by the timezone ... */
+
+ /* this should convert the time to the GMT equiv time */
+ date -= ((n / 100) * 60 * 60) + (n % 100) * 60;
+
+ return date;
+}
+
+static void
+add_message_from_data (CamelFolder *folder, GPtrArray *messages,
+ int first, GData *data)
+{
+ CamelMimeMessage *msg;
+ CamelStream *stream;
+ CamelMessageInfo *mi;
+ const char *idate;
+ int seq;
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ if (seq < first)
+ return;
+ stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
+ if (!stream)
+ return;
+
+ if (seq - first >= messages->len)
+ g_ptr_array_set_size (messages, seq - first + 1);
+
+ msg = camel_mime_message_new ();
+ if (camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream) == -1) {
+ camel_object_unref (CAMEL_OBJECT (msg));
+ return;
+ }
+
+ mi = camel_folder_summary_info_new_from_message (folder->summary, msg);
+ camel_object_unref (CAMEL_OBJECT (msg));
+
+ if ((idate = g_datalist_get_data (&data, "INTERNALDATE")))
+ mi->date_received = decode_internaldate (idate);
+
+ if (mi->date_received == -1)
+ mi->date_received = mi->date_sent;
+
+ messages->pdata[seq - first] = mi;
+}
+
+
+#define CAMEL_MESSAGE_INFO_HEADERS "DATE FROM TO CC SUBJECT REFERENCES IN-REPLY-TO MESSAGE-ID MIME-VERSION CONTENT-TYPE"
+
+/* FIXME: this needs to be kept in sync with camel-mime-utils.c's list
+ of mailing-list headers and so might be best if this were
+ auto-generated? */
+#define MAILING_LIST_HEADERS "X-MAILING-LIST X-LOOP LIST-ID LIST-POST MAILING-LIST ORIGINATOR X-LIST SENDER RETURN-PATH X-BEENTHERE"
+
+static void
+imap_update_summary (CamelFolder *folder, int exists,
+ CamelFolderChangeInfo *changes,
+ CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ GPtrArray *fetch_data = NULL, *messages = NULL, *needheaders;
+ guint32 flags, uidval;
+ int i, seq, first, size, got;
+ CamelImapResponseType type;
+ const char *header_spec;
+ CamelMessageInfo *mi, *info;
+ CamelStream *stream;
+ char *uid, *resp;
+ GData *data;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+ if (store->server_level >= IMAP_LEVEL_IMAP4REV1)
+ header_spec = "HEADER.FIELDS.NOT (RECEIVED)";
+ else
+ header_spec = "0";
+
+ /* Figure out if any of the new messages are already cached (which
+ * may be the case if we're re-syncing after disconnected operation).
+ * If so, get their UIDs, FLAGS, and SIZEs. If not, get all that
+ * and ask for the headers too at the same time.
+ */
+ seq = camel_folder_summary_count (folder->summary);
+ first = seq + 1;
+ if (seq > 0) {
+ mi = camel_folder_summary_index (folder->summary, seq - 1);
+ uidval = strtoul(camel_message_info_uid (mi), NULL, 10);
+ camel_folder_summary_info_free (folder->summary, mi);
+ } else
+ uidval = 0;
+
+ size = (exists - seq) * (IMAP_PRETEND_SIZEOF_FLAGS + IMAP_PRETEND_SIZEOF_SIZE + IMAP_PRETEND_SIZEOF_HEADERS);
+ got = 0;
+ if (!camel_imap_command_start (store, folder, ex,
+ "UID FETCH %d:* (FLAGS RFC822.SIZE INTERNALDATE BODY.PEEK[%s])",
+ uidval + 1, header_spec))
+ return;
+ camel_operation_start (NULL, _("Fetching summary information for new messages"));
+
+ /* Parse the responses. We can't add a message to the summary
+ * until we've gotten its headers, and there's no guarantee
+ * the server will send the responses in a useful order...
+ */
+ fetch_data = g_ptr_array_new ();
+ messages = g_ptr_array_new ();
+ while ((type = camel_imap_command_response (store, &resp, ex)) ==
+ CAMEL_IMAP_RESPONSE_UNTAGGED) {
+ data = parse_fetch_response (imap_folder, resp);
+ g_free (resp);
+ if (!data)
+ continue;
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ if (seq < first) {
+ g_datalist_clear (&data);
+ continue;
+ }
+
+ if (g_datalist_get_data (&data, "FLAGS"))
+ got += IMAP_PRETEND_SIZEOF_FLAGS;
+ if (g_datalist_get_data (&data, "RFC822.SIZE"))
+ got += IMAP_PRETEND_SIZEOF_SIZE;
+ stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
+ if (stream) {
+ got += IMAP_PRETEND_SIZEOF_HEADERS;
+
+ /* Use the stream now so we don't tie up many
+ * many fds if we're fetching many many messages.
+ */
+ add_message_from_data (folder, messages, first, data);
+ g_datalist_set_data (&data, "BODY_PART_STREAM", NULL);
+ }
+
+ camel_operation_progress (NULL, got * 100 / size);
+ g_ptr_array_add (fetch_data, data);
+ }
+ camel_operation_end (NULL);
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR)
+ goto lose;
+
+ /* Free the final tagged response */
+ g_free (resp);
+
+ /* Figure out which headers we still need to fetch. */
+ needheaders = g_ptr_array_new ();
+ size = got = 0;
+ for (i = 0; i < fetch_data->len; i++) {
+ data = fetch_data->pdata[i];
+ if (g_datalist_get_data (&data, "BODY_PART_LEN"))
+ continue;
+
+ uid = g_datalist_get_data (&data, "UID");
+ if (uid) {
+ g_ptr_array_add (needheaders, uid);
+ size += IMAP_PRETEND_SIZEOF_HEADERS;
+ }
+ }
+
+ /* And fetch them */
+ if (needheaders->len) {
+ char *uidset;
+ int uid = 0;
+
+ qsort (needheaders->pdata, needheaders->len,
+ sizeof (void *), uid_compar);
+
+ camel_operation_start (NULL, _("Fetching summary information for new messages"));
+
+ while (uid < needheaders->len) {
+ uidset = imap_uid_array_to_set (folder->summary, needheaders, uid, UID_SET_LIMIT, &uid);
+ if (!camel_imap_command_start (store, folder, ex,
+ "UID FETCH %s BODY.PEEK[%s]",
+ uidset, header_spec)) {
+ g_ptr_array_free (needheaders, TRUE);
+ camel_operation_end (NULL);
+ g_free (uidset);
+ goto lose;
+ }
+ g_free (uidset);
+
+ while ((type = camel_imap_command_response (store, &resp, ex))
+ == CAMEL_IMAP_RESPONSE_UNTAGGED) {
+ data = parse_fetch_response (imap_folder, resp);
+ g_free (resp);
+ if (!data)
+ continue;
+
+ stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
+ if (stream) {
+ add_message_from_data (folder, messages, first, data);
+ got += IMAP_PRETEND_SIZEOF_HEADERS;
+ camel_operation_progress (NULL, got * 100 / size);
+ }
+ g_datalist_clear (&data);
+ }
+
+ if (type == CAMEL_IMAP_RESPONSE_ERROR) {
+ g_ptr_array_free (needheaders, TRUE);
+ camel_operation_end (NULL);
+ goto lose;
+ }
+ }
+
+ g_ptr_array_free (needheaders, TRUE);
+ camel_operation_end (NULL);
+ }
+
+ /* Now finish up summary entries (fix UIDs, set flags and size) */
+ for (i = 0; i < fetch_data->len; i++) {
+ data = fetch_data->pdata[i];
+
+ seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+ if (seq >= first + messages->len) {
+ g_datalist_clear (&data);
+ continue;
+ }
+
+ mi = messages->pdata[seq - first];
+ if (mi == NULL) {
+ CamelMessageInfo *pmi = NULL;
+ int j;
+
+ /* This is a kludge around a bug in Exchange
+ * 5.5 that sometimes claims multiple messages
+ * have the same UID. See bug #17694 for
+ * details. The "solution" is to create a fake
+ * message-info with the same details as the
+ * previously valid message. Yes, the user
+ * will have a clone in his/her message-list,
+ * but at least we don't crash.
+ */
+
+ /* find the previous valid message info */
+ for (j = seq - first - 1; j >= 0; j--) {
+ pmi = messages->pdata[j];
+ if (pmi != NULL)
+ break;
+ }
+
+ if (pmi == NULL) {
+ /* Server response is *really* fucked up,
+ I guess we just pretend it never happened? */
+ continue;
+ }
+
+ mi = camel_message_info_new ();
+ camel_message_info_dup_to (pmi, mi);
+ }
+
+ uid = g_datalist_get_data (&data, "UID");
+ if (uid)
+ camel_message_info_set_uid (mi, g_strdup (uid));
+ flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS"));
+ if (flags) {
+ ((CamelImapMessageInfo *)mi)->server_flags = flags;
+ /* "or" them in with the existing flags that may
+ * have been set by summary_info_new_from_message.
+ */
+ mi->flags |= flags;
+ }
+ size = GPOINTER_TO_INT (g_datalist_get_data (&data, "RFC822.SIZE"));
+ if (size)
+ mi->size = size;
+
+ g_datalist_clear (&data);
+ }
+ g_ptr_array_free (fetch_data, TRUE);
+
+ /* And add the entries to the summary, etc. */
+ for (i = 0; i < messages->len; i++) {
+ mi = messages->pdata[i];
+ if (!mi) {
+ g_warning ("No information for message %d", i + first);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Incomplete server response: no information provided for message %d"),
+ i + first);
+ break;
+ }
+ uid = (char *)camel_message_info_uid(mi);
+ if (uid[0] == 0) {
+ g_warning("Server provided no uid: message %d", i + first);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Incomplete server response: no UID provided for message %d"),
+ i + first);
+ break;
+ }
+ info = camel_folder_summary_uid(folder->summary, uid);
+ if (info) {
+ for (seq = 0; seq < camel_folder_summary_count (folder->summary); seq++) {
+ if (folder->summary->messages->pdata[seq] == info)
+ break;
+ }
+
+ g_warning("Message already present? %s", camel_message_info_uid(mi));
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected server response: Identical UIDs provided for messages %d and %d"),
+ seq + 1, i + first);
+
+ camel_folder_summary_info_free(folder->summary, info);
+ break;
+ }
+
+ camel_folder_summary_add (folder->summary, mi);
+ camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
+
+ if ((mi->flags & CAMEL_IMAP_MESSAGE_RECENT))
+ camel_folder_change_info_recent_uid(changes, camel_message_info_uid (mi));
+ }
+
+ for ( ; i < messages->len; i++) {
+ if ((mi = messages->pdata[i]))
+ camel_folder_summary_info_free(folder->summary, mi);
+ }
+
+ g_ptr_array_free (messages, TRUE);
+
+ return;
+
+ lose:
+ if (fetch_data) {
+ for (i = 0; i < fetch_data->len; i++) {
+ data = fetch_data->pdata[i];
+ g_datalist_clear (&data);
+ }
+ g_ptr_array_free (fetch_data, TRUE);
+ }
+ if (messages) {
+ for (i = 0; i < messages->len; i++) {
+ if (messages->pdata[i])
+ camel_folder_summary_info_free (folder->summary, messages->pdata[i]);
+ }
+ g_ptr_array_free (messages, TRUE);
+ }
+}
+
+/* Called with the store's connect_lock locked */
+void
+camel_imap_folder_changed (CamelFolder *folder, int exists,
+ GArray *expunged, CamelException *ex)
+{
+ CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+ CamelFolderChangeInfo *changes;
+ CamelMessageInfo *info;
+ int len;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (folder->parent_store, connect_lock);
+
+ changes = camel_folder_change_info_new ();
+ if (expunged) {
+ int i, id;
+
+ for (i = 0; i < expunged->len; i++) {
+ id = g_array_index (expunged, int, i);
+ info = camel_folder_summary_index (folder->summary, id - 1);
+ if (info == NULL) {
+ /* FIXME: danw: does this mean that the summary is corrupt? */
+ /* I guess a message that we never retrieved got expunged? */
+ continue;
+ }
+
+ camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
+ CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock);
+ camel_imap_message_cache_remove (imap_folder->cache, camel_message_info_uid (info));
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ camel_folder_summary_remove (folder->summary, info);
+ camel_folder_summary_info_free(folder->summary, info);
+ }
+ }
+
+ len = camel_folder_summary_count (folder->summary);
+ if (exists > len)
+ imap_update_summary (folder, exists, changes, ex);
+
+ if (camel_folder_change_info_changed (changes))
+ camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", changes);
+
+ camel_folder_change_info_free (changes);
+ camel_folder_summary_save (folder->summary);
+}
+
+static void
+imap_thaw (CamelFolder *folder)
+{
+ CamelImapFolder *imap_folder;
+
+ CAMEL_FOLDER_CLASS (disco_folder_class)->thaw (folder);
+ if (camel_folder_is_frozen (folder))
+ return;
+
+ imap_folder = CAMEL_IMAP_FOLDER (folder);
+ if (imap_folder->need_refresh) {
+ imap_folder->need_refresh = FALSE;
+ imap_refresh_info (folder, NULL);
+ }
+}
+
+
+CamelStream *
+camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid,
+ const char *section_text, gboolean cache_only,
+ CamelException *ex)
+{
+ CamelFolder *folder = CAMEL_FOLDER (imap_folder);
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response;
+ CamelStream *stream;
+ GData *fetch_data;
+ char *found_uid;
+ int i;
+
+ /* EXPUNGE responses have to modify the cache, which means
+ * they have to grab the cache_lock while holding the
+ * connect_lock. So we grab the connect_lock now, in case
+ * we're going to need it below, since we can't grab it
+ * after the cache_lock.
+ */
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock);
+ stream = camel_imap_message_cache_get (imap_folder->cache, uid, section_text, ex);
+ if (!stream && (!strcmp (section_text, "HEADER") || !strcmp (section_text, "0"))) {
+ camel_exception_clear (ex);
+ stream = camel_imap_message_cache_get (imap_folder->cache, uid, "", ex);
+ }
+
+ if (stream || cache_only) {
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return stream;
+ }
+
+ if (camel_disco_store_status (CAMEL_DISCO_STORE (store)) == CAMEL_DISCO_STORE_OFFLINE) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("This message is not currently available"));
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return NULL;
+ }
+
+ camel_exception_clear (ex);
+ if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text) {
+ response = camel_imap_command (store, folder, ex,
+ "UID FETCH %s RFC822.PEEK",
+ uid);
+ } else {
+ response = camel_imap_command (store, folder, ex,
+ "UID FETCH %s BODY.PEEK[%s]",
+ uid, section_text);
+ }
+ /* We won't need the connect_lock again after this. */
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ if (!response) {
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ return NULL;
+ }
+
+ for (i = 0; i < response->untagged->len; i++) {
+ fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
+ found_uid = g_datalist_get_data (&fetch_data, "UID");
+ stream = g_datalist_get_data (&fetch_data, "BODY_PART_STREAM");
+ if (found_uid && stream && !strcmp (uid, found_uid))
+ break;
+
+ g_datalist_clear (&fetch_data);
+ stream = NULL;
+ }
+ camel_imap_response_free (store, response);
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ if (!stream) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not find message body in FETCH response."));
+ } else {
+ camel_object_ref (CAMEL_OBJECT (stream));
+ g_datalist_clear (&fetch_data);
+ }
+
+ return stream;
+}
+
+static GData *
+parse_fetch_response (CamelImapFolder *imap_folder, char *response)
+{
+ GData *data = NULL;
+ char *start, *part_spec = NULL, *body = NULL, *uid = NULL, *idate = NULL;
+ gboolean cache_header = TRUE, header = FALSE;
+ size_t body_len = 0;
+
+ if (*response != '(') {
+ long seq;
+
+ if (*response != '*' || *(response + 1) != ' ')
+ return NULL;
+ seq = strtol (response + 2, &response, 10);
+ if (seq == 0)
+ return NULL;
+ if (strncasecmp (response, " FETCH (", 8) != 0)
+ return NULL;
+ response += 7;
+
+ g_datalist_set_data (&data, "SEQUENCE", GINT_TO_POINTER (seq));
+ }
+
+ do {
+ /* Skip the initial '(' or the ' ' between elements */
+ response++;
+
+ if (!strncasecmp (response, "FLAGS ", 6)) {
+ guint32 flags;
+
+ response += 6;
+ /* FIXME user flags */
+ flags = imap_parse_flag_list (&response);
+
+ g_datalist_set_data (&data, "FLAGS", GUINT_TO_POINTER (flags));
+ } else if (!strncasecmp (response, "RFC822.SIZE ", 12)) {
+ unsigned long size;
+
+ response += 12;
+ size = strtoul (response, &response, 10);
+ g_datalist_set_data (&data, "RFC822.SIZE", GUINT_TO_POINTER (size));
+ } else if (!strncasecmp (response, "BODY[", 5) ||
+ !strncasecmp (response, "RFC822 ", 7)) {
+ char *p;
+
+ if (*response == 'B') {
+ response += 5;
+
+ /* HEADER], HEADER.FIELDS (...)], or 0] */
+ if (!strncasecmp (response, "HEADER", 6)) {
+ header = TRUE;
+ if (!strncasecmp (response + 6, ".FIELDS", 7))
+ cache_header = FALSE;
+ } else if (!strncasecmp (response, "0]", 2))
+ header = TRUE;
+
+ p = strchr (response, ']');
+ if (!p || *(p + 1) != ' ')
+ break;
+
+ if (cache_header)
+ part_spec = g_strndup (response, p - response);
+ else
+ part_spec = g_strdup ("HEADER.FIELDS");
+
+ response = p + 2;
+ } else {
+ part_spec = g_strdup ("");
+ response += 7;
+
+ if (!strncasecmp (response, "HEADER", 6))
+ header = TRUE;
+ }
+
+ body = imap_parse_nstring ((const char **) &response, &body_len);
+ if (!response) {
+ g_free (part_spec);
+ break;
+ }
+
+ if (!body)
+ body = g_strdup ("");
+ g_datalist_set_data_full (&data, "BODY_PART_SPEC", part_spec, g_free);
+ g_datalist_set_data_full (&data, "BODY_PART_DATA", body, g_free);
+ g_datalist_set_data (&data, "BODY_PART_LEN", GINT_TO_POINTER (body_len));
+ } else if (!strncasecmp (response, "BODY ", 5) ||
+ !strncasecmp (response, "BODYSTRUCTURE ", 14)) {
+ response = strchr (response, ' ') + 1;
+ start = response;
+ imap_skip_list ((const char **) &response);
+ g_datalist_set_data_full (&data, "BODY", g_strndup (start, response - start), g_free);
+ } else if (!strncasecmp (response, "UID ", 4)) {
+ int len;
+
+ len = strcspn (response + 4, " )");
+ uid = g_strndup (response + 4, len);
+ g_datalist_set_data_full (&data, "UID", uid, g_free);
+ response += 4 + len;
+ } else if (!strncasecmp (response, "INTERNALDATE ", 13)) {
+ int len;
+
+ response += 13;
+ if (*response == '"') {
+ response++;
+ len = strcspn (response, "\"");
+ idate = g_strndup (response, len);
+ g_datalist_set_data_full (&data, "INTERNALDATE", idate, g_free);
+ response += len + 1;
+ }
+ } else {
+ g_warning ("Unexpected FETCH response from server: (%s", response);
+ break;
+ }
+ } while (response && *response != ')');
+
+ if (!response || *response != ')') {
+ g_datalist_clear (&data);
+ return NULL;
+ }
+
+ if (uid && body) {
+ CamelStream *stream;
+
+ if (header && !cache_header) {
+ stream = camel_stream_mem_new_with_buffer (body, body_len);
+ } else {
+ CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock);
+ stream = camel_imap_message_cache_insert (imap_folder->cache,
+ uid, part_spec,
+ body, body_len, NULL);
+ CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock);
+ if (stream == NULL)
+ stream = camel_stream_mem_new_with_buffer (body, body_len);
+ }
+
+ if (stream)
+ g_datalist_set_data_full (&data, "BODY_PART_STREAM", stream,
+ (GDestroyNotify) camel_object_unref);
+ }
+
+ return data;
+}
+
diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c
new file mode 100644
index 0000000000..29f56a9da6
--- /dev/null
+++ b/camel/providers/imap/camel-imap-store.c
@@ -0,0 +1,3271 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-store.c : class for an imap store */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2000, 2003 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "e-util/e-path.h"
+
+#include "camel-imap-store.h"
+#include "camel-imap-store-summary.h"
+#include "camel-imap-folder.h"
+#include "camel-imap-utils.h"
+#include "camel-imap-command.h"
+#include "camel-imap-summary.h"
+#include "camel-imap-message-cache.h"
+#include "camel-disco-diary.h"
+#include "camel-file-utils.h"
+#include "camel-folder.h"
+#include "camel-exception.h"
+#include "camel-session.h"
+#include "camel-stream.h"
+#include "camel-stream-buffer.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-process.h"
+#include "camel-tcp-stream-raw.h"
+#include "camel-tcp-stream-ssl.h"
+#include "camel-url.h"
+#include "camel-sasl.h"
+#include "camel-utf8.h"
+#include "camel-string-utils.h"
+
+#include "camel-imap-private.h"
+#include "camel-private.h"
+
+#include "camel-debug.h"
+
+#define d(x)
+
+/* Specified in RFC 2060 */
+#define IMAP_PORT "143"
+#define IMAPS_PORT "993"
+
+static CamelDiscoStoreClass *parent_class = NULL;
+
+static char imap_tag_prefix = 'A';
+
+static void construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+
+static int imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
+static int imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
+
+static char *imap_get_name (CamelService *service, gboolean brief);
+
+static gboolean can_work_offline (CamelDiscoStore *disco_store);
+static gboolean imap_connect_online (CamelService *service, CamelException *ex);
+static gboolean imap_connect_offline (CamelService *service, CamelException *ex);
+static gboolean imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex);
+static gboolean imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex);
+static void imap_noop (CamelStore *store, CamelException *ex);
+static CamelFolder *imap_get_junk(CamelStore *store, CamelException *ex);
+static CamelFolder *imap_get_trash(CamelStore *store, CamelException *ex);
+static GList *query_auth_types (CamelService *service, CamelException *ex);
+static guint hash_folder_name (gconstpointer key);
+static gint compare_folder_name (gconstpointer a, gconstpointer b);
+static CamelFolder *get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+static CamelFolder *get_folder_offline (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+
+static CamelFolderInfo *create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
+static void delete_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *get_folder_info_online (CamelStore *store,
+ const char *top,
+ guint32 flags,
+ CamelException *ex);
+static CamelFolderInfo *get_folder_info_offline (CamelStore *store,
+ const char *top,
+ guint32 flags,
+ CamelException *ex);
+static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
+static void subscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex);
+static void unsubscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex);
+
+static void get_folders_online (CamelImapStore *imap_store, const char *pattern,
+ GPtrArray *folders, gboolean lsub, CamelException *ex);
+
+
+static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
+static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, CamelException *ex);
+static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
+static void imap_set_server_level (CamelImapStore *store);
+
+static void
+camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
+{
+ CamelObjectClass *camel_object_class =
+ CAMEL_OBJECT_CLASS (camel_imap_store_class);
+ CamelServiceClass *camel_service_class =
+ CAMEL_SERVICE_CLASS (camel_imap_store_class);
+ CamelStoreClass *camel_store_class =
+ CAMEL_STORE_CLASS (camel_imap_store_class);
+ CamelDiscoStoreClass *camel_disco_store_class =
+ CAMEL_DISCO_STORE_CLASS (camel_imap_store_class);
+
+ parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
+
+ /* virtual method overload */
+ camel_object_class->setv = imap_setv;
+ camel_object_class->getv = imap_getv;
+
+ camel_service_class->construct = construct;
+ camel_service_class->query_auth_types = query_auth_types;
+ camel_service_class->get_name = imap_get_name;
+
+ camel_store_class->hash_folder_name = hash_folder_name;
+ camel_store_class->compare_folder_name = compare_folder_name;
+ camel_store_class->create_folder = create_folder;
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = rename_folder;
+ camel_store_class->free_folder_info = camel_store_free_folder_info_full;
+ camel_store_class->folder_subscribed = folder_subscribed;
+ camel_store_class->subscribe_folder = subscribe_folder;
+ camel_store_class->unsubscribe_folder = unsubscribe_folder;
+ camel_store_class->noop = imap_noop;
+ camel_store_class->get_trash = imap_get_trash;
+ camel_store_class->get_junk = imap_get_junk;
+
+ camel_disco_store_class->can_work_offline = can_work_offline;
+ camel_disco_store_class->connect_online = imap_connect_online;
+ camel_disco_store_class->connect_offline = imap_connect_offline;
+ camel_disco_store_class->disconnect_online = imap_disconnect_online;
+ camel_disco_store_class->disconnect_offline = imap_disconnect_offline;
+ camel_disco_store_class->get_folder_online = get_folder_online;
+ camel_disco_store_class->get_folder_offline = get_folder_offline;
+ camel_disco_store_class->get_folder_resyncing = get_folder_online;
+ camel_disco_store_class->get_folder_info_online = get_folder_info_online;
+ camel_disco_store_class->get_folder_info_offline = get_folder_info_offline;
+ camel_disco_store_class->get_folder_info_resyncing = get_folder_info_online;
+}
+
+static gboolean
+free_key (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+ return TRUE;
+}
+
+static void
+camel_imap_store_finalize (CamelObject *object)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
+
+ /* This frees current_folder, folders, authtypes, streams, and namespace. */
+ camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
+
+ if (imap_store->summary) {
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ camel_object_unref(imap_store->summary);
+ }
+
+ if (imap_store->base_url)
+ g_free (imap_store->base_url);
+ if (imap_store->storage_path)
+ g_free (imap_store->storage_path);
+}
+
+static void
+camel_imap_store_init (gpointer object, gpointer klass)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
+
+ imap_store->istream = NULL;
+ imap_store->ostream = NULL;
+
+ imap_store->dir_sep = '\0';
+ imap_store->current_folder = NULL;
+ imap_store->connected = FALSE;
+ imap_store->preauthed = FALSE;
+
+ imap_store->tag_prefix = imap_tag_prefix++;
+ if (imap_tag_prefix > 'Z')
+ imap_tag_prefix = 'A';
+}
+
+CamelType
+camel_imap_store_get_type (void)
+{
+ static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_imap_store_type == CAMEL_INVALID_TYPE) {
+ camel_imap_store_type =
+ camel_type_register (CAMEL_DISCO_STORE_TYPE,
+ "CamelImapStore",
+ sizeof (CamelImapStore),
+ sizeof (CamelImapStoreClass),
+ (CamelObjectClassInitFunc) camel_imap_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap_store_init,
+ (CamelObjectFinalizeFunc) camel_imap_store_finalize);
+ }
+
+ return camel_imap_store_type;
+}
+
+static void
+construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (service);
+ CamelStore *store = CAMEL_STORE (service);
+ char *tmp;
+ CamelURL *summary_url;
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ imap_store->storage_path = camel_session_get_storage_path (session, service, ex);
+ if (!imap_store->storage_path)
+ return;
+
+ /* FIXME */
+ imap_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
+ CAMEL_URL_HIDE_PARAMS |
+ CAMEL_URL_HIDE_AUTH));
+
+ imap_store->parameters = 0;
+ if (camel_url_get_param (url, "use_lsub"))
+ store->flags |= CAMEL_STORE_SUBSCRIPTIONS;
+ if (camel_url_get_param (url, "namespace")) {
+ imap_store->parameters |= IMAP_PARAM_OVERRIDE_NAMESPACE;
+ g_free(imap_store->namespace);
+ imap_store->namespace = g_strdup (camel_url_get_param (url, "namespace"));
+ }
+ if (camel_url_get_param (url, "check_all"))
+ imap_store->parameters |= IMAP_PARAM_CHECK_ALL;
+ if (camel_url_get_param (url, "filter")) {
+ imap_store->parameters |= IMAP_PARAM_FILTER_INBOX;
+ store->flags |= CAMEL_STORE_FILTER_INBOX;
+ }
+ if (camel_url_get_param (url, "filter_junk"))
+ imap_store->parameters |= IMAP_PARAM_FILTER_JUNK;
+ if (camel_url_get_param (url, "filter_junk_inbox"))
+ imap_store->parameters |= IMAP_PARAM_FILTER_JUNK_INBOX;
+
+ /* setup/load the store summary */
+ tmp = alloca(strlen(imap_store->storage_path)+32);
+ sprintf(tmp, "%s/.ev-store-summary", imap_store->storage_path);
+ imap_store->summary = camel_imap_store_summary_new();
+ camel_store_summary_set_filename((CamelStoreSummary *)imap_store->summary, tmp);
+ summary_url = camel_url_new(imap_store->base_url, NULL);
+ camel_store_summary_set_uri_base((CamelStoreSummary *)imap_store->summary, summary_url);
+ camel_url_free(summary_url);
+ if (camel_store_summary_load((CamelStoreSummary *)imap_store->summary) == 0) {
+ CamelImapStoreSummary *is = imap_store->summary;
+
+ if (is->namespace) {
+ /* if namespace has changed, clear folder list */
+ if (imap_store->namespace && strcmp(imap_store->namespace, is->namespace->full_name) != 0) {
+ camel_store_summary_clear((CamelStoreSummary *)is);
+ } else {
+ imap_store->namespace = g_strdup(is->namespace->full_name);
+ imap_store->dir_sep = is->namespace->sep;
+ }
+ }
+
+ imap_store->capabilities = is->capabilities;
+ imap_set_server_level(imap_store);
+ }
+}
+
+static int
+imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+ CamelImapStore *store = (CamelImapStore *) object;
+ guint32 tag, flags;
+ int i;
+
+ for (i = 0; i < args->argc; i++) {
+ tag = args->argv[i].tag;
+
+ /* make sure this is an arg we're supposed to handle */
+ if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
+ (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
+ continue;
+
+ switch (tag) {
+ case CAMEL_IMAP_STORE_NAMESPACE:
+ if (strcmp (store->namespace, args->argv[i].ca_str) != 0) {
+ g_free (store->namespace);
+ store->namespace = g_strdup (args->argv[i].ca_str);
+ /* the current imap code will need to do a reconnect for this to take effect */
+ /*reconnect = TRUE;*/
+ }
+ break;
+ case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_OVERRIDE_NAMESPACE : 0;
+ flags |= (store->parameters & ~IMAP_PARAM_OVERRIDE_NAMESPACE);
+
+ if (store->parameters != flags) {
+ store->parameters = flags;
+ /* the current imap code will need to do a reconnect for this to take effect */
+ /*reconnect = TRUE;*/
+ }
+ break;
+ case CAMEL_IMAP_STORE_CHECK_ALL:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_CHECK_ALL : 0;
+ flags |= (store->parameters & ~IMAP_PARAM_CHECK_ALL);
+ store->parameters = flags;
+ /* no need to reconnect for this option to take effect... */
+ break;
+ case CAMEL_IMAP_STORE_FILTER_INBOX:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_INBOX : 0;
+ flags |= (store->parameters & ~IMAP_PARAM_FILTER_INBOX);
+ store->parameters = flags;
+ /* no need to reconnect for this option to take effect... */
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_JUNK : 0;
+ store->parameters = flags | (store->parameters & ~IMAP_PARAM_FILTER_JUNK);
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_JUNK_INBOX : 0;
+ store->parameters = flags | (store->parameters & ~IMAP_PARAM_FILTER_JUNK_INBOX);
+ break;
+ default:
+ /* error?? */
+ continue;
+ }
+
+ /* let our parent know that we've handled this arg */
+ camel_argv_ignore (args, i);
+ }
+
+ /* FIXME: if we need to reconnect for a change to take affect,
+ we need to do it here... or, better yet, somehow chain it
+ up to CamelService's setv implementation. */
+
+ return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
+}
+
+static int
+imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ CamelImapStore *store = (CamelImapStore *) object;
+ guint32 tag;
+ int i;
+
+ for (i = 0; i < args->argc; i++) {
+ tag = args->argv[i].tag;
+
+ /* make sure this is an arg we're supposed to handle */
+ if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
+ (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
+ continue;
+
+ switch (tag) {
+ case CAMEL_IMAP_STORE_NAMESPACE:
+ *args->argv[i].ca_str = store->namespace;
+ break;
+ case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_CHECK_ALL:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_CHECK_ALL ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_FILTER_INBOX:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_INBOX ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK_INBOX ? TRUE : FALSE;
+ break;
+ default:
+ /* error? */
+ break;
+ }
+ }
+
+ return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
+}
+
+static char *
+imap_get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup_printf (_("IMAP server %s"), service->url->host);
+ else
+ return g_strdup_printf (_("IMAP service for %s on %s"),
+ service->url->user, service->url->host);
+}
+
+static void
+imap_set_server_level (CamelImapStore *store)
+{
+ if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) {
+ store->server_level = IMAP_LEVEL_IMAP4REV1;
+ store->capabilities |= IMAP_CAPABILITY_STATUS;
+ } else if (store->capabilities & IMAP_CAPABILITY_IMAP4)
+ store->server_level = IMAP_LEVEL_IMAP4;
+ else
+ store->server_level = IMAP_LEVEL_UNKNOWN;
+}
+
+static struct {
+ const char *name;
+ guint32 flag;
+} capabilities[] = {
+ { "IMAP4", IMAP_CAPABILITY_IMAP4 },
+ { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 },
+ { "STATUS", IMAP_CAPABILITY_STATUS },
+ { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE },
+ { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS },
+ { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS },
+ { "STARTTLS", IMAP_CAPABILITY_STARTTLS },
+ { NULL, 0 }
+};
+
+static gboolean
+imap_get_capability (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelImapResponse *response;
+ char *result, *capa, *lasts;
+ int i;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+
+ /* Find out the IMAP capabilities */
+ /* We assume we have utf8 capable search until a failed search tells us otherwise */
+ store->capabilities = IMAP_CAPABILITY_utf8_search;
+ store->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
+ response = camel_imap_command (store, NULL, ex, "CAPABILITY");
+ if (!response)
+ return FALSE;
+ result = camel_imap_response_extract (store, response, "CAPABILITY ", ex);
+ if (!result)
+ return FALSE;
+
+ /* Skip over "* CAPABILITY ". */
+ capa = result + 13;
+ for (capa = strtok_r (capa, " ", &lasts); capa;
+ capa = strtok_r (NULL, " ", &lasts)) {
+ if (!strncmp (capa, "AUTH=", 5)) {
+ g_hash_table_insert (store->authtypes,
+ g_strdup (capa + 5),
+ GINT_TO_POINTER (1));
+ continue;
+ }
+ for (i = 0; capabilities[i].name; i++) {
+ if (g_ascii_strcasecmp (capa, capabilities[i].name) == 0) {
+ store->capabilities |= capabilities[i].flag;
+ break;
+ }
+ }
+ }
+ g_free (result);
+
+ imap_set_server_level (store);
+
+ if (store->summary->capabilities != store->capabilities) {
+ store->summary->capabilities = store->capabilities;
+ camel_store_summary_touch((CamelStoreSummary *)store->summary);
+ camel_store_summary_save((CamelStoreSummary *)store->summary);
+ }
+
+ return TRUE;
+}
+
+enum {
+ USE_SSL_NEVER,
+ USE_SSL_ALWAYS,
+ USE_SSL_WHEN_POSSIBLE
+};
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+static gboolean
+connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex)
+{
+ CamelImapStore *store = (CamelImapStore *) service;
+ CamelImapResponse *response;
+ CamelStream *tcp_stream;
+ CamelSockOptData sockopt;
+ gboolean force_imap4 = FALSE;
+ int clean_quit;
+ int ret;
+ char *buf;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
+ const char *port = NULL;
+
+ /* FIXME: this connect stuff is duplicated everywhere */
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else {
+ serv = "imap";
+ port = IMAP_PORT;
+ }
+
+ if (ssl_mode != USE_SSL_NEVER) {
+#ifdef HAVE_SSL
+ if (try_starttls) {
+ tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
+ } else {
+ if (service->url->port == 0) {
+ serv = "imaps";
+ port = IMAPS_PORT;
+ }
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+ }
+#else
+ if (!try_starttls && service->url->port == 0) {
+ serv = "imaps";
+ port = IMAPS_PORT;
+ }
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
+ _("SSL unavailable"));
+ return FALSE;
+#endif /* HAVE_SSL */
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
+ camel_exception_clear(ex);
+ ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
+ }
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ return FALSE;
+ }
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv, g_strerror (errno));
+
+ camel_object_unref (tcp_stream);
+
+ return FALSE;
+ }
+
+ store->ostream = tcp_stream;
+ store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
+
+ store->connected = TRUE;
+ store->preauthed = FALSE;
+ store->command = 0;
+
+ /* Disable Nagle - we send a lot of small requests which nagle slows down */
+ sockopt.option = CAMEL_SOCKOPT_NODELAY;
+ sockopt.value.no_delay = TRUE;
+ camel_tcp_stream_setsockopt((CamelTcpStream *)tcp_stream, &sockopt);
+
+ /* Set keepalive - needed for some hosts/router configurations, we're idle a lot */
+ sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
+ sockopt.value.keep_alive = TRUE;
+ camel_tcp_stream_setsockopt((CamelTcpStream *)tcp_stream, &sockopt);
+
+ /* Read the greeting, if any, and deal with PREAUTH */
+ if (camel_imap_store_readline (store, &buf, ex) < 0) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+
+ return FALSE;
+ }
+
+ if (!strncmp(buf, "* PREAUTH", 9))
+ store->preauthed = TRUE;
+
+ if (strstr (buf, "Courier-IMAP") || getenv("CAMEL_IMAP_BRAINDAMAGED")) {
+ /* Courier-IMAP is braindamaged. So far this flag only
+ * works around the fact that Courier-IMAP is known to
+ * give invalid BODY responses seemingly because its
+ * MIME parser sucks. In any event, we can't rely on
+ * them so we always have to request the full messages
+ * rather than getting individual parts. */
+ store->braindamaged = TRUE;
+ } else if (strstr (buf, "WEB.DE") || strstr (buf, "Mail2World")) {
+ /* This is a workaround for servers which advertise
+ * IMAP4rev1 but which can sometimes subtly break in
+ * various ways if we try to use IMAP4rev1 queries.
+ *
+ * WEB.DE: when querying for HEADER.FIELDS.NOT, it
+ * returns an empty literal for the headers. Many
+ * complaints about empty message-list fields on the
+ * mailing lists and probably a few bugzilla bugs as
+ * well.
+ *
+ * Mail2World (aka NamePlanet): When requesting
+ * message info's, it ignores the fact that we
+ * requested BODY.PEEK[HEADER.FIELDS.NOT (RECEIVED)]
+ * and so the responses are incomplete. See bug #58766
+ * for details.
+ **/
+ force_imap4 = TRUE;
+ }
+
+ g_free (buf);
+
+ /* get the imap server capabilities */
+ if (!imap_get_capability (service, ex)) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+ return FALSE;
+ }
+
+ if (force_imap4) {
+ store->capabilities &= ~IMAP_CAPABILITY_IMAP4REV1;
+ store->server_level = IMAP_LEVEL_IMAP4;
+ }
+
+#ifdef HAVE_SSL
+ if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ if (store->capabilities & IMAP_CAPABILITY_STARTTLS)
+ goto starttls;
+ } else if (ssl_mode == USE_SSL_ALWAYS) {
+ if (try_starttls) {
+ if (store->capabilities & IMAP_CAPABILITY_STARTTLS) {
+ /* attempt to toggle STARTTLS mode */
+ goto starttls;
+ } else {
+ /* server doesn't support STARTTLS, abort */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ service->url->host, _("SSL/TLS extension not supported."));
+ /* we have the possibility of quitting cleanly here */
+ clean_quit = TRUE;
+ goto exception;
+ }
+ }
+ }
+#endif /* HAVE_SSL */
+
+ return TRUE;
+
+#ifdef HAVE_SSL
+ starttls:
+
+ /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */
+ clean_quit = FALSE;
+
+ response = camel_imap_command (store, NULL, ex, "STARTTLS");
+ if (!response) {
+ camel_object_unref (store->istream);
+ camel_object_unref (store->ostream);
+ store->istream = store->ostream = NULL;
+ return FALSE;
+ }
+
+ camel_imap_response_free_without_processing (store, response);
+
+ /* Okay, now toggle SSL/TLS mode */
+ if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ service->url->host, _("SSL negotiations failed"));
+ goto exception;
+ }
+
+ /* rfc2595, section 4 states that after a successful STLS
+ command, the client MUST discard prior CAPA responses */
+ if (!imap_get_capability (service, ex)) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+ exception:
+
+ if (clean_quit && store->connected) {
+ /* try to disconnect cleanly */
+ response = camel_imap_command (store, NULL, ex, "LOGOUT");
+ if (response)
+ camel_imap_response_free_without_processing (store, response);
+ }
+
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+
+ return FALSE;
+#endif /* HAVE_SSL */
+}
+
+static gboolean
+connect_to_server_process (CamelService *service, const char *cmd, CamelException *ex)
+{
+ CamelImapStore *store = (CamelImapStore *) service;
+ CamelStream *cmd_stream;
+ int ret, i = 0;
+ char *buf;
+ char *cmd_copy;
+ char *full_cmd;
+ char *child_env[7];
+
+ /* Put full details in the environment, in case the connection
+ program needs them */
+ buf = camel_url_to_string(service->url, 0);
+ child_env[i++] = g_strdup_printf("URL=%s", buf);
+ g_free(buf);
+
+ child_env[i++] = g_strdup_printf("URLHOST=%s", service->url->host);
+ if (service->url->port)
+ child_env[i++] = g_strdup_printf("URLPORT=%d", service->url->port);
+ if (service->url->user)
+ child_env[i++] = g_strdup_printf("URLUSER=%s", service->url->user);
+ if (service->url->passwd)
+ child_env[i++] = g_strdup_printf("URLPASSWD=%s", service->url->passwd);
+ if (service->url->path)
+ child_env[i++] = g_strdup_printf("URLPATH=%s", service->url->path);
+ child_env[i] = NULL;
+
+ /* Now do %h, %u, etc. substitution in cmd */
+ buf = cmd_copy = g_strdup(cmd);
+
+ full_cmd = g_strdup("");
+
+ for(;;) {
+ char *pc;
+ char *tmp;
+ char *var;
+ int len;
+
+ pc = strchr(buf, '%');
+ ignore:
+ if (!pc) {
+ tmp = g_strdup_printf("%s%s", full_cmd, buf);
+ g_free(full_cmd);
+ full_cmd = tmp;
+ break;
+ }
+
+ len = pc - buf;
+
+ var = NULL;
+
+ switch(pc[1]) {
+ case 'h':
+ var = service->url->host;
+ break;
+ case 'u':
+ var = service->url->user;
+ break;
+ }
+ if (!var) {
+ /* If there wasn't a valid %-code, with an actual
+ variable to insert, pretend we didn't see the % */
+ pc = strchr(pc + 1, '%');
+ goto ignore;
+ }
+ tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var);
+ g_free(full_cmd);
+ full_cmd = tmp;
+ buf = pc + 2;
+ }
+
+ g_free(cmd_copy);
+
+ cmd_stream = camel_stream_process_new ();
+
+ ret = camel_stream_process_connect (CAMEL_STREAM_PROCESS(cmd_stream),
+ full_cmd, (const char **)child_env);
+
+ while (i)
+ g_free(child_env[--i]);
+
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect with command \"%s\": %s"),
+ full_cmd, g_strerror (errno));
+
+ camel_object_unref (cmd_stream);
+ g_free (full_cmd);
+ return FALSE;
+ }
+ g_free (full_cmd);
+
+ store->ostream = cmd_stream;
+ store->istream = camel_stream_buffer_new (cmd_stream, CAMEL_STREAM_BUFFER_READ);
+
+ store->connected = TRUE;
+ store->preauthed = FALSE;
+ store->command = 0;
+
+ /* Read the greeting, if any, and deal with PREAUTH */
+ if (camel_imap_store_readline (store, &buf, ex) < 0) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+ return FALSE;
+ }
+ if (!strncmp(buf, "* PREAUTH", 9))
+ store->preauthed = TRUE;
+ g_free (buf);
+
+ /* get the imap server capabilities */
+ if (!imap_get_capability (service, ex)) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+static struct {
+ char *value;
+ int mode;
+} ssl_options[] = {
+ { "", USE_SSL_ALWAYS },
+ { "always", USE_SSL_ALWAYS },
+ { "when-possible", USE_SSL_WHEN_POSSIBLE },
+ { "never", USE_SSL_NEVER },
+ { NULL, USE_SSL_NEVER },
+};
+
+static gboolean
+connect_to_server_wrapper (CamelService *service, CamelException *ex)
+{
+ const char *command;
+#ifdef HAVE_SSL
+ const char *use_ssl;
+ int i, ssl_mode;
+#endif
+ command = camel_url_get_param (service->url, "command");
+ if (command)
+ return connect_to_server_process (service, command, ex);
+
+#ifdef HAVE_SSL
+ use_ssl = camel_url_get_param (service->url, "use_ssl");
+ if (use_ssl) {
+ for (i = 0; ssl_options[i].value; i++)
+ if (!strcmp (ssl_options[i].value, use_ssl))
+ break;
+ ssl_mode = ssl_options[i].mode;
+ } else
+ ssl_mode = USE_SSL_NEVER;
+
+ if (ssl_mode == USE_SSL_ALWAYS) {
+ /* First try the ssl port */
+ if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
+ if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
+ /* The ssl port seems to be unavailable, lets try STARTTLS */
+ camel_exception_clear (ex);
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ /* If the server supports STARTTLS, use it */
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ /* User doesn't care about SSL */
+ return connect_to_server (service, ssl_mode, FALSE, ex);
+ }
+#else
+ return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
+#endif
+}
+
+extern CamelServiceAuthType camel_imap_password_authtype;
+
+static GList *
+query_auth_types (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelServiceAuthType *authtype;
+ GList *sasl_types, *t, *next;
+ gboolean connected;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return NULL;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ connected = connect_to_server_wrapper (service, ex);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ if (!connected)
+ return NULL;
+
+ sasl_types = camel_sasl_authtype_list (FALSE);
+ for (t = sasl_types; t; t = next) {
+ authtype = t->data;
+ next = t->next;
+
+ if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) {
+ sasl_types = g_list_remove_link (sasl_types, t);
+ g_list_free_1 (t);
+ }
+ }
+
+ return g_list_prepend (sasl_types, &camel_imap_password_authtype);
+}
+
+/* folder_name is path name */
+static CamelFolderInfo *
+imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name)
+{
+ CamelURL *url;
+ const char *name;
+ CamelFolderInfo *fi;
+
+ fi = g_malloc0(sizeof(*fi));
+
+ fi->full_name = g_strdup(folder_name);
+ fi->unread = 0;
+ fi->total = 0;
+
+ url = camel_url_new (imap_store->base_url, NULL);
+ g_free (url->path);
+ url->path = g_strdup_printf ("/%s", folder_name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free(url);
+ name = strrchr (fi->full_name, '/');
+ if (name == NULL)
+ name = fi->full_name;
+ else
+ name++;
+ fi->name = g_strdup (name);
+
+ return fi;
+}
+
+static void
+imap_folder_effectively_unsubscribed(CamelImapStore *imap_store,
+ const char *folder_name, CamelException *ex)
+{
+ CamelFolderInfo *fi;
+ CamelStoreInfo *si;
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ if (si) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ if (imap_store->renaming) {
+ /* we don't need to emit a "folder_unsubscribed" signal
+ if we are in the process of renaming folders, so we
+ are done here... */
+ return;
+
+ }
+
+ fi = imap_build_folder_info(imap_store, folder_name);
+ camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_unsubscribed", fi);
+ camel_folder_info_free (fi);
+}
+
+static void
+imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelException *ex)
+{
+ CamelFolderSummary *summary;
+ CamelImapMessageCache *cache;
+ char *summary_file, *state_file;
+ char *journal_file;
+ char *folder_dir, *storage_path;
+ CamelFolderInfo *fi;
+ const char *name;
+
+ name = strrchr (folder_name, imap_store->dir_sep);
+ if (name)
+ name++;
+ else
+ name = folder_name;
+
+ storage_path = g_strdup_printf ("%s/folders", imap_store->storage_path);
+ folder_dir = e_path_to_physical (storage_path, folder_name);
+ g_free (storage_path);
+ if (access (folder_dir, F_OK) != 0) {
+ g_free (folder_dir);
+ goto event;
+ }
+
+ summary_file = g_strdup_printf ("%s/summary", folder_dir);
+ summary = camel_imap_summary_new (summary_file);
+ if (!summary) {
+ g_free (summary_file);
+ g_free (folder_dir);
+ goto event;
+ }
+
+ cache = camel_imap_message_cache_new (folder_dir, summary, ex);
+ if (cache)
+ camel_imap_message_cache_clear (cache);
+
+ camel_object_unref (cache);
+ camel_object_unref (summary);
+
+ unlink (summary_file);
+ g_free (summary_file);
+
+ journal_file = g_strdup_printf ("%s/journal", folder_dir);
+ unlink (journal_file);
+ g_free (journal_file);
+
+ state_file = g_strdup_printf ("%s/cmeta", folder_dir);
+ unlink (state_file);
+ g_free (state_file);
+
+ rmdir (folder_dir);
+ g_free (folder_dir);
+
+ event:
+
+ camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+
+ fi = imap_build_folder_info(imap_store, folder_name);
+ camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_deleted", fi);
+ camel_folder_info_free (fi);
+}
+
+static gboolean
+imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name,
+ CamelException *ex)
+{
+ CamelImapResponse *response;
+
+ response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %F",
+ full_name);
+
+ if (response) {
+ gboolean stillthere = response->untagged->len != 0;
+
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ return stillthere;
+ }
+
+ /* if the command was rejected, there must be some other error,
+ assume it worked so we dont blow away the folder unecessarily */
+ return TRUE;
+}
+
+/* This is a little 'hack' to avoid the deadlock conditions that would otherwise
+ ensue when calling camel_folder_refresh_info from inside a lock */
+/* NB: on second thougts this is probably not entirely safe, but it'll do for now */
+/* No, its definetly not safe. So its been changed to copy the folders first */
+/* the alternative is to:
+ make the camel folder->lock recursive (which should probably be done)
+ or remove it from camel_folder_refresh_info, and use another locking mechanism */
+/* also see get_folder_info_online() for the same hack repeated */
+static void
+imap_store_refresh_folders (CamelImapStore *store, CamelException *ex)
+{
+ GPtrArray *folders;
+ int i;
+
+ folders = camel_object_bag_list(CAMEL_STORE (store)->folders);
+
+ for (i = 0; i <folders->len; i++) {
+ CamelFolder *folder = folders->pdata[i];
+
+ /* NB: we can have vtrash folders also in our store ... bit hacky */
+ if (!CAMEL_IS_IMAP_FOLDER(folder)) {
+ camel_object_unref(folder);
+ continue;
+ }
+
+ CAMEL_IMAP_FOLDER (folder)->need_rescan = TRUE;
+ if (!camel_exception_is_set(ex))
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
+
+ if (camel_exception_is_set (ex) &&
+ imap_check_folder_still_extant (store, folder->full_name, ex) == FALSE) {
+ gchar *namedup;
+
+ /* the folder was deleted (may happen when we come back online
+ * after being offline */
+
+ namedup = g_strdup (folder->full_name);
+ camel_object_unref(folder);
+ imap_folder_effectively_unsubscribed (store, namedup, ex);
+ imap_forget_folder (store, namedup, ex);
+ g_free (namedup);
+ } else
+ camel_object_unref(folder);
+ }
+
+ g_ptr_array_free (folders, TRUE);
+}
+
+static gboolean
+try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
+{
+ CamelSasl *sasl;
+ CamelImapResponse *response;
+ char *resp;
+ char *sasl_resp;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+
+ response = camel_imap_command (store, NULL, ex, "AUTHENTICATE %s", mech);
+ if (!response)
+ return FALSE;
+
+ sasl = camel_sasl_new ("imap", mech, CAMEL_SERVICE (store));
+ while (!camel_sasl_authenticated (sasl)) {
+ resp = camel_imap_response_extract_continuation (store, response, ex);
+ if (!resp)
+ goto lose;
+
+ sasl_resp = camel_sasl_challenge_base64 (sasl, imap_next_word (resp), ex);
+ g_free (resp);
+ if (camel_exception_is_set (ex))
+ goto break_and_lose;
+
+ response = camel_imap_command_continuation (store, sasl_resp, strlen (sasl_resp), ex);
+ g_free (sasl_resp);
+ if (!response)
+ goto lose;
+ }
+
+ resp = camel_imap_response_extract_continuation (store, response, NULL);
+ if (resp) {
+ /* Oops. SASL claims we're done, but the IMAP server
+ * doesn't think so...
+ */
+ g_free (resp);
+ goto lose;
+ }
+
+ camel_object_unref (sasl);
+
+ return TRUE;
+
+ break_and_lose:
+ /* Get the server out of "waiting for continuation data" mode. */
+ response = camel_imap_command_continuation (store, "*", 1, NULL);
+ if (response)
+ camel_imap_response_free (store, response);
+
+ lose:
+ if (!camel_exception_is_set (ex)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ }
+
+ camel_object_unref (sasl);
+
+ return FALSE;
+}
+
+static gboolean
+imap_auth_loop (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelSession *session = camel_service_get_session (service);
+ CamelServiceAuthType *authtype = NULL;
+ CamelImapResponse *response;
+ char *errbuf = NULL;
+ gboolean authenticated = FALSE;
+ const char *auth_domain;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+ auth_domain = camel_url_get_param (service->url, "auth-domain");
+
+ if (store->preauthed) {
+ if (camel_verbose_debug)
+ fprintf(stderr, "Server %s has preauthenticated us.\n",
+ service->url->host);
+ return TRUE;
+ }
+
+ if (service->url->authmech) {
+ if (!g_hash_table_lookup (store->authtypes, service->url->authmech)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("IMAP server %s does not support requested "
+ "authentication type %s"),
+ service->url->host,
+ service->url->authmech);
+ return FALSE;
+ }
+
+ authtype = camel_sasl_authtype (service->url->authmech);
+ if (!authtype) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("No support for authentication type %s"),
+ service->url->authmech);
+ return FALSE;
+ }
+
+ if (!authtype->need_password) {
+ authenticated = try_auth (store, authtype->authproto, ex);
+ if (!authenticated)
+ return FALSE;
+ }
+ }
+
+ while (!authenticated) {
+ if (errbuf) {
+ /* We need to un-cache the password before prompting again */
+ camel_session_forget_password (session, service, auth_domain, "password", ex);
+ g_free (service->url->passwd);
+ service->url->passwd = NULL;
+ }
+
+ if (!service->url->passwd) {
+ char *prompt;
+
+ prompt = g_strdup_printf (_("%sPlease enter the IMAP "
+ "password for %s@%s"),
+ errbuf ? errbuf : "",
+ service->url->user,
+ service->url->host);
+ service->url->passwd =
+ camel_session_get_password (session, service, auth_domain,
+ prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
+ g_free (prompt);
+ g_free (errbuf);
+ errbuf = NULL;
+
+ if (!service->url->passwd) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("You didn't enter a password."));
+ return FALSE;
+ }
+ }
+
+ if (!store->connected) {
+ /* Some servers (eg, courier) will disconnect on
+ * a bad password. So reconnect here.
+ */
+ if (!connect_to_server_wrapper (service, ex))
+ return FALSE;
+ }
+
+ if (authtype)
+ authenticated = try_auth (store, authtype->authproto, ex);
+ else {
+ response = camel_imap_command (store, NULL, ex,
+ "LOGIN %S %S",
+ service->url->user,
+ service->url->passwd);
+ if (response) {
+ camel_imap_response_free (store, response);
+ authenticated = TRUE;
+ }
+ }
+ if (!authenticated) {
+ if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL)
+ return FALSE;
+
+ errbuf = g_strdup_printf (_("Unable to authenticate "
+ "to IMAP server.\n%s\n\n"),
+ camel_exception_get_description (ex));
+ camel_exception_clear (ex);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+can_work_offline (CamelDiscoStore *disco_store)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (disco_store);
+
+ return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0;
+}
+
+static gboolean
+imap_connect_online (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
+ CamelImapResponse *response;
+ /*struct _namespaces *namespaces;*/
+ char *result, *name, *path;
+ int i;
+ size_t len;
+ CamelImapStoreNamespace *ns;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ if (!connect_to_server_wrapper (service, ex) ||
+ !imap_auth_loop (service, ex)) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+
+ /* Get namespace and hierarchy separator */
+ if ((store->capabilities & IMAP_CAPABILITY_NAMESPACE) &&
+ !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
+ response = camel_imap_command (store, NULL, ex, "NAMESPACE");
+ if (!response)
+ goto done;
+
+ result = camel_imap_response_extract (store, response, "NAMESPACE", ex);
+ if (!result)
+ goto done;
+
+#if 0
+ /* new code... */
+ namespaces = imap_parse_namespace_response (result);
+ imap_namespaces_destroy (namespaces);
+ /* end new code */
+#endif
+
+ name = camel_strstrcase (result, "NAMESPACE ((");
+ if (name) {
+ char *sep;
+
+ name += 12;
+ store->namespace = imap_parse_string ((const char **) &name, &len);
+ if (name && *name++ == ' ') {
+ sep = imap_parse_string ((const char **) &name, &len);
+ if (sep) {
+ store->dir_sep = *sep;
+ g_free (sep);
+ }
+ }
+ }
+ g_free (result);
+ }
+
+ if (!store->namespace)
+ store->namespace = g_strdup ("");
+
+ if (!store->dir_sep) {
+ if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
+ /* This idiom means "tell me the hierarchy separator
+ * for the given path, even if that path doesn't exist.
+ */
+ response = camel_imap_command (store, NULL, ex,
+ "LIST %S \"\"",
+ store->namespace);
+ } else {
+ /* Plain IMAP4 doesn't have that idiom, so we fall back
+ * to "tell me about this folder", which will fail if
+ * the folder doesn't exist (eg, if namespace is "").
+ */
+ response = camel_imap_command (store, NULL, ex,
+ "LIST \"\" %S",
+ store->namespace);
+ }
+ if (!response)
+ goto done;
+
+ result = camel_imap_response_extract (store, response, "LIST", NULL);
+ if (result) {
+ imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL);
+ g_free (result);
+ }
+ if (!store->dir_sep) {
+ store->dir_sep = '/'; /* Guess */
+ }
+ }
+
+ /* canonicalize the namespace to end with dir_sep */
+ len = strlen (store->namespace);
+ if (len && store->namespace[len - 1] != store->dir_sep) {
+ gchar *tmp;
+
+ tmp = g_strdup_printf ("%s%c", store->namespace, store->dir_sep);
+ g_free (store->namespace);
+ store->namespace = tmp;
+ }
+
+ ns = camel_imap_store_summary_namespace_new(store->summary, store->namespace, store->dir_sep);
+ camel_imap_store_summary_namespace_set(store->summary, ns);
+
+ if (CAMEL_STORE (store)->flags & CAMEL_STORE_SUBSCRIPTIONS) {
+ gboolean haveinbox = FALSE;
+ GPtrArray *folders;
+ char *pattern;
+
+ /* this pre-fills the summary, and checks that lsub is useful */
+ folders = g_ptr_array_new ();
+ pattern = g_strdup_printf ("%s*", store->namespace);
+ get_folders_online (store, pattern, folders, TRUE, ex);
+ g_free (pattern);
+
+ for (i = 0; i < folders->len; i++) {
+ CamelFolderInfo *fi = folders->pdata[i];
+
+ haveinbox = haveinbox || !g_ascii_strcasecmp (fi->full_name, "INBOX");
+
+ if (fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED))
+ store->capabilities |= IMAP_CAPABILITY_useful_lsub;
+ camel_folder_info_free (fi);
+ }
+
+ /* if the namespace is under INBOX, check INBOX explicitly */
+ if (!g_ascii_strncasecmp (store->namespace, "INBOX", 5) && !camel_exception_is_set (ex)) {
+ gboolean just_subscribed = FALSE;
+ gboolean need_subscribe = FALSE;
+
+ recheck:
+ g_ptr_array_set_size (folders, 0);
+ get_folders_online (store, "INBOX", folders, TRUE, ex);
+
+ for (i = 0; i < folders->len; i++) {
+ CamelFolderInfo *fi = folders->pdata[i];
+
+ /* this should always be TRUE if folders->len > 0 */
+ if (!g_ascii_strcasecmp (fi->full_name, "INBOX")) {
+ haveinbox = TRUE;
+
+ /* if INBOX is marked as \NoSelect then it is probably
+ because it has not been subscribed to */
+ if (!need_subscribe)
+ need_subscribe = fi->flags & CAMEL_FOLDER_NOSELECT;
+ }
+
+ camel_folder_info_free (fi);
+ }
+
+ need_subscribe = !haveinbox || need_subscribe;
+ if (need_subscribe && !just_subscribed && !camel_exception_is_set (ex)) {
+ /* in order to avoid user complaints, force a subscription to INBOX */
+ response = camel_imap_command (store, NULL, ex, "SUBSCRIBE INBOX");
+ if (response != NULL) {
+ /* force a re-check which will pre-fill the summary and
+ also get any folder flags present on the INBOX */
+ camel_imap_response_free (store, response);
+ just_subscribed = TRUE;
+ goto recheck;
+ }
+ }
+ }
+
+ g_ptr_array_free (folders, TRUE);
+ }
+
+ path = g_strdup_printf ("%s/journal", store->storage_path);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+
+ done:
+ /* save any changes we had */
+ camel_store_summary_save((CamelStoreSummary *)store->summary);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ if (camel_exception_is_set (ex))
+ camel_service_disconnect (service, TRUE, NULL);
+ else if (camel_disco_diary_empty (disco_store->diary))
+ imap_store_refresh_folders (store, ex);
+
+ return !camel_exception_is_set (ex);
+}
+
+static gboolean
+imap_connect_offline (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
+ char *path;
+
+ path = g_strdup_printf ("%s/journal", store->storage_path);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+ if (!disco_store->diary)
+ return FALSE;
+
+ imap_store_refresh_folders (store, ex);
+
+ store->connected = !camel_exception_is_set (ex);
+ return store->connected;
+}
+
+static gboolean
+imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelDiscoStore *disco = CAMEL_DISCO_STORE (service);
+
+ store->connected = FALSE;
+ if (store->current_folder) {
+ camel_object_unref (store->current_folder);
+ store->current_folder = NULL;
+ }
+
+ if (store->authtypes) {
+ g_hash_table_foreach_remove (store->authtypes,
+ free_key, NULL);
+ g_hash_table_destroy (store->authtypes);
+ store->authtypes = NULL;
+ }
+
+ if (store->namespace && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
+ g_free (store->namespace);
+ store->namespace = NULL;
+ }
+
+ if (disco->diary) {
+ camel_object_unref (disco->diary);
+ disco->diary = NULL;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelImapResponse *response;
+
+ if (store->connected && clean) {
+ response = camel_imap_command (store, NULL, NULL, "LOGOUT");
+ camel_imap_response_free (store, response);
+ }
+
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ imap_disconnect_offline (service, clean, ex);
+
+ return TRUE;
+}
+
+
+static gboolean
+imap_summary_is_dirty (CamelFolderSummary *summary)
+{
+ CamelMessageInfo *info;
+ int max, i;
+
+ max = camel_folder_summary_count (summary);
+ for (i = 0; i < max; i++) {
+ info = camel_folder_summary_index (summary, i);
+ if (info && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+imap_noop (CamelStore *store, CamelException *ex)
+{
+ CamelImapStore *imap_store = (CamelImapStore *) store;
+ CamelDiscoStore *disco = (CamelDiscoStore *) store;
+ CamelImapResponse *response;
+ CamelFolder *current_folder;
+
+ if (camel_disco_store_status (disco) != CAMEL_DISCO_STORE_ONLINE)
+ return;
+
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+
+ current_folder = imap_store->current_folder;
+ if (current_folder && imap_summary_is_dirty (current_folder->summary)) {
+ /* let's sync the flags instead. NB: must avoid folder lock */
+ ((CamelFolderClass *)((CamelObject *)current_folder)->klass)->sync(current_folder, FALSE, ex);
+ } else {
+ response = camel_imap_command (imap_store, NULL, ex, "NOOP");
+ if (response)
+ camel_imap_response_free (imap_store, response);
+ }
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+}
+
+static CamelFolder *
+imap_get_trash(CamelStore *store, CamelException *ex)
+{
+ CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_trash(store, ex);
+
+ if (folder) {
+ char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Trash.cmeta", NULL);
+
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
+ g_free(state);
+ /* no defaults? */
+ camel_object_state_read(folder);
+ }
+
+ return folder;
+}
+
+static CamelFolder *
+imap_get_junk(CamelStore *store, CamelException *ex)
+{
+ CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_junk(store, ex);
+
+ if (folder) {
+ char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Junk.cmeta", NULL);
+
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
+ g_free(state);
+ /* no defaults? */
+ camel_object_state_read(folder);
+ }
+
+ return folder;
+}
+
+static guint
+hash_folder_name (gconstpointer key)
+{
+ if (g_ascii_strcasecmp (key, "INBOX") == 0)
+ return g_str_hash ("INBOX");
+ else
+ return g_str_hash (key);
+}
+
+static gint
+compare_folder_name (gconstpointer a, gconstpointer b)
+{
+ gconstpointer aname = a, bname = b;
+
+ if (g_ascii_strcasecmp (a, "INBOX") == 0)
+ aname = "INBOX";
+ if (g_ascii_strcasecmp (b, "INBOX") == 0)
+ bname = "INBOX";
+ return g_str_equal (aname, bname);
+}
+
+struct imap_status_item {
+ struct imap_status_item *next;
+ char *name;
+ guint32 value;
+};
+
+static void
+imap_status_item_free (struct imap_status_item *items)
+{
+ struct imap_status_item *next;
+
+ while (items != NULL) {
+ next = items->next;
+ g_free (items->name);
+ g_free (items);
+ items = next;
+ }
+}
+
+static struct imap_status_item *
+get_folder_status (CamelImapStore *imap_store, const char *folder_name, const char *type)
+{
+ struct imap_status_item *items, *item, *tail;
+ CamelImapResponse *response;
+ char *status, *name, *p;
+
+ /* FIXME: we assume the server is STATUS-capable */
+
+ response = camel_imap_command (imap_store, NULL, NULL,
+ "STATUS %F (%s)",
+ folder_name,
+ type);
+
+ if (!response) {
+ CamelException ex;
+
+ camel_exception_init (&ex);
+ if (imap_check_folder_still_extant (imap_store, folder_name, &ex) == FALSE) {
+ imap_folder_effectively_unsubscribed (imap_store, folder_name, &ex);
+ imap_forget_folder (imap_store, folder_name, &ex);
+ }
+ camel_exception_clear (&ex);
+ return NULL;
+ }
+
+ if (!(status = camel_imap_response_extract (imap_store, response, "STATUS", NULL)))
+ return NULL;
+
+ p = status + strlen ("* STATUS ");
+ while (*p == ' ')
+ p++;
+
+ /* skip past the mailbox string */
+ if (*p == '"') {
+ p++;
+ while (*p != '\0') {
+ if (*p == '"' && p[-1] != '\\') {
+ p++;
+ break;
+ }
+
+ p++;
+ }
+ } else {
+ while (*p != ' ')
+ p++;
+ }
+
+ while (*p == ' ')
+ p++;
+
+ if (*p++ != '(') {
+ g_free (status);
+ return NULL;
+ }
+
+ while (*p == ' ')
+ p++;
+
+ if (*p == ')') {
+ g_free (status);
+ return NULL;
+ }
+
+ items = NULL;
+ tail = (struct imap_status_item *) &items;
+
+ do {
+ name = p;
+ while (*p != ' ')
+ p++;
+
+ item = g_malloc (sizeof (struct imap_status_item));
+ item->next = NULL;
+ item->name = g_strndup (name, p - name);
+ item->value = strtoul (p, &p, 10);
+
+ tail->next = item;
+ tail = item;
+
+ while (*p == ' ')
+ p++;
+ } while (*p != ')');
+
+ g_free (status);
+
+ return items;
+}
+
+static CamelFolder *
+get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+ CamelFolder *new_folder;
+ char *folder_dir, *storage_path;
+
+ if (!camel_imap_store_connected (imap_store, ex))
+ return NULL;
+
+ if (!g_ascii_strcasecmp (folder_name, "INBOX"))
+ folder_name = "INBOX";
+
+ /* Lock around the whole lot to check/create atomically */
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+ if (imap_store->current_folder) {
+ camel_object_unref (imap_store->current_folder);
+ imap_store->current_folder = NULL;
+ }
+ response = camel_imap_command (imap_store, NULL, ex, "SELECT %F", folder_name);
+ if (!response) {
+ char *folder_real, *parent_name, *parent_real;
+ const char *c;
+
+ if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ return NULL;
+ }
+
+ camel_exception_clear (ex);
+
+ if (!(flags & CAMEL_STORE_FOLDER_CREATE)) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("No such folder %s"), folder_name);
+ return NULL;
+ }
+
+ if ((parent_name = strrchr (folder_name, '/'))) {
+ parent_name = g_strndup (folder_name, parent_name - folder_name);
+ parent_real = camel_imap_store_summary_path_to_full (imap_store->summary, parent_name, imap_store->dir_sep);
+ } else {
+ parent_real = NULL;
+ }
+
+ c = parent_name ? parent_name : folder_name;
+ while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
+ c++;
+
+ if (*c != '\0') {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+ _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
+ folder_name, *c);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ if (parent_real != NULL) {
+ gboolean need_convert = FALSE;
+ char *resp, *thisone;
+ guint32 flags;
+ int i;
+
+ if (!(response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S", parent_real))) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ /* FIXME: does not handle unexpected circumstances very well */
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i];
+
+ if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
+ continue;
+
+ if (!strcmp (parent_name, thisone)) {
+ if (flags & CAMEL_FOLDER_NOINFERIORS)
+ need_convert = TRUE;
+ }
+
+ g_free (thisone);
+ }
+
+ camel_imap_response_free (imap_store, response);
+
+ /* if not, check if we can delete it and recreate it */
+ if (need_convert) {
+ struct imap_status_item *items, *item;
+ guint32 messages = 0;
+ CamelException lex;
+ char *name;
+
+ item = items = get_folder_status (imap_store, parent_name, "MESSAGES");
+ while (item != NULL) {
+ if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
+ messages = item->value;
+ break;
+ }
+
+ item = item->next;
+ }
+
+ imap_status_item_free (items);
+
+ if (messages > 0) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
+ _("The parent folder is not allowed to contain subfolders"));
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ /* delete the old parent and recreate it */
+ camel_exception_init (&lex);
+ delete_folder (store, parent_name, &lex);
+ if (camel_exception_is_set (&lex)) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ camel_exception_xfer (ex, &lex);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ /* add the dirsep to the end of parent_name */
+ name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S",
+ name);
+ g_free (name);
+
+ if (!response) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ } else
+ camel_imap_response_free (imap_store, response);
+ }
+
+ g_free (parent_real);
+ }
+
+ g_free (parent_name);
+
+ folder_real = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", folder_real);
+
+ if (response) {
+ camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, imap_store->dir_sep);
+
+ camel_imap_response_free (imap_store, response);
+
+ response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
+ }
+ g_free(folder_real);
+ if (!response) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ return NULL;
+ }
+ } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': folder exists."),
+ folder_name);
+
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+
+ return NULL;
+ }
+
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ folder_dir = e_path_to_physical (storage_path, folder_name);
+ g_free(storage_path);
+ new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
+ g_free (folder_dir);
+ if (new_folder) {
+ CamelException local_ex;
+
+ imap_store->current_folder = new_folder;
+ camel_object_ref (new_folder);
+ camel_exception_init (&local_ex);
+ camel_imap_folder_selected (new_folder, response, &local_ex);
+
+ if (camel_exception_is_set (&local_ex)) {
+ camel_exception_xfer (ex, &local_ex);
+ camel_object_unref (imap_store->current_folder);
+ imap_store->current_folder = NULL;
+ camel_object_unref (new_folder);
+ new_folder = NULL;
+ }
+ }
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+
+ return new_folder;
+}
+
+static CamelFolder *
+get_folder_offline (CamelStore *store, const char *folder_name,
+ guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelFolder *new_folder;
+ char *folder_dir, *storage_path;
+
+ if (!imap_store->connected &&
+ !camel_service_connect (CAMEL_SERVICE (store), ex))
+ return NULL;
+
+ if (!g_ascii_strcasecmp (folder_name, "INBOX"))
+ folder_name = "INBOX";
+
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ folder_dir = e_path_to_physical (storage_path, folder_name);
+ g_free(storage_path);
+ if (!folder_dir || access (folder_dir, F_OK) != 0) {
+ g_free (folder_dir);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("No such folder %s"), folder_name);
+ return NULL;
+ }
+
+ new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
+ g_free (folder_dir);
+
+ return new_folder;
+}
+
+static void
+delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+
+ /* make sure this folder isn't currently SELECTed */
+ response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
+ if (response) {
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+
+ if (imap_store->current_folder)
+ camel_object_unref (imap_store->current_folder);
+ /* no need to actually create a CamelFolder for INBOX */
+ imap_store->current_folder = NULL;
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ } else
+ return;
+
+ response = camel_imap_command (imap_store, NULL, ex, "DELETE %F",
+ folder_name);
+
+ if (response) {
+ camel_imap_response_free (imap_store, response);
+ imap_forget_folder (imap_store, folder_name, ex);
+ }
+}
+
+static void
+manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelStoreInfo *si;
+ int olen = strlen(old_name);
+ const char *path;
+ int i, count;
+
+ count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
+ for (i=0;i<count;i++) {
+ si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
+ if (si) {
+ path = camel_store_info_path(imap_store->summary, si);
+ if (strncmp(path, old_name, olen) == 0) {
+ if (subscribe)
+ subscribe_folder(store, path, NULL);
+ else
+ unsubscribe_folder(store, path, NULL);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+ }
+}
+
+static void
+rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name)
+{
+ int i, count;
+ CamelStoreInfo *si;
+ int olen = strlen(old_name);
+ const char *path;
+ char *npath, *nfull;
+
+ count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
+ for (i=0;i<count;i++) {
+ si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
+ if (si == NULL)
+ continue;
+ path = camel_store_info_path(imap_store->summary, si);
+ if (strncmp(path, old_name, olen) == 0) {
+ if (strlen(path) > olen)
+ npath = g_strdup_printf("%s/%s", new_name, path+olen+1);
+ else
+ npath = g_strdup(new_name);
+ nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep);
+
+ /* workaround for broken server (courier uses '.') that doesn't rename
+ subordinate folders as required by rfc 2060 */
+ if (imap_store->dir_sep == '.') {
+ CamelImapResponse *response;
+
+ response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %S", path, nfull);
+ if (response)
+ camel_imap_response_free (imap_store, response);
+ }
+
+ camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath);
+ camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull);
+
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ g_free(nfull);
+ g_free(npath);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+}
+
+static void
+rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+ char *oldpath, *newpath, *storage_path, *new_name;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+
+ /* make sure this folder isn't currently SELECTed - it's
+ actually possible to rename INBOX but if you do another
+ INBOX will immediately be created by the server */
+ response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
+ if (response) {
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+
+ if (imap_store->current_folder)
+ camel_object_unref (imap_store->current_folder);
+ /* no need to actually create a CamelFolder for INBOX */
+ imap_store->current_folder = NULL;
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ } else
+ return;
+
+ imap_store->renaming = TRUE;
+
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ manage_subscriptions(store, old_name, FALSE);
+
+ new_name = camel_imap_store_summary_path_to_full(imap_store->summary, new_name_in, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %S", old_name, new_name);
+
+ if (!response) {
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ manage_subscriptions(store, old_name, TRUE);
+ g_free(new_name);
+ imap_store->renaming = FALSE;
+ return;
+ }
+
+ camel_imap_response_free (imap_store, response);
+
+ /* rename summary, and handle broken server */
+ rename_folder_info(imap_store, old_name, new_name_in);
+
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ manage_subscriptions(store, new_name_in, TRUE);
+
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ oldpath = e_path_to_physical (storage_path, old_name);
+ newpath = e_path_to_physical (storage_path, new_name_in);
+ g_free(storage_path);
+
+ /* So do we care if this didn't work? Its just a cache? */
+ if (rename (oldpath, newpath) == -1) {
+ g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset",
+ oldpath, newpath, strerror (errno));
+ }
+
+ g_free (oldpath);
+ g_free (newpath);
+ g_free(new_name);
+
+ imap_store->renaming = FALSE;
+}
+
+static CamelFolderInfo *
+create_folder (CamelStore *store, const char *parent_name,
+ const char *folder_name, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ char *full_name, *resp, *thisone, *parent_real, *real_name;
+ CamelImapResponse *response;
+ CamelException internal_ex;
+ CamelFolderInfo *root = NULL;
+ gboolean need_convert;
+ int i = 0, flags;
+ const char *c;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return NULL;
+ if (!parent_name)
+ parent_name = "";
+
+ c = folder_name;
+ while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
+ c++;
+
+ if (*c != '\0') {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+ _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
+ folder_name, *c);
+ return NULL;
+ }
+
+ /* check if the parent allows inferiors */
+
+ /* FIXME: use storesummary directly */
+ parent_real = camel_imap_store_summary_full_from_path(imap_store->summary, parent_name);
+ if (parent_real == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
+ _("Unknown parent folder: %s"), parent_name);
+ return NULL;
+ }
+
+ need_convert = FALSE;
+ response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S",
+ parent_real);
+ if (!response) /* whoa, this is bad */ {
+ g_free(parent_real);
+ return NULL;
+ }
+
+ /* FIXME: does not handle unexpected circumstances very well */
+ for (i = 0; i < response->untagged->len && !need_convert; i++) {
+ resp = response->untagged->pdata[i];
+
+ if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
+ continue;
+
+ if (strcmp (thisone, parent_name) == 0) {
+ if (flags & CAMEL_FOLDER_NOINFERIORS)
+ need_convert = TRUE;
+ }
+
+ g_free(thisone);
+ }
+
+ camel_imap_response_free (imap_store, response);
+
+ camel_exception_init (&internal_ex);
+
+ /* if not, check if we can delete it and recreate it */
+ if (need_convert) {
+ struct imap_status_item *items, *item;
+ guint32 messages = 0;
+ char *name;
+
+ item = items = get_folder_status (imap_store, parent_name, "MESSAGES");
+ while (item != NULL) {
+ if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
+ messages = item->value;
+ break;
+ }
+
+ item = item->next;
+ }
+
+ imap_status_item_free (items);
+
+ if (messages > 0) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
+ _("The parent folder is not allowed to contain subfolders"));
+ g_free(parent_real);
+ return NULL;
+ }
+
+ /* delete the old parent and recreate it */
+ delete_folder (store, parent_name, &internal_ex);
+ if (camel_exception_is_set (&internal_ex)) {
+ camel_exception_xfer (ex, &internal_ex);
+ return NULL;
+ }
+
+ /* add the dirsep to the end of parent_name */
+ name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S",
+ name);
+ g_free (name);
+
+ if (!response) {
+ g_free(parent_real);
+ return NULL;
+ } else
+ camel_imap_response_free (imap_store, response);
+
+ root = imap_build_folder_info(imap_store, parent_name);
+ }
+
+ /* ok now we can create the folder */
+ real_name = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, imap_store->dir_sep);
+ full_name = imap_concat (imap_store, parent_real, real_name);
+ g_free(real_name);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", full_name);
+
+ if (response) {
+ CamelImapStoreInfo *si;
+ CamelFolderInfo *fi;
+
+ camel_imap_response_free (imap_store, response);
+
+ si = camel_imap_store_summary_add_from_full(imap_store->summary, full_name, imap_store->dir_sep);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ fi = imap_build_folder_info(imap_store, camel_store_info_path(imap_store->summary, si));
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+ if (root) {
+ root->child = fi;
+ fi->parent = root;
+ } else {
+ root = fi;
+ }
+ camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
+ } else if (root) {
+ /* need to re-recreate the folder we just deleted */
+ camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
+ camel_folder_info_free(root);
+ root = NULL;
+ }
+
+ g_free (full_name);
+ g_free(parent_real);
+
+ return root;
+}
+
+static CamelFolderInfo *
+parse_list_response_as_folder_info (CamelImapStore *imap_store,
+ const char *response)
+{
+ CamelFolderInfo *fi;
+ int flags;
+ char sep, *dir, *path;
+ CamelURL *url;
+ CamelImapStoreInfo *si;
+ guint32 newflags;
+
+ if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir))
+ return NULL;
+
+ /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */
+
+ si = camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/');
+ g_free(dir);
+ if (si == NULL)
+ return NULL;
+
+ newflags = (si->info.flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) | (flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED);
+ if (si->info.flags != newflags) {
+ si->info.flags = newflags;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ }
+
+ fi = g_new0 (CamelFolderInfo, 1);
+ fi->name = g_strdup(camel_store_info_name(imap_store->summary, si));
+ fi->full_name = g_strdup(camel_store_info_path(imap_store->summary, si));
+ if (!g_ascii_strcasecmp(fi->full_name, "inbox"))
+ flags |= CAMEL_FOLDER_SYSTEM;
+ /* HACK: some servers report noinferiors for all folders (uw-imapd)
+ We just translate this into nochildren, and let the imap layer enforce
+ it. See create folder */
+ if (flags & CAMEL_FOLDER_NOINFERIORS)
+ flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
+ fi->flags = flags;
+
+ url = camel_url_new (imap_store->base_url, NULL);
+ path = alloca(strlen(fi->full_name)+2);
+ sprintf(path, "/%s", fi->full_name);
+ camel_url_set_path(url, path);
+
+ if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0)
+ camel_url_set_param (url, "noselect", "yes");
+ fi->uri = camel_url_to_string (url, 0);
+ camel_url_free (url);
+
+ /* FIXME: redundant */
+ if (flags & CAMEL_IMAP_FOLDER_UNMARKED)
+ fi->unread = -1;
+
+ return fi;
+}
+
+/* returns true if full_name is a sub-folder of top, or is top */
+static int
+imap_is_subfolder(const char *full_name, const char *top)
+{
+ size_t len = strlen(top);
+
+ /* Looks for top being a full-path subset of full_name.
+ Handle IMAP Inbox case insensitively */
+
+ if (g_ascii_strncasecmp(top, "inbox", 5) == 0
+ && (top[5] == 0 || top[5] == '/')
+ && g_ascii_strncasecmp(full_name, "inbox", 5) == 0
+ && (full_name[5] == 0 || full_name[5] == '/')) {
+ full_name += 5;
+ top += 5;
+ len -= 5;
+ }
+
+ return top[0] == 0
+ || (strncmp(full_name, top, len) == 0
+ && (full_name[len] == 0
+ || full_name[len] == '/'));
+}
+
+/* this is used when lsub doesn't provide very useful information */
+static GPtrArray *
+get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelException *ex)
+{
+ GPtrArray *names, *folders;
+ int i;
+ CamelStoreInfo *si;
+ CamelImapResponse *response;
+ CamelFolderInfo *fi;
+ char *result;
+ int haveinbox = FALSE;
+
+ if (camel_debug("imap:folder_info"))
+ printf(" get_subscribed folders\n");
+
+ folders = g_ptr_array_new ();
+ names = g_ptr_array_new ();
+ for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i));i++) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED
+ && imap_is_subfolder(camel_store_info_path(imap_store->summary, si), top)) {
+ g_ptr_array_add(names, (char *)camel_imap_store_info_full_name(imap_store->summary, si));
+ haveinbox = haveinbox || g_ascii_strcasecmp(camel_imap_store_info_full_name(imap_store->summary, si), "INBOX") == 0;
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ if (!haveinbox)
+ g_ptr_array_add (names, "INBOX");
+
+ for (i = 0; i < names->len; i++) {
+ response = camel_imap_command (imap_store, NULL, ex,
+ "LIST \"\" %S",
+ names->pdata[i]);
+ if (!response)
+ break;
+
+ result = camel_imap_response_extract (imap_store, response, "LIST", NULL);
+ if (!result) {
+ camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, names->pdata[i]);
+ g_ptr_array_remove_index_fast (names, i);
+ i--;
+ continue;
+ }
+
+ fi = parse_list_response_as_folder_info (imap_store, result);
+ g_free (result);
+ if (!fi)
+ continue;
+
+ if (!imap_is_subfolder(fi->full_name, top)) {
+ camel_folder_info_free (fi);
+ continue;
+ }
+
+ g_ptr_array_add (folders, fi);
+ }
+
+ g_ptr_array_free (names, TRUE);
+
+ return folders;
+}
+
+static int imap_match_pattern(char dir_sep, const char *pattern, const char *name)
+{
+ char p, n;
+
+ p = *pattern++;
+ n = *name++;
+ while (n && p) {
+ if (n == p) {
+ p = *pattern++;
+ n = *name++;
+ } else if (p == '%') {
+ if (n != dir_sep) {
+ n = *name++;
+ } else {
+ p = *pattern++;
+ }
+ } else if (p == '*') {
+ return TRUE;
+ } else
+ return FALSE;
+ }
+
+ return n == 0 && (p == '%' || p == 0);
+}
+
+static void
+get_folders_online (CamelImapStore *imap_store, const char *pattern,
+ GPtrArray *folders, gboolean lsub, CamelException *ex)
+{
+ CamelImapResponse *response;
+ CamelFolderInfo *fi;
+ char *list;
+ int i, count;
+ GHashTable *present;
+ CamelStoreInfo *si;
+
+ response = camel_imap_command (imap_store, NULL, ex,
+ "%s \"\" %S", lsub ? "LSUB" : "LIST",
+ pattern);
+ if (!response)
+ return;
+
+ present = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i = 0; i < response->untagged->len; i++) {
+ list = response->untagged->pdata[i];
+ fi = parse_list_response_as_folder_info (imap_store, list);
+ if (fi) {
+ g_ptr_array_add(folders, fi);
+ g_hash_table_insert(present, fi->full_name, fi);
+ }
+ }
+ camel_imap_response_free (imap_store, response);
+
+ /* update our summary to match the server */
+ count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
+ for (i=0;i<count;i++) {
+ si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
+ if (si == NULL)
+ continue;
+
+ if (imap_match_pattern(imap_store->dir_sep, pattern, camel_imap_store_info_full_name(imap_store->summary, si))) {
+ if (g_hash_table_lookup(present, camel_store_info_path(imap_store->summary, si)) != NULL) {
+ if (lsub && (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
+ si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ }
+ } else {
+ if (lsub) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ }
+ } else {
+ camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si);
+ count--;
+ i--;
+ }
+ }
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+ g_hash_table_destroy(present);
+}
+
+#if 0
+static void
+dumpfi(CamelFolderInfo *fi)
+{
+ int depth;
+ CamelFolderInfo *n = fi;
+
+ if (fi == NULL)
+ return;
+
+ depth = 0;
+ while (n->parent) {
+ depth++;
+ n = n->parent;
+ }
+
+ while (fi) {
+ printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url);
+ if (fi->child)
+ dumpfi(fi->child);
+ fi = fi->sibling;
+ }
+}
+#endif
+
+static void
+fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
+{
+ CamelFolder *folder;
+
+ fi->unread = -1;
+ fi->total = -1;
+ folder = camel_object_bag_peek(store->folders, fi->full_name);
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ /* we use connect lock for everything, so this should be safe */
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, NULL);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ camel_object_unref(folder);
+ } else {
+ char *storage_path, *folder_dir, *path;
+ CamelFolderSummary *s;
+
+ /* This is a lot of work for one path! */
+ storage_path = g_strdup_printf("%s/folders", ((CamelImapStore *)store)->storage_path);
+ folder_dir = e_path_to_physical(storage_path, fi->full_name);
+ path = g_strdup_printf("%s/summary", folder_dir);
+ s = (CamelFolderSummary *)camel_object_new(camel_imap_summary_get_type());
+ camel_folder_summary_set_build_content(s, TRUE);
+ camel_folder_summary_set_filename(s, path);
+ if (camel_folder_summary_header_load(s) != -1) {
+ fi->unread = s->unread_count;
+ fi->total = s->saved_count;
+ }
+ g_free(storage_path);
+ g_free(folder_dir);
+ g_free(path);
+
+ camel_object_unref(s);
+ }
+}
+
+/* NB: We should have connect_lock at this point */
+static void
+get_folder_counts(CamelImapStore *imap_store, CamelFolderInfo *fi, CamelException *ex)
+{
+ GSList *q;
+ CamelFolder *folder;
+
+ /* non-recursive breath first search */
+
+ q = g_slist_append(NULL, fi);
+
+ while (q) {
+ fi = q->data;
+ q = g_slist_remove_link(q, q);
+
+ while (fi) {
+ /* ignore noselect folders, and check only inbox if we only check inbox */
+ if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0
+ && ( (imap_store->parameters & IMAP_PARAM_CHECK_ALL)
+ || g_ascii_strcasecmp(fi->full_name, "inbox") == 0) ) {
+
+ /* For the current folder, poke it to check for new
+ * messages and then report that number, rather than
+ * doing a STATUS command.
+ */
+ if (imap_store->current_folder && strcmp(imap_store->current_folder->full_name, fi->full_name) == 0) {
+ /* we bypass the folder locking otherwise we can deadlock. we use the command lock for
+ any operations anyway so this is 'safe'. See comment above imap_store_refresh_folders() for info */
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex);
+ fi->unread = camel_folder_get_unread_message_count (imap_store->current_folder);
+ fi->total = camel_folder_get_message_count(imap_store->current_folder);
+ } else {
+ struct imap_status_item *items, *item;
+
+ fi->unread = -1;
+ fi->total = -1;
+
+ item = items = get_folder_status (imap_store, fi->full_name, "MESSAGES UNSEEN");
+ while (item != NULL) {
+ if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
+ fi->total = item->value;
+ } else if (!g_ascii_strcasecmp (item->name, "UNSEEN")) {
+ fi->unread = item->value;
+ }
+
+ item = item->next;
+ }
+
+ imap_status_item_free (items);
+
+ /* if we have this folder open, and the unread count has changed, update */
+ folder = camel_object_bag_peek(CAMEL_STORE(imap_store)->folders, fi->full_name);
+ if (folder) {
+ if (fi->unread != camel_folder_get_unread_message_count(folder)) {
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ }
+ camel_object_unref(folder);
+ }
+ }
+ } else {
+ /* since its cheap, get it if they're open/consult summary file */
+ fill_fi((CamelStore *)imap_store, fi, 0);
+ }
+
+ if (fi->child)
+ q = g_slist_append(q, fi->child);
+ fi = fi->next;
+ }
+ }
+}
+
+/* imap needs to treat inbox case insensitive */
+/* we'll assume the names are normalised already */
+static guint folder_hash(const void *ap)
+{
+ const char *a = ap;
+
+ if (g_ascii_strcasecmp(a, "INBOX") == 0)
+ a = "INBOX";
+
+ return g_str_hash(a);
+}
+
+static int folder_eq(const void *ap, const void *bp)
+{
+ const char *a = ap;
+ const char *b = bp;
+
+ if (g_ascii_strcasecmp(a, "INBOX") == 0)
+ a = "INBOX";
+ if (g_ascii_strcasecmp(b, "INBOX") == 0)
+ b = "INBOX";
+
+ return g_str_equal(a, b);
+}
+
+static GSList *
+get_folders_add_folders(GSList *p, int recurse, GHashTable *infos, GPtrArray *folders, GPtrArray *folders_out)
+{
+ CamelFolderInfo *oldfi, *fi;
+ int i;
+
+ /* This is a nasty mess, because some servers will return
+ broken results from LIST or LSUB if you use '%'. e.g. you
+ may get (many) duplicate names, and worse, names may have
+ conflicting flags. */
+ for (i=0; i<folders->len; i++) {
+ fi = folders->pdata[i];
+ oldfi = g_hash_table_lookup(infos, fi->full_name);
+ if (oldfi == NULL) {
+ d(printf(" new folder '%s'\n", fi->full_name));
+ g_hash_table_insert(infos, fi->full_name, fi);
+ if (recurse)
+ p = g_slist_prepend(p, fi);
+ g_ptr_array_add(folders_out, fi);
+ } else {
+ d(printf(" old folder '%s', old flags %08x new flags %08x\n", fi->full_name, oldfi->flags, fi->flags));
+
+ /* need to special-case noselect, since it also affects the uri */
+ if ((oldfi->flags & CAMEL_FOLDER_NOSELECT) != 0
+ && (fi->flags & CAMEL_FOLDER_NOSELECT) == 0) {
+ g_free(oldfi->uri);
+ oldfi->uri = fi->uri;
+ fi->uri = NULL;
+ }
+
+ /* some flags are anded together, some are or'd */
+
+ oldfi->flags = (oldfi->flags & fi->flags & (CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_NOINFERIORS))
+ | ((oldfi->flags | fi->flags) & ~(CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_NOINFERIORS));
+
+ camel_folder_info_free(fi);
+ }
+ }
+
+ g_ptr_array_set_size(folders, 0);
+
+ return p;
+}
+
+static GPtrArray *
+get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ GSList *q, *p = NULL;
+ GHashTable *infos;
+ int i;
+ GPtrArray *folders, *folders_out;
+ CamelFolderInfo *fi;
+ char *name;
+ int depth = 0;
+ int haveinbox = 0;
+ static int imap_max_depth = 0;
+
+ if (!camel_imap_store_connected (imap_store, ex))
+ return NULL;
+
+ if (camel_debug("imap:folder_info"))
+ printf(" get_folders\n");
+
+ /* allow megalomaniacs to override the max of 10 */
+ if (imap_max_depth == 0) {
+ name = getenv("CAMEL_IMAP_MAX_DEPTH");
+ if (name) {
+ imap_max_depth = atoi (name);
+ imap_max_depth = MIN (MAX (imap_max_depth, 0), 2);
+ } else
+ imap_max_depth = 10;
+ }
+
+ infos = g_hash_table_new(folder_hash, folder_eq);
+
+ /* get starting point & strip trailing '/' */
+ if (top[0] == 0) {
+ if (imap_store->namespace) {
+ top = imap_store->namespace;
+ i = strlen(top)-1;
+ name = g_malloc(i+2);
+ strcpy(name, top);
+ while (i>0 && name[i] == imap_store->dir_sep)
+ name[i--] = 0;
+ } else
+ name = g_strdup("");
+ } else {
+ name = camel_imap_store_summary_full_from_path(imap_store->summary, top);
+ if (name == NULL)
+ name = camel_imap_store_summary_path_to_full(imap_store->summary, top, imap_store->dir_sep);
+ }
+
+ d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE"));
+
+ folders_out = g_ptr_array_new();
+ folders = g_ptr_array_new();
+
+ /* first get working list of names */
+ get_folders_online (imap_store, name[0]?name:"%", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
+ if (camel_exception_is_set(ex))
+ goto fail;
+ for (i=0; i<folders->len && !haveinbox; i++) {
+ fi = folders->pdata[i];
+ haveinbox = (g_ascii_strcasecmp(fi->full_name, "INBOX")) == 0;
+ }
+
+ if (!haveinbox && top == imap_store->namespace) {
+ get_folders_online (imap_store, "INBOX", folders,
+ flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
+
+ if (camel_exception_is_set (ex))
+ goto fail;
+ }
+
+ p = get_folders_add_folders(p, TRUE, infos, folders, folders_out);
+
+ /* p is a reversed list of pending folders for the next level, q is the list of folders for this */
+ while (p) {
+ q = g_slist_reverse(p);
+
+ p = NULL;
+ while (q) {
+ fi = q->data;
+
+ q = g_slist_remove_link(q, q);
+
+ d(printf("Checking parent folder '%s'\n", fi->full_name));
+
+ /* First if we're not recursive mode on the top level, and we know it has or doesn't
+ or can't have children, no need to go further - a bit ugly */
+ if ( top == imap_store->namespace
+ && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
+ && (fi->flags & (CAMEL_FOLDER_CHILDREN|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) != 0) {
+ /* do nothing */
+ d(printf(" not interested in folder right now ...\n"));
+ }
+ /* Otherwise, if this has (or might have) children, scan it */
+ else if ( (fi->flags & (CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0
+ || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) {
+ char *n, *real;
+
+ real = camel_imap_store_summary_full_from_path(imap_store->summary, fi->full_name);
+ n = imap_concat(imap_store, real?real:fi->full_name, "%");
+ get_folders_online(imap_store, n, folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
+ g_free(n);
+ g_free(real);
+
+ if (camel_exception_is_set (ex))
+ goto fail;
+
+ if (folders->len > 0)
+ fi->flags |= CAMEL_FOLDER_CHILDREN;
+
+ p = get_folders_add_folders(p, (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) && depth<imap_max_depth,
+ infos, folders, folders_out);
+ }
+ }
+ depth++;
+ }
+
+ g_ptr_array_free(folders, TRUE);
+ g_hash_table_destroy(infos);
+ g_free(name);
+
+ return folders_out;
+fail:
+ g_ptr_array_free(folders, TRUE);
+ g_ptr_array_free(folders_out, TRUE);
+ g_hash_table_destroy(infos);
+ g_slist_free (p);
+ g_free(name);
+
+ return NULL;
+}
+
+static CamelFolderInfo *
+get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelFolderInfo *tree = NULL;
+ GPtrArray *folders;
+
+ if (top == NULL)
+ top = "";
+
+ if (camel_debug("imap:folder_info"))
+ printf("get folder info online\n");
+
+ CAMEL_SERVICE_LOCK(store, connect_lock);
+
+ if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
+ && !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub)
+ && (imap_store->parameters & IMAP_PARAM_CHECK_ALL))
+ folders = get_subscribed_folders(imap_store, top, ex);
+ else
+ folders = get_folders(store, top, flags, ex);
+
+ if (folders == NULL)
+ goto done;
+
+ tree = camel_folder_info_build(folders, top, '/', TRUE);
+ g_ptr_array_free(folders, TRUE);
+
+ if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST))
+ get_folder_counts(imap_store, tree, ex);
+
+ d(dumpfi(tree));
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+done:
+ CAMEL_SERVICE_UNLOCK(store, connect_lock);
+
+ return tree;
+}
+
+static gboolean
+get_one_folder_offline (const char *physical_path, const char *path, gpointer data)
+{
+ GPtrArray *folders = data;
+ CamelImapStore *imap_store = folders->pdata[0];
+ CamelFolderInfo *fi;
+ CamelStoreInfo *si;
+
+ if (*path != '/')
+ return TRUE;
+
+ /* folder_info_build will insert parent nodes as necessary and mark
+ * them as noselect, which is information we actually don't have at
+ * the moment. So let it do the right thing by bailing out if it's
+ * not a folder we're explicitly interested in.
+ */
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, path+1);
+ if (si) {
+ if ((((CamelStore *)imap_store)->flags & CAMEL_STORE_SUBSCRIPTIONS) == 0
+ || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
+ fi = imap_build_folder_info(imap_store, path+1);
+ fi->flags = si->flags;
+ /* HACK: some servers report noinferiors for all folders (uw-imapd)
+ We just translate this into nochildren, and let the imap layer enforce
+ it. See create folder */
+ if (fi->flags & CAMEL_FOLDER_NOINFERIORS)
+ fi->flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
+
+ if (si->flags & CAMEL_FOLDER_NOSELECT) {
+ CamelURL *url = camel_url_new(fi->uri, NULL);
+
+ camel_url_set_param (url, "noselect", "yes");
+ g_free(fi->uri);
+ fi->uri = camel_url_to_string (url, 0);
+ camel_url_free (url);
+ } else {
+ fill_fi((CamelStore *)imap_store, fi, 0);
+ }
+ g_ptr_array_add (folders, fi);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ return TRUE;
+}
+
+static CamelFolderInfo *
+get_folder_info_offline (CamelStore *store, const char *top,
+ guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelFolderInfo *fi;
+ GPtrArray *folders;
+ char *storage_path;
+
+ if (camel_debug("imap:folder_info"))
+ printf("get folder info offline\n");
+
+ if (!imap_store->connected &&
+ !camel_service_connect (CAMEL_SERVICE (store), ex))
+ return NULL;
+
+ if ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) &&
+ !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)) {
+ camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex);
+ return NULL;
+ }
+
+ /* FIXME: obey other flags */
+
+ folders = g_ptr_array_new ();
+
+ /* A kludge to avoid having to pass a struct to the callback */
+ g_ptr_array_add (folders, imap_store);
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ if (!e_path_find_folders (storage_path, get_one_folder_offline, folders)) {
+ camel_disco_store_check_online (CAMEL_DISCO_STORE (imap_store), ex);
+ fi = NULL;
+ } else {
+ g_ptr_array_remove_index_fast (folders, 0);
+ fi = camel_folder_info_build (folders, "", '/', TRUE);
+ }
+ g_free(storage_path);
+
+ g_ptr_array_free (folders, TRUE);
+ return fi;
+}
+
+static gboolean
+folder_subscribed (CamelStore *store, const char *folder_name)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelStoreInfo *si;
+ int truth = FALSE;
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ if (si) {
+ truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ return truth;
+}
+
+/* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */
+static void
+subscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+ CamelFolderInfo *fi;
+ CamelStoreInfo *si;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+ if (!camel_imap_store_connected (imap_store, ex))
+ return;
+
+ response = camel_imap_command (imap_store, NULL, ex,
+ "SUBSCRIBE %F", folder_name);
+ if (!response)
+ return;
+ camel_imap_response_free (imap_store, response);
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ if (si) {
+ if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
+ si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ if (imap_store->renaming) {
+ /* we don't need to emit a "folder_subscribed" signal
+ if we are in the process of renaming folders, so we
+ are done here... */
+ return;
+ }
+
+ fi = imap_build_folder_info(imap_store, folder_name);
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+
+ camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi);
+ camel_folder_info_free (fi);
+}
+
+static void
+unsubscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+ if (!camel_imap_store_connected (imap_store, ex))
+ return;
+
+ response = camel_imap_command (imap_store, NULL, ex,
+ "UNSUBSCRIBE %F", folder_name);
+ if (!response)
+ return;
+ camel_imap_response_free (imap_store, response);
+
+ imap_folder_effectively_unsubscribed (imap_store, folder_name, ex);
+}
+
+#if 0
+static gboolean
+folder_flags_have_changed (CamelFolder *folder)
+{
+ CamelMessageInfo *info;
+ int i, max;
+
+ max = camel_folder_summary_count (folder->summary);
+ for (i = 0; i < max; i++) {
+ info = camel_folder_summary_index (folder->summary, i);
+ if (!info)
+ continue;
+ if (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+#endif
+
+
+gboolean
+camel_imap_store_connected (CamelImapStore *store, CamelException *ex)
+{
+ if (store->istream == NULL || !store->connected)
+ return camel_service_connect (CAMEL_SERVICE (store), ex);
+ return TRUE;
+}
+
+
+/* FIXME: please god, when will the hurting stop? Thus function is so
+ fucking broken it's not even funny. */
+ssize_t
+camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *ex)
+{
+ CamelStreamBuffer *stream;
+ char linebuf[1024];
+ GByteArray *ba;
+ ssize_t nread;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1);
+ g_return_val_if_fail (dest, -1);
+
+ *dest = NULL;
+
+ /* Check for connectedness. Failed (or cancelled) operations will
+ * close the connection. We can't expect a read to have any
+ * meaning if we reconnect, so always set an exception.
+ */
+
+ if (!camel_imap_store_connected (store, ex))
+ return -1;
+
+ stream = CAMEL_STREAM_BUFFER (store->istream);
+
+ ba = g_byte_array_new ();
+ while ((nread = camel_stream_buffer_gets (stream, linebuf, sizeof (linebuf))) > 0) {
+ g_byte_array_append (ba, linebuf, nread);
+ if (linebuf[nread - 1] == '\n')
+ break;
+ }
+
+ if (nread <= 0) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Server unexpectedly disconnected: %s"),
+ g_strerror (errno));
+
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ g_byte_array_free (ba, TRUE);
+ return -1;
+ }
+
+ if (camel_verbose_debug) {
+ fprintf (stderr, "received: ");
+ fwrite (ba->data, 1, ba->len, stderr);
+ }
+
+ /* camel-imap-command.c:imap_read_untagged expects the CRLFs
+ to be stripped off and be nul-terminated *sigh* */
+ nread = ba->len - 1;
+ ba->data[nread] = '\0';
+ if (ba->data[nread - 1] == '\r') {
+ ba->data[nread - 1] = '\0';
+ nread--;
+ }
+
+ *dest = ba->data;
+ g_byte_array_free (ba, FALSE);
+
+ return nread;
+}
diff --git a/camel/providers/imap4/camel-imap4-engine.c b/camel/providers/imap4/camel-imap4-engine.c
new file mode 100644
index 0000000000..adbf862747
--- /dev/null
+++ b/camel/providers/imap4/camel-imap4-engine.c
@@ -0,0 +1,1550 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <camel/camel-sasl.h>
+#include <camel/camel-stream-buffer.h>
+
+#include "camel-imap4-summary.h"
+#include "camel-imap4-command.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-utils.h"
+
+#include "camel-imap4-engine.h"
+
+#define d(x) x
+
+
+static void camel_imap4_engine_class_init (CamelIMAP4EngineClass *klass);
+static void camel_imap4_engine_init (CamelIMAP4Engine *engine, CamelIMAP4EngineClass *klass);
+static void camel_imap4_engine_finalize (CamelObject *object);
+
+
+static CamelObjectClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_engine_get_type (void)
+{
+ static CamelType type = 0;
+
+ if (!type) {
+ type = camel_type_register (camel_object_get_type (),
+ "CamelIMAP4Engine",
+ sizeof (CamelIMAP4Engine),
+ sizeof (CamelIMAP4EngineClass),
+ (CamelObjectClassInitFunc) camel_imap4_engine_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap4_engine_init,
+ (CamelObjectFinalizeFunc) camel_imap4_engine_finalize);
+ }
+
+ return type;
+}
+
+static void
+camel_imap4_engine_class_init (CamelIMAP4EngineClass *klass)
+{
+ parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
+
+ klass->tagprefix = 'A';
+}
+
+static void
+camel_imap4_engine_init (CamelIMAP4Engine *engine, CamelIMAP4EngineClass *klass)
+{
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+ engine->level = CAMEL_IMAP4_LEVEL_UNKNOWN;
+
+ engine->session = NULL;
+ engine->service = NULL;
+ engine->url = NULL;
+
+ engine->istream = NULL;
+ engine->ostream = NULL;
+
+ engine->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
+
+ engine->capa = 0;
+
+ /* this is the suggested default, impacts the max command line length we'll send */
+ engine->maxlentype = CAMEL_IMAP4_ENGINE_MAXLEN_LINE;
+ engine->maxlen = 1000;
+
+ engine->namespaces.personal = NULL;
+ engine->namespaces.other = NULL;
+ engine->namespaces.shared = NULL;
+
+ if (klass->tagprefix > 'Z')
+ klass->tagprefix = 'A';
+
+ engine->tagprefix = klass->tagprefix++;
+ engine->tag = 0;
+
+ engine->nextid = 1;
+
+ engine->folder = NULL;
+
+ e_dlist_init (&engine->queue);
+}
+
+static void
+imap4_namespace_clear (CamelIMAP4Namespace **namespace)
+{
+ CamelIMAP4Namespace *node, *next;
+
+ node = *namespace;
+ while (node != NULL) {
+ next = node->next;
+ g_free (node->path);
+ g_free (node);
+ node = next;
+ }
+
+ *namespace = NULL;
+}
+
+static void
+camel_imap4_engine_finalize (CamelObject *object)
+{
+ CamelIMAP4Engine *engine = (CamelIMAP4Engine *) object;
+ EDListNode *node;
+
+ if (engine->istream)
+ camel_object_unref (engine->istream);
+
+ if (engine->ostream)
+ camel_object_unref (engine->ostream);
+
+ g_hash_table_foreach (engine->authtypes, (GHFunc) g_free, NULL);
+ g_hash_table_destroy (engine->authtypes);
+
+ imap4_namespace_clear (&engine->namespaces.personal);
+ imap4_namespace_clear (&engine->namespaces.other);
+ imap4_namespace_clear (&engine->namespaces.shared);
+
+ if (engine->folder)
+ camel_object_unref (engine->folder);
+
+ while ((node = e_dlist_remhead (&engine->queue))) {
+ node->next = NULL;
+ node->prev = NULL;
+
+ camel_imap4_command_unref ((CamelIMAP4Command *) node);
+ }
+}
+
+
+/**
+ * camel_imap4_engine_new:
+ * @service: service
+ *
+ * Returns a new imap4 engine
+ **/
+CamelIMAP4Engine *
+camel_imap4_engine_new (CamelService *service, CamelIMAP4ReconnectFunc reconnect)
+{
+ CamelIMAP4Engine *engine;
+
+ g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+
+ engine = (CamelIMAP4Engine *) camel_object_new (CAMEL_TYPE_IMAP4_ENGINE);
+ engine->session = service->session;
+ engine->url = service->url;
+ engine->service = service;
+ engine->reconnect = reconnect;
+
+ return engine;
+}
+
+
+/**
+ * camel_imap4_engine_take_stream:
+ * @engine: imap4 engine
+ * @stream: tcp stream
+ * @ex: exception
+ *
+ * Gives ownership of @stream to @engine and reads the greeting from
+ * the stream.
+ *
+ * Returns 0 on success or -1 on fail.
+ *
+ * Note: on error, @stream will be unref'd.
+ **/
+int
+camel_imap4_engine_take_stream (CamelIMAP4Engine *engine, CamelStream *stream, CamelException *ex)
+{
+ camel_imap4_token_t token;
+ int code;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), -1);
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+
+ if (engine->istream)
+ camel_object_unref (engine->istream);
+
+ if (engine->ostream)
+ camel_object_unref (engine->ostream);
+
+ engine->istream = (CamelIMAP4Stream *) camel_imap4_stream_new (stream);
+ engine->ostream = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_WRITE);
+ engine->state = CAMEL_IMAP4_ENGINE_CONNECTED;
+ camel_object_unref (stream);
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != '*') {
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if ((code = camel_imap4_engine_handle_untagged_1 (engine, &token, ex)) == -1) {
+ goto exception;
+ } else if (code != CAMEL_IMAP4_UNTAGGED_OK && code != CAMEL_IMAP4_UNTAGGED_PREAUTH) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Unexpected greeting from IMAP server %s."),
+ engine->url->host);
+ goto exception;
+ }
+
+ return 0;
+
+ exception:
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+
+ camel_object_unref (engine->istream);
+ engine->istream = NULL;
+ camel_object_unref (engine->ostream);
+ engine->ostream = NULL;
+
+ return -1;
+}
+
+
+/**
+ * camel_imap4_engine_capability:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Forces the IMAP4 engine to query the IMAP4 server for a list of capabilities.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_capability (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ CamelIMAP4Command *ic;
+ int id, retval = 0;
+
+ ic = camel_imap4_engine_prequeue (engine, NULL, "CAPABILITY\r\n");
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ retval = -1;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return retval;
+}
+
+
+/**
+ * camel_imap4_engine_namespace:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Forces the IMAP4 engine to query the IMAP4 server for a list of namespaces.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_namespace (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ camel_imap4_list_t *list;
+ GPtrArray *array = NULL;
+ CamelIMAP4Command *ic;
+ int id, i;
+
+ if (engine->capa & CAMEL_IMAP4_CAPABILITY_NAMESPACE) {
+ ic = camel_imap4_engine_prequeue (engine, NULL, "NAMESPACE\r\n");
+ } else {
+ ic = camel_imap4_engine_prequeue (engine, NULL, "LIST \"\" \"\"\r\n");
+ camel_imap4_command_register_untagged (ic, "LIST", camel_imap4_untagged_list);
+ ic->user_data = array = g_ptr_array_new ();
+ }
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+
+ if (array != NULL)
+ g_ptr_array_free (array, TRUE);
+
+ return -1;
+ }
+
+ if (array != NULL) {
+ if (ic->result == CAMEL_IMAP4_RESULT_OK) {
+ CamelIMAP4Namespace *namespace;
+
+ g_assert (array->len == 1);
+ list = array->pdata[0];
+
+ namespace = g_new (CamelIMAP4Namespace, 1);
+ namespace->next = NULL;
+ namespace->path = g_strdup ("");
+ namespace->sep = list->delim;
+
+ engine->namespaces.personal = namespace;
+ } else {
+ /* should never *ever* happen */
+ }
+
+ for (i = 0; i < array->len; i++) {
+ list = array->pdata[i];
+ g_free (list->name);
+ g_free (list);
+ }
+
+ g_ptr_array_free (array, TRUE);
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return 0;
+}
+
+
+int
+camel_imap4_engine_select_folder (CamelIMAP4Engine *engine, CamelFolder *folder, CamelException *ex)
+{
+ CamelIMAP4RespCode *resp;
+ CamelIMAP4Command *ic;
+ int id, retval = 0;
+ int i;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), -1);
+ g_return_val_if_fail (CAMEL_IS_IMAP4_FOLDER (folder), -1);
+
+ /* POSSIBLE FIXME: if the folder to be selected will already
+ * be selected by the time the queue is emptied, simply
+ * no-op? */
+
+ ic = camel_imap4_engine_queue (engine, folder, "SELECT %F\r\n", folder);
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+ return -1;
+ }
+
+ switch (ic->result) {
+ case CAMEL_IMAP4_RESULT_OK:
+ /*folder->mode = 0;*/
+ for (i = 0; i < ic->resp_codes->len; i++) {
+ resp = ic->resp_codes->pdata[i];
+ switch (resp->code) {
+ case CAMEL_IMAP4_RESP_CODE_PERM_FLAGS:
+ folder->permanent_flags = resp->v.flags;
+ break;
+ case CAMEL_IMAP4_RESP_CODE_READONLY:
+ /*folder->mode = CAMEL_FOLDER_MODE_READ_ONLY;*/
+ break;
+ case CAMEL_IMAP4_RESP_CODE_READWRITE:
+ /*folder->mode = CAMEL_FOLDER_MODE_READ_WRITE;*/
+ break;
+ case CAMEL_IMAP4_RESP_CODE_UIDNEXT:
+ camel_imap4_summary_set_uidnext (folder->summary, resp->v.uidnext);
+ break;
+ case CAMEL_IMAP4_RESP_CODE_UIDVALIDITY:
+ camel_imap4_summary_set_uidvalidity (folder->summary, resp->v.uidvalidity);
+ break;
+ case CAMEL_IMAP4_RESP_CODE_UNSEEN:
+ camel_imap4_summary_set_unseen (folder->summary, resp->v.unseen);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*if (folder->mode == 0) {
+ folder->mode = CAMEL_FOLDER_MODE_READ_ONLY;
+ g_warning ("Expected to find [READ-ONLY] or [READ-WRITE] in SELECT response");
+ }*/
+
+ break;
+ case CAMEL_IMAP4_RESULT_NO:
+ /* FIXME: would be good to save the NO reason into the err message */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot select folder `%s': Invalid mailbox name"),
+ folder->full_name);
+ retval = -1;
+ break;
+ case CAMEL_IMAP4_RESULT_BAD:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot select folder `%s': Bad command"),
+ folder->full_name);
+ retval = -1;
+ break;
+ default:
+ g_assert_not_reached ();
+ retval = -1;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return retval;
+}
+
+
+static struct {
+ const char *name;
+ guint32 flag;
+} imap4_capabilities[] = {
+ { "IMAP4", CAMEL_IMAP4_CAPABILITY_IMAP4 },
+ { "IMAP4REV1", CAMEL_IMAP4_CAPABILITY_IMAP4REV1 },
+ { "STATUS", CAMEL_IMAP4_CAPABILITY_STATUS },
+ { "NAMESPACE", CAMEL_IMAP4_CAPABILITY_NAMESPACE },
+ { "UIDPLUS", CAMEL_IMAP4_CAPABILITY_UIDPLUS },
+ { "LITERAL+", CAMEL_IMAP4_CAPABILITY_LITERALPLUS },
+ { "LOGINDISABLED", CAMEL_IMAP4_CAPABILITY_LOGINDISABLED },
+ { "STARTTLS", CAMEL_IMAP4_CAPABILITY_STARTTLS },
+ { NULL, 0 }
+};
+
+static gboolean
+auth_free (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+ return TRUE;
+}
+
+static int
+engine_parse_capability (CamelIMAP4Engine *engine, int sentinel, CamelException *ex)
+{
+ camel_imap4_token_t token;
+ int i;
+
+ engine->capa = CAMEL_IMAP4_CAPABILITY_utf8_search;
+ engine->level = 0;
+
+ g_hash_table_foreach_remove (engine->authtypes, (GHRFunc) auth_free, NULL);
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ while (token.token == CAMEL_IMAP4_TOKEN_ATOM) {
+ if (!strncasecmp ("AUTH=", token.v.atom, 5)) {
+ CamelServiceAuthType *auth;
+
+ if ((auth = camel_sasl_authtype (token.v.atom + 5)) != NULL)
+ g_hash_table_insert (engine->authtypes, g_strdup (token.v.atom + 5), auth);
+ } else {
+ for (i = 0; imap4_capabilities[i].name; i++) {
+ if (!g_ascii_strcasecmp (imap4_capabilities[i].name, token.v.atom))
+ engine->capa |= imap4_capabilities[i].flag;
+ }
+ }
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+ }
+
+ if (token.token != sentinel) {
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ /* unget our sentinel token */
+ camel_imap4_stream_unget_token (engine->istream, &token);
+
+ /* figure out which version of IMAP4 we are dealing with */
+ if (engine->capa & CAMEL_IMAP4_CAPABILITY_IMAP4REV1) {
+ engine->level = CAMEL_IMAP4_LEVEL_IMAP4REV1;
+ engine->capa |= CAMEL_IMAP4_CAPABILITY_STATUS;
+ } else if (engine->capa & CAMEL_IMAP4_CAPABILITY_IMAP4) {
+ engine->level = CAMEL_IMAP4_LEVEL_IMAP4;
+ } else {
+ engine->level = CAMEL_IMAP4_LEVEL_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int
+engine_parse_flags_list (CamelIMAP4Engine *engine, CamelIMAP4RespCode *resp, int perm, CamelException *ex)
+{
+ guint32 flags = 0;
+
+ if (camel_imap4_parse_flags_list (engine, &flags, ex) == -1)
+ return-1;
+
+ if (resp != NULL)
+ resp->v.flags = flags;
+
+ if (engine->current && engine->current->folder) {
+ if (perm)
+ ((CamelFolder *) engine->current->folder)->permanent_flags = flags;
+ /*else
+ ((CamelFolder *) engine->current->folder)->folder_flags = flags;*/
+ } else if (engine->folder) {
+ if (perm)
+ ((CamelFolder *) engine->folder)->permanent_flags = flags;
+ /*else
+ ((CamelFolder *) engine->folder)->folder_flags = flags;*/
+ } else {
+ fprintf (stderr, "We seem to be in a bit of a pickle. we've just parsed an untagged %s\n"
+ "response for a folder, yet we do not currently have a folder selected?\n",
+ perm ? "PERMANENTFLAGS" : "FLAGS");
+ }
+
+ return 0;
+}
+
+static int
+engine_parse_flags (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ camel_imap4_token_t token;
+
+ if (engine_parse_flags_list (engine, NULL, FALSE, ex) == -1)
+ return -1;
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != '\n') {
+ d(fprintf (stderr, "Expected to find a '\\n' token after the FLAGS response\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+engine_parse_namespace (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ CamelIMAP4Namespace *namespaces[3], *node, *tail;
+ camel_imap4_token_t token;
+ int i, n = 0;
+
+ imap4_namespace_clear (&engine->namespaces.personal);
+ imap4_namespace_clear (&engine->namespaces.other);
+ imap4_namespace_clear (&engine->namespaces.shared);
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ do {
+ namespaces[n] = NULL;
+ tail = (CamelIMAP4Namespace *) &namespaces[n];
+
+ if (token.token == '(') {
+ /* decode the list of namespace pairs */
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ while (token.token == '(') {
+ /* decode a namespace pair */
+
+ /* get the path name token */
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_QSTRING) {
+ d(fprintf (stderr, "Expected to find a qstring token as first element in NAMESPACE pair\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ node = g_new (CamelIMAP4Namespace, 1);
+ node->next = NULL;
+ node->path = g_strdup (token.v.qstring);
+
+ /* get the path delimiter token */
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1) {
+ g_free (node->path);
+ g_free (node);
+
+ goto exception;
+ }
+
+ if (token.token != CAMEL_IMAP4_TOKEN_QSTRING || strlen (token.v.qstring) > 1) {
+ d(fprintf (stderr, "Expected to find a qstring token as second element in NAMESPACE pair\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ g_free (node->path);
+ g_free (node);
+
+ goto exception;
+ }
+
+ node->sep = *token.v.qstring;
+ tail->next = node;
+ tail = node;
+
+ /* canonicalise the namespace path */
+ if (node->path[strlen (node->path) - 1] == node->sep)
+ node->path[strlen (node->path) - 1] = '\0';
+
+ /* canonicalise if this is an INBOX namespace */
+ if (!g_ascii_strncasecmp (node->path, "INBOX", 5) &&
+ (node->path[6] == '\0' || node->path[6] == node->sep))
+ memcpy (node->path, "INBOX", 5);
+
+ /* get the closing ')' for this namespace pair */
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != ')') {
+ d(fprintf (stderr, "Expected to find a ')' token to close the current namespace pair\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+
+ goto exception;
+ }
+
+ /* get the next token (should be either '(' or ')') */
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+ }
+
+ if (token.token != ')') {
+ d(fprintf (stderr, "Expected to find a ')' to close the current namespace list\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+ } else if (token.token == CAMEL_IMAP4_TOKEN_NIL) {
+ /* namespace list is NIL */
+ namespaces[n] = NULL;
+ } else {
+ d(fprintf (stderr, "Expected to find either NIL or '(' token in untagged NAMESPACE response\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ /* get the next token (should be either '(', NIL, or '\n') */
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ n++;
+ } while (n < 3);
+
+ engine->namespaces.personal = namespaces[0];
+ engine->namespaces.other = namespaces[1];
+ engine->namespaces.shared = namespaces[2];
+
+ return 0;
+
+ exception:
+
+ for (i = 0; i <= n; i++)
+ imap4_namespace_clear (&namespaces[i]);
+
+ return -1;
+}
+
+
+/**
+ *
+ * resp-text-code = "ALERT" /
+ * "BADCHARSET" [SP "(" astring *(SP astring) ")" ] /
+ * capability-data / "PARSE" /
+ * "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" /
+ * "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
+ * "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
+ * "UNSEEN" SP nz-number /
+ * atom [SP 1*<any TEXT-CHAR except "]">]
+ **/
+
+static struct {
+ const char *name;
+ camel_imap4_resp_code_t code;
+ int save;
+} imap4_resp_codes[] = {
+ { "ALERT", CAMEL_IMAP4_RESP_CODE_ALERT, 0 },
+ { "BADCHARSET", CAMEL_IMAP4_RESP_CODE_BADCHARSET, 0 },
+ { "CAPABILITY", CAMEL_IMAP4_RESP_CODE_CAPABILITY, 0 },
+ { "PARSE", CAMEL_IMAP4_RESP_CODE_PARSE, 1 },
+ { "PERMANENTFLAGS", CAMEL_IMAP4_RESP_CODE_PERM_FLAGS, 1 },
+ { "READ-ONLY", CAMEL_IMAP4_RESP_CODE_READONLY, 1 },
+ { "READ-WRITE", CAMEL_IMAP4_RESP_CODE_READWRITE, 1 },
+ { "TRYCREATE", CAMEL_IMAP4_RESP_CODE_TRYCREATE, 1 },
+ { "UIDNEXT", CAMEL_IMAP4_RESP_CODE_UIDNEXT, 1 },
+ { "UIDVALIDITY", CAMEL_IMAP4_RESP_CODE_UIDVALIDITY, 1 },
+ { "UNSEEN", CAMEL_IMAP4_RESP_CODE_UNSEEN, 1 },
+ { "NEWNAME", CAMEL_IMAP4_RESP_CODE_NEWNAME, 1 },
+ { "APPENDUID", CAMEL_IMAP4_RESP_CODE_APPENDUID, 1 },
+ { "COPYUID", CAMEL_IMAP4_RESP_CODE_COPYUID, 1 },
+ { NULL, CAMEL_IMAP4_RESP_CODE_UNKNOWN, 0 }
+};
+
+
+int
+camel_imap4_engine_parse_resp_code (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ CamelIMAP4RespCode *resp = NULL;
+ camel_imap4_resp_code_t code;
+ camel_imap4_token_t token;
+ unsigned char *linebuf;
+ size_t len;
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != '[') {
+ d(fprintf (stderr, "Expected a '[' token (followed by a RESP-CODE)\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_ATOM) {
+ d(fprintf (stderr, "Expected an atom token containing a RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ for (code = 0; imap4_resp_codes[code].name; code++) {
+ if (!strcmp (imap4_resp_codes[code].name, token.v.atom)) {
+ if (engine->current && imap4_resp_codes[code].save) {
+ resp = g_new0 (CamelIMAP4RespCode, 1);
+ resp->code = code;
+ }
+ break;
+ }
+ }
+
+ switch (code) {
+ case CAMEL_IMAP4_RESP_CODE_BADCHARSET:
+ /* apparently we don't support UTF-8 afterall */
+ engine->capa &= ~CAMEL_IMAP4_CAPABILITY_utf8_search;
+ break;
+ case CAMEL_IMAP4_RESP_CODE_CAPABILITY:
+ /* capability list follows */
+ if (engine_parse_capability (engine, ']', ex) == -1)
+ goto exception;
+ break;
+ case CAMEL_IMAP4_RESP_CODE_PERM_FLAGS:
+ /* flag list follows */
+ if (engine_parse_flags_list (engine, resp, TRUE, ex) == -1)
+ goto exception;
+ break;
+ case CAMEL_IMAP4_RESP_CODE_READONLY:
+ break;
+ case CAMEL_IMAP4_RESP_CODE_READWRITE:
+ break;
+ case CAMEL_IMAP4_RESP_CODE_TRYCREATE:
+ break;
+ case CAMEL_IMAP4_RESP_CODE_UIDNEXT:
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDNEXT RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.uidnext = token.v.number;
+
+ break;
+ case CAMEL_IMAP4_RESP_CODE_UIDVALIDITY:
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDVALIDITY RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.uidvalidity = token.v.number;
+
+ break;
+ case CAMEL_IMAP4_RESP_CODE_UNSEEN:
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as an argument to the UNSEEN RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.unseen = token.v.number;
+
+ break;
+ case CAMEL_IMAP4_RESP_CODE_NEWNAME:
+ /* this RESP-CODE may actually be removed - see here:
+ * http://www.washington.edu/imap4/listarch/2001/msg00058.html */
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_QSTRING) {
+ d(fprintf (stderr, "Expected an atom or qstring token as the first argument to the NEWNAME RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.newname[0] = g_strdup (token.v.atom);
+
+ if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_QSTRING) {
+ d(fprintf (stderr, "Expected an atom or qstring token as the second argument to the NEWNAME RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.newname[1] = g_strdup (token.v.atom);
+
+ break;
+ case CAMEL_IMAP4_RESP_CODE_APPENDUID:
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as the first argument to the APPENDUID RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.appenduid.uidvalidity = token.v.number;
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as the second argument to the APPENDUID RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.appenduid.uid = token.v.number;
+
+ break;
+ case CAMEL_IMAP4_RESP_CODE_COPYUID:
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as the first argument to the COPYUID RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.copyuid.uidvalidity = token.v.number;
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_ATOM) {
+ d(fprintf (stderr, "Expected an atom token as the second argument to the COPYUID RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.copyuid.srcset = g_strdup (token.v.atom);
+
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP4_TOKEN_ATOM) {
+ d(fprintf (stderr, "Expected an atom token as the third argument to the APPENDUID RESP-CODE\n"));
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.copyuid.destset = g_strdup (token.v.atom);
+
+ break;
+ default:
+ d(fprintf (stderr, "Unknown RESP-CODE encountered: %s\n", token.v.atom));
+
+ /* extensions are of the form: "[" atom [SPACE 1*<any TEXT_CHAR except "]">] "]" */
+
+ /* eat up the TEXT_CHARs */
+ while (token.token != ']' && token.token != '\n') {
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+ }
+
+ break;
+ }
+
+ while (token.token != ']' && token.token != '\n') {
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+ }
+
+ if (token.token != ']') {
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+ d(fprintf (stderr, "Expected to find a ']' token after the RESP-CODE\n"));
+ return -1;
+ }
+
+ if (code == CAMEL_IMAP4_RESP_CODE_ALERT) {
+ if (camel_imap4_engine_line (engine, &linebuf, &len, ex) == -1)
+ goto exception;
+
+ camel_session_alert_user (engine->session, CAMEL_SESSION_ALERT_INFO, linebuf, FALSE);
+ g_free (linebuf);
+ } else if (resp != NULL && code == CAMEL_IMAP4_RESP_CODE_PARSE) {
+ if (camel_imap4_engine_line (engine, &linebuf, &len, ex) == -1)
+ goto exception;
+
+ resp->v.parse = linebuf;
+ } else {
+ /* eat up the rest of the response */
+ if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1)
+ goto exception;
+ }
+
+ if (resp != NULL)
+ g_ptr_array_add (engine->current->resp_codes, resp);
+
+ return 0;
+
+ exception:
+
+ if (resp != NULL)
+ camel_imap4_resp_code_free (resp);
+
+ return -1;
+}
+
+
+
+/* returns -1 on error, or one of CAMEL_IMAP4_UNTAGGED_[OK,NO,BAD,PREAUTH,HANDLED] on success */
+int
+camel_imap4_engine_handle_untagged_1 (CamelIMAP4Engine *engine, camel_imap4_token_t *token, CamelException *ex)
+{
+ int code = CAMEL_IMAP4_UNTAGGED_HANDLED;
+ CamelIMAP4Command *ic = engine->current;
+ CamelIMAP4UntaggedCallback untagged;
+ CamelFolder *folder;
+ unsigned int v;
+
+ if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token == CAMEL_IMAP4_TOKEN_ATOM) {
+ if (!strcmp ("BYE", token->v.atom)) {
+ /* we don't care if we fail here, either way we've been disconnected */
+ if (camel_imap4_engine_next_token (engine, token, NULL) == 0) {
+ if (token->token == '[') {
+ camel_imap4_stream_unget_token (engine->istream, token);
+ camel_imap4_engine_parse_resp_code (engine, NULL);
+ } else {
+ camel_imap4_engine_line (engine, NULL, NULL, NULL);
+ }
+ }
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+
+ /* we don't return -1 here because there may be more untagged responses after the BYE */
+ } else if (!strcmp ("CAPABILITY", token->v.atom)) {
+ /* capability tokens follow */
+ if (engine_parse_capability (engine, '\n', ex) == -1)
+ return -1;
+
+ /* find the eoln token */
+ if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token != '\n') {
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+ return -1;
+ }
+ } else if (!strcmp ("FLAGS", token->v.atom)) {
+ /* flags list follows */
+ if (engine_parse_flags (engine, ex) == -1)
+ return -1;
+ } else if (!strcmp ("NAMESPACE", token->v.atom)) {
+ if (engine_parse_namespace (engine, ex) == -1)
+ return -1;
+ } else if (!strcmp ("NO", token->v.atom) || !strcmp ("BAD", token->v.atom)) {
+ code = !strcmp ("NO", token->v.atom) ? CAMEL_IMAP4_UNTAGGED_NO : CAMEL_IMAP4_UNTAGGED_BAD;
+
+ /* our command has been rejected */
+ if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token == '[') {
+ /* we have a resp code */
+ camel_imap4_stream_unget_token (engine->istream, token);
+ if (camel_imap4_engine_parse_resp_code (engine, ex) == -1)
+ return -1;
+ } else if (token->token != '\n') {
+ /* we just have resp text */
+ if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1)
+ return -1;
+ }
+ } else if (!strcmp ("OK", token->v.atom)) {
+ code = CAMEL_IMAP4_UNTAGGED_OK;
+
+ if (engine->state == CAMEL_IMAP4_ENGINE_CONNECTED) {
+ /* initial server greeting */
+ engine->state = CAMEL_IMAP4_ENGINE_PREAUTH;
+ }
+
+ if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token == '[') {
+ /* we have a resp code */
+ camel_imap4_stream_unget_token (engine->istream, token);
+ if (camel_imap4_engine_parse_resp_code (engine, ex) == -1)
+ return -1;
+ } else {
+ /* we just have resp text */
+ if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1)
+ return -1;
+ }
+ } else if (!strcmp ("PREAUTH", token->v.atom)) {
+ code = CAMEL_IMAP4_UNTAGGED_PREAUTH;
+
+ if (engine->state == CAMEL_IMAP4_ENGINE_CONNECTED)
+ engine->state = CAMEL_IMAP4_ENGINE_AUTHENTICATED;
+
+ if (camel_imap4_engine_parse_resp_code (engine, ex) == -1)
+ return -1;
+ } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) {
+ /* registered untagged handler for imap4 command */
+ if (untagged (engine, ic, 0, token, ex) == -1)
+ return -1;
+ } else {
+ d(fprintf (stderr, "Unhandled atom token in untagged response: %s", token->v.atom));
+
+ if (camel_imap4_engine_eat_line (engine, ex) == -1)
+ return -1;
+ }
+ } else if (token->token == CAMEL_IMAP4_TOKEN_NUMBER) {
+ /* we probably have something like "* 1 EXISTS" */
+ v = token->v.number;
+
+ if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token != CAMEL_IMAP4_TOKEN_ATOM) {
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+
+ return -1;
+ }
+
+ /* which folder is this EXISTS/EXPUNGE/RECENT acting on? */
+ if (engine->current && engine->current->folder)
+ folder = (CamelFolder *) engine->current->folder;
+ else if (engine->folder)
+ folder = (CamelFolder *) engine->folder;
+ else
+ folder = NULL;
+
+ /* NOTE: these can be over-ridden by a registered untagged response handler */
+ if (!strcmp ("EXISTS", token->v.atom)) {
+ camel_imap4_summary_set_exists (folder->summary, v);
+ } else if (!strcmp ("EXPUNGE", token->v.atom)) {
+ camel_imap4_summary_expunge (folder->summary, (int) v);
+ } else if (!strcmp ("RECENT", token->v.atom)) {
+ camel_imap4_summary_set_recent (folder->summary, v);
+ } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) {
+ /* registered untagged handler for imap4 command */
+ if (untagged (engine, ic, v, token, ex) == -1)
+ return -1;
+ } else {
+ d(fprintf (stderr, "Unrecognized untagged response: * %u %s\n", v, token->v.atom));
+ }
+
+ /* find the eoln token */
+ if (camel_imap4_engine_eat_line (engine, ex) == -1)
+ return -1;
+ } else {
+ camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+
+ return -1;
+ }
+
+ return code;
+}
+
+
+void
+camel_imap4_engine_handle_untagged (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ camel_imap4_token_t token;
+
+ g_return_if_fail (CAMEL_IS_IMAP4_ENGINE (engine));
+
+ do {
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != '*')
+ break;
+
+ if (camel_imap4_engine_handle_untagged_1 (engine, &token, ex) == -1)
+ goto exception;
+ } while (1);
+
+ camel_imap4_stream_unget_token (engine->istream, &token);
+
+ return;
+
+ exception:
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+}
+
+
+static int
+imap4_process_command (CamelIMAP4Engine *engine, CamelIMAP4Command *ic)
+{
+ int retval;
+
+ while ((retval = camel_imap4_command_step (ic)) == 0)
+ ;
+
+ if (retval == -1) {
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void
+engine_prequeue_folder_select (CamelIMAP4Engine *engine)
+{
+ CamelIMAP4Command *ic;
+ const char *cmd;
+
+ ic = (CamelIMAP4Command *) engine->queue.head;
+ cmd = (const char *) ic->parts->buffer;
+
+ if (!ic->folder || ic->folder == engine->folder ||
+ !strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) {
+ /* no need to pre-queue a SELECT */
+ return;
+ }
+
+ /* we need to pre-queue a SELECT */
+ ic = camel_imap4_engine_prequeue (engine, (CamelFolder *) ic->folder, "SELECT %F\r\n", ic->folder);
+ ic->user_data = engine;
+
+ camel_imap4_command_unref (ic);
+}
+
+
+static int
+engine_state_change (CamelIMAP4Engine *engine, CamelIMAP4Command *ic)
+{
+ const char *cmd;
+ int retval = 0;
+
+ cmd = ic->parts->buffer;
+ if (!strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) {
+ if (ic->result == CAMEL_IMAP4_RESULT_OK) {
+ /* Update the selected folder */
+ camel_object_ref (ic->folder);
+ if (engine->folder)
+ camel_object_unref (engine->folder);
+ engine->folder = ic->folder;
+
+ engine->state = CAMEL_IMAP4_ENGINE_SELECTED;
+ } else if (ic->user_data == engine) {
+ /* the engine pre-queued this SELECT command */
+ retval = -1;
+ }
+ } else if (!strncmp (cmd, "CLOSE", 5)) {
+ if (ic->result == CAMEL_IMAP4_RESULT_OK)
+ engine->state = CAMEL_IMAP4_ENGINE_AUTHENTICATED;
+ } else if (!strncmp (cmd, "LOGOUT", 6)) {
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+ }
+
+ return retval;
+}
+
+/**
+ * camel_imap4_engine_iterate:
+ * @engine: IMAP4 engine
+ *
+ * Processes the first command in the queue.
+ *
+ * Returns the id of the processed command, 0 if there were no
+ * commands to process, or -1 on error.
+ *
+ * Note: more details on the error will be held on the
+ * CamelIMAP4Command that failed.
+ **/
+int
+camel_imap4_engine_iterate (CamelIMAP4Engine *engine)
+{
+ CamelIMAP4Command *ic, *nic;
+ GPtrArray *resp_codes;
+ int retval = -1;
+
+ if (e_dlist_empty (&engine->queue))
+ return 0;
+
+ /* This sucks... it would be nicer if we didn't have to check the stream's disconnected status */
+ if ((engine->state == CAMEL_IMAP4_ENGINE_DISCONNECTED || engine->istream->disconnected) && !engine->reconnecting) {
+ CamelException rex;
+ gboolean connected;
+
+ camel_exception_init (&rex);
+ engine->reconnecting = TRUE;
+ connected = engine->reconnect (engine, &rex);
+ engine->reconnecting = FALSE;
+
+ if (!connected) {
+ /* pop the first command and act as tho it failed (which, technically, it did...) */
+ ic = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue);
+ ic->status = CAMEL_IMAP4_COMMAND_ERROR;
+ camel_exception_xfer (&ic->ex, &rex);
+ return -1;
+ }
+ }
+
+ /* check to see if we need to pre-queue a SELECT, if so do it */
+ engine_prequeue_folder_select (engine);
+
+ engine->current = ic = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue);
+ ic->status = CAMEL_IMAP4_COMMAND_ACTIVE;
+
+ if (imap4_process_command (engine, ic) != -1) {
+ if (engine_state_change (engine, ic) == -1) {
+ /* This can ONLY happen if @ic was the pre-queued SELECT command
+ * and it got a NO or BAD response.
+ *
+ * We have to pop the next imap4 command or we'll get into an
+ * infinite loop. In order to provide @nic's owner with as much
+ * information as possible, we move all @ic status information
+ * over to @nic and pretend we just processed @nic.
+ **/
+
+ nic = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue);
+
+ nic->status = ic->status;
+ nic->result = ic->result;
+ resp_codes = nic->resp_codes;
+ nic->resp_codes = ic->resp_codes;
+ ic->resp_codes = resp_codes;
+
+ camel_exception_xfer (&nic->ex, &ic->ex);
+
+ camel_imap4_command_unref (ic);
+ ic = nic;
+ }
+
+ retval = ic->id;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return retval;
+}
+
+
+/**
+ * camel_imap4_engine_queue:
+ * @engine: IMAP4 engine
+ * @folder: IMAP4 folder that the command will affect (or %NULL if it doesn't matter)
+ * @format: command format
+ * @Varargs: arguments
+ *
+ * Basically the same as camel_imap4_command_new() except that this
+ * function also places the command in the engine queue.
+ *
+ * Returns the CamelIMAP4Command.
+ **/
+CamelIMAP4Command *
+camel_imap4_engine_queue (CamelIMAP4Engine *engine, CamelFolder *folder, const char *format, ...)
+{
+ CamelIMAP4Command *ic;
+ va_list args;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), NULL);
+
+ va_start (args, format);
+ ic = camel_imap4_command_newv (engine, (CamelIMAP4Folder *) folder, format, args);
+ va_end (args);
+
+ ic->id = engine->nextid++;
+ e_dlist_addtail (&engine->queue, (EDListNode *) ic);
+ camel_imap4_command_ref (ic);
+
+ return ic;
+}
+
+
+/**
+ * camel_imap4_engine_prequeue:
+ * @engine: IMAP4 engine
+ * @folder: IMAP4 folder that the command will affect (or %NULL if it doesn't matter)
+ * @format: command format
+ * @Varargs: arguments
+ *
+ * Same as camel_imap4_engine_queue() except this places the new
+ * command at the head of the queue.
+ *
+ * Returns the CamelIMAP4Command.
+ **/
+CamelIMAP4Command *
+camel_imap4_engine_prequeue (CamelIMAP4Engine *engine, CamelFolder *folder, const char *format, ...)
+{
+ CamelIMAP4Command *ic;
+ va_list args;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), NULL);
+
+ va_start (args, format);
+ ic = camel_imap4_command_newv (engine, (CamelIMAP4Folder *) folder, format, args);
+ va_end (args);
+
+ if (e_dlist_empty (&engine->queue)) {
+ e_dlist_addtail (&engine->queue, (EDListNode *) ic);
+ ic->id = engine->nextid++;
+ } else {
+ CamelIMAP4Command *nic;
+ EDListNode *node;
+
+ node = (EDListNode *) ic;
+ e_dlist_addhead (&engine->queue, node);
+ nic = (CamelIMAP4Command *) node->next;
+ ic->id = nic->id - 1;
+
+ if (ic->id == 0) {
+ /* increment all command ids */
+ node = engine->queue.head;
+ while (node->next) {
+ nic = (CamelIMAP4Command *) node;
+ node = node->next;
+ nic->id++;
+ }
+ }
+ }
+
+ camel_imap4_command_ref (ic);
+
+ return ic;
+}
+
+
+void
+camel_imap4_engine_dequeue (CamelIMAP4Engine *engine, CamelIMAP4Command *ic)
+{
+ EDListNode *node = (EDListNode *) ic;
+
+ if (node->next == NULL && node->prev == NULL)
+ return;
+
+ e_dlist_remove (node);
+ node->next = NULL;
+ node->prev = NULL;
+
+ camel_imap4_command_unref (ic);
+}
+
+
+int
+camel_imap4_engine_next_token (CamelIMAP4Engine *engine, camel_imap4_token_t *token, CamelException *ex)
+{
+ if (camel_imap4_stream_next_token (engine->istream, token) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP4 server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
+camel_imap4_engine_eat_line (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ camel_imap4_token_t token;
+ unsigned char *literal;
+ int retval;
+ size_t n;
+
+ do {
+ if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token == CAMEL_IMAP4_TOKEN_LITERAL) {
+ while ((retval = camel_imap4_stream_literal (engine->istream, &literal, &n)) == 1)
+ ;
+
+ if (retval == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP4 server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+
+ return -1;
+ }
+ }
+ } while (token.token != '\n');
+
+ return 0;
+}
+
+
+int
+camel_imap4_engine_line (CamelIMAP4Engine *engine, unsigned char **line, size_t *len, CamelException *ex)
+{
+ GByteArray *linebuf = NULL;
+ unsigned char *buf;
+ size_t buflen;
+ int retval;
+
+ if (line != NULL)
+ linebuf = g_byte_array_new ();
+
+ while ((retval = camel_imap4_stream_line (engine->istream, &buf, &buflen)) > 0) {
+ if (linebuf != NULL)
+ g_byte_array_append (linebuf, buf, buflen);
+ }
+
+ if (retval == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP4 server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+
+ if (linebuf != NULL)
+ g_byte_array_free (linebuf, TRUE);
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+
+ return -1;
+ }
+
+ if (linebuf != NULL) {
+ g_byte_array_append (linebuf, buf, buflen);
+
+ *line = linebuf->data;
+ *len = linebuf->len;
+
+ g_byte_array_free (linebuf, FALSE);
+ }
+
+ return 0;
+}
+
+
+int
+camel_imap4_engine_literal (CamelIMAP4Engine *engine, unsigned char **literal, size_t *len, CamelException *ex)
+{
+ GByteArray *literalbuf = NULL;
+ unsigned char *buf;
+ size_t buflen;
+ int retval;
+
+ if (literal != NULL)
+ literalbuf = g_byte_array_new ();
+
+ while ((retval = camel_imap4_stream_literal (engine->istream, &buf, &buflen)) > 0) {
+ if (literalbuf != NULL)
+ g_byte_array_append (literalbuf, buf, buflen);
+ }
+
+ if (retval == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP4 server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+
+ if (literalbuf != NULL)
+ g_byte_array_free (literalbuf, TRUE);
+
+ engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+
+ return -1;
+ }
+
+ if (literalbuf != NULL) {
+ g_byte_array_append (literalbuf, buf, buflen);
+ g_byte_array_append (literalbuf, "", 1);
+
+ *literal = literalbuf->data;
+ *len = literalbuf->len - 1;
+
+ g_byte_array_free (literalbuf, FALSE);
+ }
+
+ return 0;
+}
+
+
+void
+camel_imap4_resp_code_free (CamelIMAP4RespCode *rcode)
+{
+ switch (rcode->code) {
+ case CAMEL_IMAP4_RESP_CODE_PARSE:
+ g_free (rcode->v.parse);
+ break;
+ case CAMEL_IMAP4_RESP_CODE_NEWNAME:
+ g_free (rcode->v.newname[0]);
+ g_free (rcode->v.newname[1]);
+ break;
+ case CAMEL_IMAP4_RESP_CODE_COPYUID:
+ g_free (rcode->v.copyuid.srcset);
+ g_free (rcode->v.copyuid.destset);
+ break;
+ default:
+ break;
+ }
+
+ g_free (rcode);
+}
diff --git a/camel/providers/imap4/camel-imap4-store.c b/camel/providers/imap4/camel-imap4-store.c
new file mode 100644
index 0000000000..0d33f43c5d
--- /dev/null
+++ b/camel/providers/imap4/camel-imap4-store.c
@@ -0,0 +1,1391 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <camel/camel-sasl.h>
+#include <camel/camel-utf8.h>
+#include <camel/camel-tcp-stream-raw.h>
+#include <camel/camel-tcp-stream-ssl.h>
+
+#include <camel/camel-private.h>
+
+#include "camel-imap4-store.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-command.h"
+#include "camel-imap4-utils.h"
+#include "camel-imap4-summary.h"
+
+
+static void camel_imap4_store_class_init (CamelIMAP4StoreClass *klass);
+static void camel_imap4_store_init (CamelIMAP4Store *store, CamelIMAP4StoreClass *klass);
+static void camel_imap4_store_finalize (CamelObject *object);
+
+/* service methods */
+static void imap4_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+static char *imap4_get_name (CamelService *service, gboolean brief);
+static gboolean imap4_connect (CamelService *service, CamelException *ex);
+static gboolean imap4_reconnect (CamelIMAP4Engine *engine, CamelException *ex);
+static gboolean imap4_disconnect (CamelService *service, gboolean clean, CamelException *ex);
+static GList *imap4_query_auth_types (CamelService *service, CamelException *ex);
+
+/* store methods */
+static CamelFolder *imap4_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+static CamelFolderInfo *imap4_create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
+static void imap4_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void imap4_rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *imap4_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+static void imap4_subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void imap4_unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void imap4_noop (CamelStore *store, CamelException *ex);
+
+
+static CamelStoreClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_store_get_type (void)
+{
+ static CamelType type = 0;
+
+ if (!type) {
+ type = camel_type_register (CAMEL_STORE_TYPE,
+ "CamelIMAP4Store",
+ sizeof (CamelIMAP4Store),
+ sizeof (CamelIMAP4StoreClass),
+ (CamelObjectClassInitFunc) camel_imap4_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap4_store_init,
+ (CamelObjectFinalizeFunc) camel_imap4_store_finalize);
+ }
+
+ return type;
+}
+
+static guint
+imap4_hash_folder_name (gconstpointer key)
+{
+ if (g_ascii_strcasecmp (key, "INBOX") == 0)
+ return g_str_hash ("INBOX");
+ else
+ return g_str_hash (key);
+}
+
+static int
+imap4_compare_folder_name (gconstpointer a, gconstpointer b)
+{
+ gconstpointer aname = a, bname = b;
+
+ if (g_ascii_strcasecmp (a, "INBOX") == 0)
+ aname = "INBOX";
+ if (g_ascii_strcasecmp (b, "INBOX") == 0)
+ bname = "INBOX";
+
+ return g_str_equal (aname, bname);
+}
+
+static void
+camel_imap4_store_class_init (CamelIMAP4StoreClass *klass)
+{
+ CamelServiceClass *service_class = (CamelServiceClass *) klass;
+ CamelStoreClass *store_class = (CamelStoreClass *) klass;
+
+ parent_class = (CamelStoreClass *) camel_type_get_global_classfuncs (CAMEL_STORE_TYPE);
+
+ service_class->construct = imap4_construct;
+ service_class->get_name = imap4_get_name;
+ service_class->connect = imap4_connect;
+ service_class->disconnect = imap4_disconnect;
+ service_class->query_auth_types = imap4_query_auth_types;
+
+ store_class->hash_folder_name = imap4_hash_folder_name;
+ store_class->compare_folder_name = imap4_compare_folder_name;
+
+ store_class->get_folder = imap4_get_folder;
+ store_class->create_folder = imap4_create_folder;
+ store_class->delete_folder = imap4_delete_folder;
+ store_class->rename_folder = imap4_rename_folder;
+ store_class->get_folder_info = imap4_get_folder_info;
+ store_class->subscribe_folder = imap4_subscribe_folder;
+ store_class->unsubscribe_folder = imap4_unsubscribe_folder;
+ store_class->noop = imap4_noop;
+}
+
+static void
+camel_imap4_store_init (CamelIMAP4Store *store, CamelIMAP4StoreClass *klass)
+{
+ store->engine = NULL;
+}
+
+static void
+camel_imap4_store_finalize (CamelObject *object)
+{
+ CamelIMAP4Store *store = (CamelIMAP4Store *) object;
+
+ if (store->engine)
+ camel_object_unref (store->engine);
+
+ g_free (store->storage_path);
+}
+
+
+static void
+imap4_construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
+{
+ CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ store->storage_path = camel_session_get_storage_path (session, service, ex);
+ store->engine = camel_imap4_engine_new (service, imap4_reconnect);
+}
+
+static char *
+imap4_get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup_printf (_("IMAP server %s"), service->url->host);
+ else
+ return g_strdup_printf (_("IMAP service for %s on %s"),
+ service->url->user, service->url->host);
+}
+
+enum {
+ MODE_CLEAR,
+ MODE_SSL,
+ MODE_TLS,
+};
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+static gboolean
+connect_to_server (CamelIMAP4Engine *engine, struct addrinfo *ai, int ssl_mode, CamelException *ex)
+{
+ CamelService *service = engine->service;
+ CamelStream *tcp_stream;
+ CamelIMAP4Command *ic;
+ int id, ret;
+
+ if (ssl_mode != MODE_CLEAR) {
+#ifdef HAVE_SSL
+ if (ssl_mode == MODE_TLS) {
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, STARTTLS_FLAGS);
+ } else {
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+ }
+#else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s: %s"),
+ service->url->host, _("SSL unavailable"));
+
+ return FALSE;
+#endif /* HAVE_SSL */
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+
+ if ((ret = camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai)) == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s: %s"),
+ service->url->host,
+ g_strerror (errno));
+
+ camel_object_unref (tcp_stream);
+
+ return FALSE;
+ }
+
+ if (camel_imap4_engine_take_stream (engine, tcp_stream, ex) == -1)
+ return FALSE;
+
+ if (camel_imap4_engine_capability (engine, ex) == -1)
+ return FALSE;
+
+ if (ssl_mode != MODE_TLS) {
+ /* we're done */
+ return TRUE;
+ }
+
+ if (!(engine->capa & CAMEL_IMAP4_CAPABILITY_STARTTLS)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ service->url->host, _("SSL negotiations failed"));
+
+ return FALSE;
+ }
+
+ ic = camel_imap4_engine_prequeue (engine, NULL, "STARTTLS\r\n");
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->result != CAMEL_IMAP4_RESULT_OK) {
+ if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ service->url->host, _("Unknown error"));
+ } else {
+ camel_exception_xfer (ex, &ic->ex);
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return FALSE;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return TRUE;
+}
+
+static struct {
+ char *value;
+ char *serv;
+ char *port;
+ int mode;
+} ssl_options[] = {
+ { "", "imaps", "993", MODE_SSL }, /* really old (1.x) */
+ { "always", "imaps", "993", MODE_SSL },
+ { "when-possible", "imap", "143", MODE_TLS },
+ { "never", "imap", "143", MODE_CLEAR },
+ { NULL, "imap", "143", MODE_CLEAR },
+};
+
+static gboolean
+connect_to_server_wrapper (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ CamelService *service = engine->service;
+ struct addrinfo *ai, hints;
+ const char *ssl_mode;
+ int mode, ret, i;
+ char *serv;
+ const char *port;
+
+ if ((ssl_mode = camel_url_get_param (service->url, "use_ssl"))) {
+ for (i = 0; ssl_options[i].value; i++)
+ if (!strcmp (ssl_options[i].value, ssl_mode))
+ break;
+ mode = ssl_options[i].mode;
+ serv = ssl_options[i].serv;
+ port = ssl_options[i].port;
+ } else {
+ mode = MODE_CLEAR;
+ serv = "imap";
+ port = "143";
+ }
+
+ if (service->url->port) {
+ serv = g_alloca (16);
+ sprintf (serv, "%d", service->url->port);
+ port = NULL;
+ }
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = PF_UNSPEC;
+ ai = camel_getaddrinfo (service->url->host, serv, &hints, ex);
+ if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
+ camel_exception_clear (ex);
+ ai = camel_getaddrinfo (service->url->host, port, &hints, ex);
+ }
+ if (ai == NULL)
+ return FALSE;
+
+ ret = connect_to_server (engine, ai, mode, ex);
+
+ camel_freeaddrinfo (ai);
+
+ return ret;
+}
+
+static int
+sasl_auth (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, const unsigned char *linebuf, size_t linelen, CamelException *ex)
+{
+ /* Perform a single challenge iteration */
+ CamelSasl *sasl = ic->user_data;
+ char *challenge;
+
+ if (camel_sasl_authenticated (sasl)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Cannot authenticate to IMAP server %s using the %s authentication mechanism"),
+ engine->url->host, engine->url->authmech);
+ return -1;
+ }
+
+ while (isspace (*linebuf))
+ linebuf++;
+
+ if (*linebuf == '\0')
+ linebuf = NULL;
+
+ if (!(challenge = camel_sasl_challenge_base64 (sasl, (const char *) linebuf, ex)))
+ return -1;
+
+ fprintf (stderr, "sending : %s\r\n", challenge);
+
+ if (camel_stream_printf (engine->ostream, "%s\r\n", challenge) == -1) {
+ g_free (challenge);
+ return -1;
+ }
+
+ g_free (challenge);
+
+ if (camel_stream_flush (engine->ostream) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int
+imap4_try_authenticate (CamelIMAP4Engine *engine, gboolean reprompt, const char *errmsg, CamelException *ex)
+{
+ CamelService *service = engine->service;
+ CamelSession *session = service->session;
+ CamelSasl *sasl = NULL;
+ CamelIMAP4Command *ic;
+ int id;
+
+ if (!service->url->passwd) {
+ guint32 flags = CAMEL_SESSION_PASSWORD_SECRET;
+ char *prompt;
+
+ if (reprompt)
+ flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
+
+ prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s on host %s"),
+ errmsg ? errmsg : "",
+ service->url->user,
+ service->url->host);
+
+ service->url->passwd = camel_session_get_password (session, service, NULL, prompt, "password", flags, ex);
+
+ g_free (prompt);
+
+ if (!service->url->passwd)
+ return FALSE;
+ }
+
+ if (service->url->authmech) {
+ CamelServiceAuthType *mech;
+
+ mech = g_hash_table_lookup (engine->authtypes, service->url->authmech);
+ sasl = camel_sasl_new ("imap4", mech->authproto, service);
+
+ ic = camel_imap4_engine_prequeue (engine, NULL, "AUTHENTICATE %s\r\n", service->url->authmech);
+ ic->plus = sasl_auth;
+ ic->user_data = sasl;
+ } else {
+ ic = camel_imap4_engine_prequeue (engine, NULL, "LOGIN %S %S\r\n",
+ service->url->user, service->url->passwd);
+ }
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (sasl != NULL)
+ camel_object_unref (sasl);
+
+ if (id == -1 || ic->status == CAMEL_IMAP4_COMMAND_ERROR) {
+ /* unrecoverable error */
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+
+ return FALSE;
+ }
+
+ if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+ camel_imap4_command_unref (ic);
+
+ /* try again */
+
+ return TRUE;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ return FALSE;
+}
+
+static gboolean
+imap4_reconnect (CamelIMAP4Engine *engine, CamelException *ex)
+{
+ CamelService *service = engine->service;
+ CamelServiceAuthType *mech;
+ gboolean reprompt = FALSE;
+ char *errmsg = NULL;
+ CamelException lex;
+
+ if (!connect_to_server_wrapper (engine, ex))
+ return FALSE;
+
+#define CANT_USE_AUTHMECH (!(mech = g_hash_table_lookup (engine->authtypes, service->url->authmech)))
+ if (service->url->authmech && CANT_USE_AUTHMECH) {
+ /* Oops. We can't AUTH using the requested mechanism */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Cannot authenticate to IMAP server %s using %s"),
+ service->url->host, service->url->authmech);
+
+ return FALSE;
+ }
+
+ camel_exception_init (&lex);
+ while (imap4_try_authenticate (engine, reprompt, errmsg, &lex)) {
+ g_free (errmsg);
+ errmsg = g_strdup (lex.desc);
+ camel_exception_clear (&lex);
+ reprompt = TRUE;
+ }
+ g_free (errmsg);
+
+ if (camel_exception_is_set (&lex)) {
+ camel_exception_xfer (ex, &lex);
+ return FALSE;
+ }
+
+ if (camel_imap4_engine_namespace (engine, ex) == -1)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+imap4_connect (CamelService *service, CamelException *ex)
+{
+ gboolean retval;
+
+ CAMEL_SERVICE_LOCK (service, connect_lock);
+ retval = imap4_reconnect (((CamelIMAP4Store *) service)->engine, ex);
+ CAMEL_SERVICE_UNLOCK (service, connect_lock);
+
+ return retval;
+}
+
+static gboolean
+imap4_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+ CamelIMAP4Command *ic;
+ int id;
+
+ if (clean && !store->engine->istream->disconnected) {
+ ic = camel_imap4_engine_queue (store->engine, NULL, "LOGOUT\r\n");
+ while ((id = camel_imap4_engine_iterate (store->engine)) < ic->id && id != -1)
+ ;
+
+ camel_imap4_command_unref (ic);
+ }
+
+ return 0;
+}
+
+extern CamelServiceAuthType camel_imap4_password_authtype;
+
+static GList *
+imap4_query_auth_types (CamelService *service, CamelException *ex)
+{
+ CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+ CamelServiceAuthType *authtype;
+ GList *sasl_types, *t, *next;
+ gboolean connected;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ connected = connect_to_server_wrapper (store->engine, ex);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ if (!connected)
+ return NULL;
+
+ sasl_types = camel_sasl_authtype_list (FALSE);
+ for (t = sasl_types; t; t = next) {
+ authtype = t->data;
+ next = t->next;
+
+ if (!g_hash_table_lookup (store->engine->authtypes, authtype->authproto)) {
+ sasl_types = g_list_remove_link (sasl_types, t);
+ g_list_free_1 (t);
+ }
+ }
+
+ return g_list_prepend (sasl_types, &camel_imap4_password_authtype);
+}
+
+
+static char
+imap4_get_path_delim (CamelIMAP4Engine *engine, const char *full_name)
+{
+ /* FIXME: move this to utils so imap4-folder.c can share */
+ CamelIMAP4Namespace *namespace;
+ const char *slash;
+ size_t len;
+ char *top;
+
+ if ((slash = strchr (full_name, '/')))
+ len = (slash - full_name);
+ else
+ len = strlen (full_name);
+
+ top = g_alloca (len + 1);
+ memcpy (top, full_name, len);
+ top[len] = '\0';
+
+ if (!g_ascii_strcasecmp (top, "INBOX"))
+ top = "INBOX";
+
+ retry:
+ namespace = engine->namespaces.personal;
+ while (namespace != NULL) {
+ if (!strcmp (namespace->path, top))
+ return namespace->sep;
+ namespace = namespace->next;
+ }
+
+ namespace = engine->namespaces.other;
+ while (namespace != NULL) {
+ if (!strcmp (namespace->path, top))
+ return namespace->sep;
+ namespace = namespace->next;
+ }
+
+ namespace = engine->namespaces.shared;
+ while (namespace != NULL) {
+ if (!strcmp (namespace->path, top))
+ return namespace->sep;
+ namespace = namespace->next;
+ }
+
+ if (top[0] != '\0') {
+ /* look for a default namespace? */
+ top[0] = '\0';
+ goto retry;
+ }
+
+ return '/';
+}
+
+static char *
+imap4_folder_utf7_name (CamelStore *store, const char *folder_name, char wildcard)
+{
+ char *real_name, *p;
+ char sep;
+ int len;
+
+ sep = imap4_get_path_delim (((CamelIMAP4Store *) store)->engine, folder_name);
+
+ if (sep != '/') {
+ p = real_name = g_alloca (strlen (folder_name) + 1);
+ strcpy (real_name, folder_name);
+ while (*p != '\0') {
+ if (*p == '/')
+ *p = sep;
+ p++;
+ }
+
+ folder_name = real_name;
+ }
+
+ if (*folder_name)
+ real_name = camel_utf8_utf7 (folder_name);
+ else
+ real_name = g_strdup ("");
+
+ if (wildcard) {
+ len = strlen (real_name);
+ real_name = g_realloc (real_name, len + 3);
+
+ if (len > 0)
+ real_name[len++] = sep;
+
+ real_name[len++] = wildcard;
+ real_name[len] = '\0';
+ }
+
+ return real_name;
+}
+
+static CamelFolder *
+imap4_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelFolder *folder = NULL;
+ camel_imap4_list_t *list;
+ CamelIMAP4Command *ic;
+ CamelFolderInfo *fi;
+ GPtrArray *array;
+ char *utf7_name;
+ int create;
+ int id, i;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ /* make sure the folder exists - try LISTing it? */
+ utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+ ic = camel_imap4_engine_queue (engine, NULL, "LIST \"\" %S\r\n", utf7_name);
+ camel_imap4_command_register_untagged (ic, "LIST", camel_imap4_untagged_list);
+ ic->user_data = array = g_ptr_array_new ();
+ g_free (utf7_name);
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+ g_ptr_array_free (array, TRUE);
+ goto done;
+ }
+
+ create = array->len == 0;
+
+ for (i = 0; i < array->len; i++) {
+ list = array->pdata[i];
+ g_free (list->name);
+ g_free (list);
+ }
+
+ g_ptr_array_free (array, TRUE);
+
+ if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get folder `%s' on IMAP server %s: Unknown"),
+ folder_name, ((CamelService *) store)->url->host);
+ camel_imap4_command_unref (ic);
+ goto done;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ if (create) {
+ const char *basename;
+ char *parent;
+ int len;
+
+ if (!(flags & CAMEL_STORE_FOLDER_CREATE))
+ goto done;
+
+ if (!(basename = strrchr (folder_name, '/')))
+ basename = folder_name;
+ else
+ basename++;
+
+ len = basename > folder_name ? (basename - folder_name) - 1 : 0;
+ parent = g_alloca (len + 1);
+ memcpy (parent, folder_name, len);
+ parent[len] = '\0';
+
+ if (!(fi = imap4_create_folder (store, parent, basename, ex)))
+ goto done;
+
+ camel_store_free_folder_info (store, fi);
+ }
+
+ folder = camel_imap4_folder_new (store, folder_name, ex);
+
+ done:
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ return folder;
+}
+
+static CamelFolderInfo *
+imap4_create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
+{
+ /* FIXME: also need to deal with parent folders that can't
+ * contain subfolders - delete them and re-create with the
+ * proper hint */
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelFolderInfo *fi = NULL;
+ CamelIMAP4Command *ic;
+ char *utf7_name;
+ CamelURL *url;
+ const char *c;
+ char *name;
+ char sep;
+ int id;
+
+ sep = imap4_get_path_delim (engine, parent_name);
+
+ c = folder_name;
+ while (*c != '\0') {
+ if (*c == sep || strchr ("/#%*", *c)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+ _("The folder name \"%s\" is invalid because "
+ "it contains the character \"%c\""),
+ folder_name, *c);
+ return NULL;
+ }
+
+ c++;
+ }
+
+ if (parent_name != NULL && *parent_name)
+ name = g_strdup_printf ("%s/%s", parent_name, folder_name);
+ else
+ name = g_strdup (folder_name);
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ utf7_name = imap4_folder_utf7_name (store, name, '\0');
+ ic = camel_imap4_engine_queue (engine, NULL, "CREATE %S\r\n", utf7_name);
+ g_free (utf7_name);
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+ g_free (name);
+ goto done;
+ }
+
+ switch (ic->result) {
+ case CAMEL_IMAP4_RESULT_OK:
+ url = camel_url_copy (engine->url);
+ camel_url_set_fragment (url, name);
+
+ c = strrchr (name, '/');
+
+ fi = g_malloc0 (sizeof (CamelFolderInfo));
+ fi->full_name = name;
+ fi->name = g_strdup (c ? c + 1: name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+ fi->flags = 0;
+ fi->unread = -1;
+ fi->total = -1;
+
+ camel_object_trigger_event (store, "folder_created", fi);
+ break;
+ case CAMEL_IMAP4_RESULT_NO:
+ /* FIXME: would be good to save the NO reason into the err message */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': Invalid mailbox name"),
+ name);
+ g_free (name);
+ break;
+ case CAMEL_IMAP4_RESULT_BAD:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': Bad command"),
+ name);
+ g_free (name);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ camel_imap4_command_unref (ic);
+
+ done:
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ return fi;
+}
+
+static void
+imap4_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelFolder *selected = (CamelFolder *) engine->folder;
+ CamelIMAP4Command *ic, *ic0 = NULL;
+ CamelFolderInfo *fi;
+ char *utf7_name;
+ CamelURL *url;
+ const char *p;
+ int id;
+
+ if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot delete folder `%s': Special folder"),
+ folder_name);
+
+ return;
+ }
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ if (selected && !strcmp (folder_name, selected->full_name))
+ ic0 = camel_imap4_engine_queue (engine, NULL, "CLOSE\r\n");
+
+ utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+ ic = camel_imap4_engine_queue (engine, NULL, "DELETE %S\r\n", utf7_name);
+ g_free (utf7_name);
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ if (ic0 && ic0->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+ camel_exception_xfer (ex, &ic0->ex);
+ else
+ camel_exception_xfer (ex, &ic->ex);
+
+ if (ic0 != NULL)
+ camel_imap4_command_unref (ic0);
+
+ camel_imap4_command_unref (ic);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+
+ if (ic0 != NULL)
+ camel_imap4_command_unref (ic0);
+
+ switch (ic->result) {
+ case CAMEL_IMAP4_RESULT_OK:
+ /* deleted */
+ url = camel_url_copy (engine->url);
+ camel_url_set_fragment (url, folder_name);
+
+ p = strrchr (folder_name, '/');
+
+ fi = g_malloc0 (sizeof (CamelFolderInfo));
+ fi->full_name = g_strdup (folder_name);
+ fi->name = g_strdup (p ? p + 1: folder_name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+ fi->flags = 0;
+ fi->unread = -1;
+ fi->total = -1;
+
+ camel_object_trigger_event (store, "folder_deleted", fi);
+
+ camel_folder_info_free (fi);
+ break;
+ case CAMEL_IMAP4_RESULT_NO:
+ /* FIXME: would be good to save the NO reason into the err message */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot delete folder `%s': Invalid mailbox name"),
+ folder_name);
+ break;
+ case CAMEL_IMAP4_RESULT_BAD:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot delete folder `%s': Bad command"),
+ folder_name);
+ break;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static void
+imap4_rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ char *old_uname, *new_uname;
+ CamelIMAP4Command *ic;
+ int id;
+
+ if (!g_ascii_strcasecmp (old_name, "INBOX")) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot rename folder `%s' to `%s': Special folder"),
+ old_name, new_name);
+
+ return;
+ }
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ old_uname = imap4_folder_utf7_name (store, old_name, '\0');
+ new_uname = imap4_folder_utf7_name (store, new_name, '\0');
+
+ ic = camel_imap4_engine_queue (engine, NULL, "RENAME %S %S\r\n", old_uname, new_uname);
+ g_free (old_uname);
+ g_free (new_uname);
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+
+ switch (ic->result) {
+ case CAMEL_IMAP4_RESULT_OK:
+ /* FIXME: need to update state on the renamed folder object */
+ break;
+ case CAMEL_IMAP4_RESULT_NO:
+ /* FIXME: would be good to save the NO reason into the err message */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot rename folder `%s' to `%s': Invalid mailbox name"),
+ old_name, new_name);
+ break;
+ case CAMEL_IMAP4_RESULT_BAD:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot rename folder `%s' to `%s': Bad command"),
+ old_name, new_name);
+ break;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static int
+list_sort (const camel_imap4_list_t **list0, const camel_imap4_list_t **list1)
+{
+ return strcmp ((*list0)->name, (*list1)->name);
+}
+
+static void
+list_remove_duplicates (GPtrArray *array)
+{
+ camel_imap4_list_t *list, *last;
+ int i;
+
+ last = array->pdata[0];
+ for (i = 1; i < array->len; i++) {
+ list = array->pdata[i];
+ if (!strcmp (list->name, last->name)) {
+ g_ptr_array_remove_index (array, i--);
+ last->flags |= list->flags;
+ g_free (list->name);
+ g_free (list);
+ }
+ }
+}
+
+static void
+imap4_status (CamelStore *store, CamelFolderInfo *fi)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ camel_imap4_status_attr_t *attr, *next;
+ camel_imap4_status_t *status;
+ CamelIMAP4Command *ic;
+ GPtrArray *array;
+ char *mailbox;
+ int id, i;
+
+ mailbox = imap4_folder_utf7_name (store, fi->full_name, '\0');
+ ic = camel_imap4_engine_queue (engine, NULL, "STATUS %S (MESSAGES UNSEEN)\r\n", mailbox);
+ g_free (mailbox);
+
+ camel_imap4_command_register_untagged (ic, "STATUS", camel_imap4_untagged_status);
+ ic->user_data = array = g_ptr_array_new ();
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_imap4_command_unref (ic);
+ g_ptr_array_free (array, TRUE);
+ return;
+ }
+
+ for (i = 0; i < array->len; i++) {
+ status = array->pdata[i];
+ attr = status->attr_list;
+ while (attr != NULL) {
+ next = attr->next;
+ if (attr->type == CAMEL_IMAP4_STATUS_MESSAGES)
+ fi->total = attr->value;
+ else if (attr->type == CAMEL_IMAP4_STATUS_UNSEEN)
+ fi->unread = attr->value;
+ g_free (attr);
+ attr = next;
+ }
+
+ g_free (status->mailbox);
+ g_free (status);
+ }
+
+ camel_imap4_command_unref (ic);
+ g_ptr_array_free (array, TRUE);
+}
+
+static CamelFolderInfo *
+imap4_build_folder_info (CamelStore *store, const char *top, guint32 flags, GPtrArray *array)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelFolder *folder = (CamelFolder *) engine->folder;
+ camel_imap4_list_t *list;
+ CamelFolderInfo *fi;
+ char *name, *p;
+ CamelURL *url;
+ int i;
+
+ if (array->len == 0) {
+ g_ptr_array_free (array, TRUE);
+ return NULL;
+ }
+
+ g_ptr_array_sort (array, (GCompareFunc) list_sort);
+
+ list_remove_duplicates (array);
+
+ url = camel_url_copy (engine->url);
+
+ for (i = 0; i < array->len; i++) {
+ list = array->pdata[i];
+ fi = g_malloc0 (sizeof (CamelFolderInfo));
+
+ p = name = camel_utf7_utf8 (list->name);
+ while (*p != '\0') {
+ if (*p == list->delim)
+ *p = '/';
+ p++;
+ }
+
+ p = strrchr (name, '/');
+ camel_url_set_fragment (url, name);
+
+ fi->full_name = name;
+ fi->name = g_strdup (p ? p + 1: name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ fi->flags = list->flags;
+ fi->unread = -1;
+ fi->total = -1;
+
+ if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST)) {
+ if (folder && !strcmp (folder->full_name, fi->full_name)) {
+ /* can't STATUS this folder since it is SELECTED, besides - it would be wasteful */
+ CamelMessageInfo *info;
+ int index;
+
+ fi->total = camel_folder_summary_count (folder->summary);
+
+ fi->unread = 0;
+ for (index = 0; index < fi->total; index++) {
+ if (!(info = camel_folder_summary_index (folder->summary, index)))
+ continue;
+
+ if ((info->flags & CAMEL_MESSAGE_SEEN) == 0)
+ fi->unread++;
+
+ camel_folder_summary_info_free (folder->summary, info);
+ }
+ } else {
+ imap4_status (store, fi);
+ }
+ }
+
+ g_free (list->name);
+ g_free (list);
+
+ array->pdata[i] = fi;
+ }
+
+ fi = camel_folder_info_build (array, top, '/', TRUE);
+
+ camel_url_free (url);
+
+ g_ptr_array_free (array, TRUE);
+
+ return fi;
+}
+
+static CamelFolderInfo *
+imap4_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelIMAP4Command *ic, *ic0 = NULL;
+ CamelFolderInfo *fi = NULL;
+ camel_imap4_list_t *list;
+ GPtrArray *array;
+ const char *cmd;
+ char *pattern;
+ char wildcard;
+ int id, i;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ if (engine->state == CAMEL_IMAP4_ENGINE_DISCONNECTED) {
+ if (!camel_service_connect ((CamelService *) store, ex))
+ return NULL;
+
+ engine = ((CamelIMAP4Store *) store)->engine;
+ }
+
+ if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
+ cmd = "LSUB";
+ else
+ cmd = "LIST";
+
+ if (top == NULL)
+ top = "";
+
+ wildcard = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) ? '*' : '%';
+ pattern = imap4_folder_utf7_name (store, top, wildcard);
+ array = g_ptr_array_new ();
+
+ if (*top != '\0') {
+ size_t len;
+ char sep;
+
+ len = strlen (pattern);
+ sep = pattern[len - 2];
+ pattern[len - 2] = '\0';
+
+ ic0 = camel_imap4_engine_queue (engine, NULL, "%s \"\" %S\r\n", cmd, pattern);
+ camel_imap4_command_register_untagged (ic0, cmd, camel_imap4_untagged_list);
+ ic0->user_data = array;
+
+ pattern[len - 2] = sep;
+ }
+
+ ic = camel_imap4_engine_queue (engine, NULL, "%s \"\" %S\r\n", cmd, pattern);
+ camel_imap4_command_register_untagged (ic, cmd, camel_imap4_untagged_list);
+ ic->user_data = array;
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ if (ic0 && ic0->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+ camel_exception_xfer (ex, &ic0->ex);
+ else
+ camel_exception_xfer (ex, &ic->ex);
+
+ if (ic0 != NULL)
+ camel_imap4_command_unref (ic0);
+ camel_imap4_command_unref (ic);
+
+ for (i = 0; i < array->len; i++) {
+ list = array->pdata[i];
+ g_free (list->name);
+ g_free (list);
+ }
+
+ g_ptr_array_free (array, TRUE);
+ g_free (pattern);
+
+ goto done;
+ }
+
+ if (ic0 != NULL)
+ camel_imap4_command_unref (ic0);
+
+ if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+ camel_imap4_command_unref (ic);
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get %s information for pattern `%s' on IMAP server %s: %s"),
+ cmd, pattern, engine->url->host, ic->result == CAMEL_IMAP4_RESULT_BAD ?
+ _("Bad command") : _("Unknown"));
+
+ for (i = 0; i < array->len; i++) {
+ list = array->pdata[i];
+ g_free (list->name);
+ g_free (list);
+ }
+
+ g_ptr_array_free (array, TRUE);
+
+ g_free (pattern);
+
+ goto done;
+ }
+
+ g_free (pattern);
+
+ fi = imap4_build_folder_info (store, top, flags, array);
+
+ done:
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ return fi;
+}
+
+static void
+imap4_subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelIMAP4Command *ic;
+ CamelFolderInfo *fi;
+ char *utf7_name;
+ CamelURL *url;
+ const char *p;
+ int id;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+ ic = camel_imap4_engine_queue (engine, NULL, "SUBSCRIBE %S\r\n", utf7_name);
+ g_free (utf7_name);
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+
+ switch (ic->result) {
+ case CAMEL_IMAP4_RESULT_OK:
+ /* subscribed */
+ url = camel_url_copy (engine->url);
+ camel_url_set_fragment (url, folder_name);
+
+ p = strrchr (folder_name, '/');
+
+ fi = g_malloc0 (sizeof (CamelFolderInfo));
+ fi->full_name = g_strdup (folder_name);
+ fi->name = g_strdup (p ? p + 1: folder_name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+ fi->flags = CAMEL_FOLDER_NOCHILDREN;
+ fi->unread = -1;
+ fi->total = -1;
+
+ camel_object_trigger_event (store, "folder_subscribed", fi);
+ camel_folder_info_free (fi);
+ break;
+ case CAMEL_IMAP4_RESULT_NO:
+ /* FIXME: would be good to save the NO reason into the err message */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot subscribe to folder `%s': Invalid mailbox name"),
+ folder_name);
+ break;
+ case CAMEL_IMAP4_RESULT_BAD:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot subscribe to folder `%s': Bad command"),
+ folder_name);
+ break;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static void
+imap4_unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelIMAP4Command *ic;
+ CamelFolderInfo *fi;
+ char *utf7_name;
+ CamelURL *url;
+ const char *p;
+ int id;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+ ic = camel_imap4_engine_queue (engine, NULL, "UNSUBSCRIBE %S\r\n", utf7_name);
+ g_free (utf7_name);
+
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap4_command_unref (ic);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+
+ switch (ic->result) {
+ case CAMEL_IMAP4_RESULT_OK:
+ /* unsubscribed */
+ url = camel_url_copy (engine->url);
+ camel_url_set_fragment (url, folder_name);
+
+ p = strrchr (folder_name, '/');
+
+ fi = g_malloc0 (sizeof (CamelFolderInfo));
+ fi->full_name = g_strdup (folder_name);
+ fi->name = g_strdup (p ? p + 1: folder_name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+ fi->flags = 0;
+ fi->unread = -1;
+ fi->total = -1;
+
+ camel_object_trigger_event (store, "folder_unsubscribed", fi);
+ camel_folder_info_free (fi);
+ break;
+ case CAMEL_IMAP4_RESULT_NO:
+ /* FIXME: would be good to save the NO reason into the err message */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot unsubscribe from folder `%s': Invalid mailbox name"),
+ folder_name);
+ break;
+ case CAMEL_IMAP4_RESULT_BAD:
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot unsubscribe from folder `%s': Bad command"),
+ folder_name);
+ break;
+ }
+
+ camel_imap4_command_unref (ic);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
+
+static void
+imap4_noop (CamelStore *store, CamelException *ex)
+{
+ CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+ CamelFolder *folder = (CamelFolder *) engine->folder;
+ CamelIMAP4Command *ic;
+ int id;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+
+ if (folder) {
+ camel_folder_sync (folder, FALSE, ex);
+ if (camel_exception_is_set (ex)) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ return;
+ }
+ }
+
+ ic = camel_imap4_engine_queue (engine, NULL, "NOOP\r\n");
+ while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+ camel_exception_xfer (ex, &ic->ex);
+
+ camel_imap4_command_unref (ic);
+
+ if (folder && !camel_exception_is_set (ex))
+ camel_imap4_summary_flush_updates (folder->summary, ex);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+}
diff --git a/camel/providers/imapp/camel-imapp-store.c b/camel/providers/imapp/camel-imapp-store.c
new file mode 100644
index 0000000000..c1c9f50649
--- /dev/null
+++ b/camel/providers/imapp/camel-imapp-store.c
@@ -0,0 +1,1016 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-store.c : class for a imap store */
+
+/*
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "camel/camel-operation.h"
+
+#include "camel/camel-stream-buffer.h"
+#include "camel/camel-session.h"
+#include "camel/camel-exception.h"
+#include "camel/camel-url.h"
+#include "camel/camel-sasl.h"
+#include "camel/camel-data-cache.h"
+#include "camel/camel-tcp-stream.h"
+#include "camel/camel-tcp-stream-raw.h"
+#ifdef HAVE_SSL
+#include "camel/camel-tcp-stream-ssl.h"
+#endif
+
+#include "camel-imapp-store-summary.h"
+#include "camel-imapp-store.h"
+#include "camel-imapp-folder.h"
+#include "camel-imapp-engine.h"
+#include "camel-imapp-exception.h"
+#include "camel-imapp-utils.h"
+#include "camel-imapp-driver.h"
+
+/* Specified in RFC 2060 section 2.1 */
+#define IMAP_PORT 143
+
+static CamelStoreClass *parent_class = NULL;
+
+static void finalize (CamelObject *object);
+
+static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
+/* static char *imap_get_name(CamelService *service, gboolean brief);*/
+static gboolean imap_connect (CamelService *service, CamelException *ex);
+static gboolean imap_disconnect (CamelService *service, gboolean clean, CamelException *ex);
+static GList *imap_query_auth_types (CamelService *service, CamelException *ex);
+
+static CamelFolder *imap_get_trash (CamelStore *store, CamelException *ex);
+
+static CamelFolder *imap_get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
+static CamelFolder *imap_get_inbox (CamelStore *store, CamelException *ex);
+static void imap_rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *imap_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+static void imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
+static void imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
+static CamelFolderInfo *imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
+
+/* yet to see if this should go global or not */
+void camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select);
+
+static void
+camel_imapp_store_class_init (CamelIMAPPStoreClass *camel_imapp_store_class)
+{
+ CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_imapp_store_class);
+ CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_imapp_store_class);
+
+ parent_class = CAMEL_STORE_CLASS(camel_type_get_global_classfuncs(camel_store_get_type()));
+
+ /* virtual method overload */
+ camel_service_class->construct = imap_construct;
+ /*camel_service_class->get_name = imap_get_name;*/
+ camel_service_class->query_auth_types = imap_query_auth_types;
+ camel_service_class->connect = imap_connect;
+ camel_service_class->disconnect = imap_disconnect;
+
+ camel_store_class->get_trash = imap_get_trash;
+ camel_store_class->get_folder = imap_get_folder;
+ camel_store_class->get_inbox = imap_get_inbox;
+
+ camel_store_class->create_folder = imap_create_folder;
+ camel_store_class->rename_folder = imap_rename_folder;
+ camel_store_class->delete_folder = imap_delete_folder;
+ camel_store_class->get_folder_info = imap_get_folder_info;
+}
+
+static void
+camel_imapp_store_init (gpointer object, gpointer klass)
+{
+ /*CamelIMAPPStore *istore = object;*/
+}
+
+CamelType
+camel_imapp_store_get_type (void)
+{
+ static CamelType camel_imapp_store_type = CAMEL_INVALID_TYPE;
+
+ if (!camel_imapp_store_type) {
+ camel_imapp_store_type = camel_type_register(CAMEL_STORE_TYPE,
+ "CamelIMAPPStore",
+ sizeof (CamelIMAPPStore),
+ sizeof (CamelIMAPPStoreClass),
+ (CamelObjectClassInitFunc) camel_imapp_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imapp_store_init,
+ finalize);
+ }
+
+ return camel_imapp_store_type;
+}
+
+static void
+finalize (CamelObject *object)
+{
+ CamelIMAPPStore *imap_store = CAMEL_IMAPP_STORE (object);
+
+ /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
+ /* SIGH */
+
+ camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
+
+ if (imap_store->driver)
+ camel_object_unref(imap_store->driver);
+ if (imap_store->cache)
+ camel_object_unref(imap_store->cache);
+}
+
+static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
+{
+ char *root, *summary;
+ CamelIMAPPStore *store = (CamelIMAPPStore *)service;
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set(ex))
+ return;
+
+ CAMEL_TRY {
+ store->summary = camel_imapp_store_summary_new();
+ root = camel_session_get_storage_path(service->session, service, ex);
+ if (root) {
+ summary = g_build_filename(root, ".ev-store-summary", NULL);
+ camel_store_summary_set_filename((CamelStoreSummary *)store->summary, summary);
+ /* FIXME: need to remove params, passwords, etc */
+ camel_store_summary_set_uri_base((CamelStoreSummary *)store->summary, service->url);
+ camel_store_summary_load((CamelStoreSummary *)store->summary);
+ }
+ } CAMEL_CATCH(e) {
+ camel_exception_xfer(ex, e);
+ } CAMEL_DONE;
+}
+
+enum {
+ USE_SSL_NEVER,
+ USE_SSL_ALWAYS,
+ USE_SSL_WHEN_POSSIBLE
+};
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+static void
+connect_to_server (CamelService *service, int ssl_mode, int try_starttls)
+/* throws IO exception */
+{
+ CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
+ CamelStream * volatile tcp_stream = NULL;
+ CamelIMAPPStream * volatile imap_stream = NULL;
+ int ret;
+ CamelException *ex;
+
+ ex = camel_exception_new();
+ CAMEL_TRY {
+ char *serv;
+ const char *port = NULL;
+ struct addrinfo *ai, hints = { 0 };
+
+ /* parent class connect initialization */
+ CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex);
+ if (ex->id)
+ camel_exception_throw_ex(ex);
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else {
+ serv = "imap";
+ port = "143";
+ }
+
+#ifdef HAVE_SSL
+ if (camel_url_get_param (service->url, "use_ssl")) {
+ if (try_starttls)
+ tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
+ else {
+ if (service->url->port == 0) {
+ serv = "imaps";
+ port = "993";
+ }
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+ }
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+#else
+ tcp_stream = camel_tcp_stream_raw_new ();
+#endif /* HAVE_SSL */
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ex->id && ex->id != CAMEL_EXCEPTION_USER_CANCEL && port != NULL) {
+ camel_exception_clear(ex);
+ ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
+ }
+
+ if (ex->id)
+ camel_exception_throw_ex(ex);
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_throw(CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled"));
+ else
+ camel_exception_throw(CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv, strerror(errno));
+ }
+
+ imap_stream = (CamelIMAPPStream *)camel_imapp_stream_new(tcp_stream);
+ store->driver = camel_imapp_driver_new(imap_stream);
+
+ camel_object_unref(imap_stream);
+ camel_object_unref(tcp_stream);
+ } CAMEL_CATCH(e) {
+ if (tcp_stream)
+ camel_object_unref(tcp_stream);
+ if (imap_stream)
+ camel_object_unref((CamelObject *)imap_stream);
+ camel_exception_throw_ex(e);
+ } CAMEL_DONE;
+
+ camel_exception_free(ex);
+}
+
+#if 0
+
+/* leave this stuff out for now */
+
+
+static struct {
+ char *value;
+ int mode;
+} ssl_options[] = {
+ { "", USE_SSL_ALWAYS },
+ { "always", USE_SSL_ALWAYS },
+ { "when-possible", USE_SSL_WHEN_POSSIBLE },
+ { "never", USE_SSL_NEVER },
+ { NULL, USE_SSL_NEVER },
+};
+
+static gboolean
+connect_to_server_wrapper (CamelService *service, CamelException *ex)
+{
+#ifdef HAVE_SSL
+ const char *use_ssl;
+ int i, ssl_mode;
+
+ use_ssl = camel_url_get_param (service->url, "use_ssl");
+ if (use_ssl) {
+ for (i = 0; ssl_options[i].value; i++)
+ if (!strcmp (ssl_options[i].value, use_ssl))
+ break;
+ ssl_mode = ssl_options[i].mode;
+ } else
+ ssl_mode = USE_SSL_NEVER;
+
+ if (ssl_mode == USE_SSL_ALWAYS) {
+ /* First try the ssl port */
+ if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
+ if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
+ /* The ssl port seems to be unavailable, lets try STARTTLS */
+ camel_exception_clear (ex);
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ /* If the server supports STARTTLS, use it */
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ /* User doesn't care about SSL */
+ return connect_to_server (service, ssl_mode, FALSE, ex);
+ }
+#else
+ return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
+#endif
+}
+#endif
+
+extern CamelServiceAuthType camel_imapp_password_authtype;
+extern CamelServiceAuthType camel_imapp_apop_authtype;
+
+static GList *
+imap_query_auth_types (CamelService *service, CamelException *ex)
+{
+ /*CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);*/
+ GList *types = NULL;
+
+ types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
+ if (types == NULL)
+ return NULL;
+
+#if 0
+ if (connect_to_server_wrapper (service, NULL)) {
+ types = g_list_concat(types, g_list_copy(store->engine->auth));
+ imap_disconnect (service, TRUE, NULL);
+ } else {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to POP server on %s"),
+ service->url->host);
+ }
+#endif
+ return types;
+}
+
+static void
+store_get_pass(CamelIMAPPStore *store)
+{
+ if (((CamelService *)store)->url->passwd == NULL) {
+ char *prompt;
+ CamelException ex;
+
+ camel_exception_init(&ex);
+
+ prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s@%s"),
+ store->login_error?store->login_error:"",
+ ((CamelService *)store)->url->user,
+ ((CamelService *)store)->url->host);
+ ((CamelService *)store)->url->passwd = camel_session_get_password(camel_service_get_session((CamelService *)store),
+ (CamelService *)store, NULL,
+ prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, &ex);
+ g_free (prompt);
+ if (camel_exception_is_set(&ex))
+ camel_exception_throw_ex(&ex);
+ }
+}
+
+static struct _CamelSasl *
+store_get_sasl(struct _CamelIMAPPDriver *driver, CamelIMAPPStore *store)
+{
+ store_get_pass(store);
+
+ if (((CamelService *)store)->url->authmech)
+ return camel_sasl_new("imap", ((CamelService *)store)->url->authmech, (CamelService *)store);
+
+ return NULL;
+}
+
+static void
+store_get_login(struct _CamelIMAPPDriver *driver, char **login, char **pass, CamelIMAPPStore *store)
+{
+ store_get_pass(store);
+
+ *login = g_strdup(((CamelService *)store)->url->user);
+ *pass = g_strdup(((CamelService *)store)->url->passwd);
+}
+
+static gboolean
+imap_connect (CamelService *service, CamelException *ex)
+{
+ CamelIMAPPStore *store = (CamelIMAPPStore *)service;
+ volatile int ret = FALSE;
+
+ CAMEL_TRY {
+ volatile int retry = TRUE;
+
+ if (store->cache == NULL) {
+ char *root;
+
+ root = camel_session_get_storage_path(service->session, service, ex);
+ if (root) {
+ store->cache = camel_data_cache_new(root, 0, ex);
+ g_free(root);
+ if (store->cache) {
+ /* Default cache expiry - 1 week or not visited in a day */
+ camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
+ camel_data_cache_set_expire_access(store->cache, 60*60*24);
+ }
+ }
+ if (camel_exception_is_set(ex))
+ camel_exception_throw_ex(ex);
+ }
+
+ connect_to_server(service, USE_SSL_NEVER, FALSE);
+
+ camel_imapp_driver_set_sasl_factory(store->driver, (CamelIMAPPSASLFunc)store_get_sasl, store);
+ camel_imapp_driver_set_login_query(store->driver, (CamelIMAPPLoginFunc)store_get_login, store);
+ store->login_error = NULL;
+
+ do {
+ CAMEL_TRY {
+ if (store->driver->engine->state != IMAP_ENGINE_AUTH)
+ camel_imapp_driver_login(store->driver);
+ ret = TRUE;
+ retry = FALSE;
+ } CAMEL_CATCH(e) {
+ g_free(store->login_error);
+ store->login_error = NULL;
+ switch (e->id) {
+ case CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE:
+ store->login_error = g_strdup_printf("%s\n\n", e->desc);
+ camel_session_forget_password(service->session, service, NULL, "password", ex);
+ camel_url_set_passwd(service->url, NULL);
+ break;
+ default:
+ camel_exception_throw_ex(e);
+ break;
+ }
+ } CAMEL_DONE;
+ } while (retry);
+ } CAMEL_CATCH(e) {
+ camel_exception_xfer(ex, e);
+ camel_service_disconnect(service, TRUE, NULL);
+ ret = FALSE;
+ } CAMEL_DONE;
+
+ g_free(store->login_error);
+ store->login_error = NULL;
+
+ return ret;
+}
+
+static gboolean
+imap_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
+
+ /* FIXME: logout */
+
+ if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
+ return FALSE;
+
+ /* logout/disconnect */
+ if (store->driver) {
+ camel_object_unref(store->driver);
+ store->driver = NULL;
+ }
+
+ return TRUE;
+}
+
+static CamelFolder *
+imap_get_trash (CamelStore *store, CamelException *ex)
+{
+ /* no-op */
+ return NULL;
+}
+
+static CamelFolder *
+imap_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
+ CamelIMAPPFolder * volatile folder = NULL;
+
+ /* ??? */
+
+ /* 1. create the folder */
+ /* 2. run select? */
+ /* 3. update the folder */
+
+ CAMEL_TRY {
+ folder = (CamelIMAPPFolder *)camel_imapp_folder_new(store, folder_name);
+ camel_imapp_driver_select(istore->driver, folder);
+ } CAMEL_CATCH (e) {
+ if (folder) {
+ camel_object_unref(folder);
+ folder = NULL;
+ }
+ camel_exception_xfer(ex, e);
+ } CAMEL_DONE;
+
+ return (CamelFolder *)folder;
+}
+
+static CamelFolder *
+imap_get_inbox(CamelStore *store, CamelException *ex)
+{
+ camel_exception_setv(ex, 1, "get_inbox::unimplemented");
+
+ return NULL;
+}
+
+/* 8 bit, string compare */
+static int folders_build_cmp(const void *app, const void *bpp)
+{
+ struct _list_info *a = *((struct _list_info **)app);
+ struct _list_info *b = *((struct _list_info **)bpp);
+ unsigned char *ap = (unsigned char *)(a->name);
+ unsigned char *bp = (unsigned char *)(b->name);
+
+ printf("qsort, cmp '%s' <> '%s'\n", ap, bp);
+
+ while (*ap && *ap == *bp) {
+ ap++;
+ bp++;
+ }
+
+ if (*ap < *bp)
+ return -1;
+ else if (*ap > *bp)
+ return 1;
+ return 0;
+}
+
+/* FIXME: this should go via storesummary? */
+static CamelFolderInfo *
+folders_build_info(CamelURL *base, struct _list_info *li)
+{
+ char *path, *full_name, *name;
+ CamelFolderInfo *fi;
+
+ full_name = imapp_list_get_path(li);
+ name = strrchr(full_name, '/');
+ if (name)
+ name++;
+ else
+ name = full_name;
+
+ path = alloca(strlen(full_name)+2);
+ sprintf(path, "/%s", full_name);
+ camel_url_set_path(base, path);
+
+ fi = g_malloc0(sizeof(*fi));
+ fi->uri = camel_url_to_string(base, CAMEL_URL_HIDE_ALL);
+ fi->name = g_strdup(name);
+ fi->full_name = full_name;
+ fi->unread = -1;
+ fi->total = -1;
+ fi->flags = li->flags;
+
+ if (!g_ascii_strcasecmp(fi->full_name, "inbox"))
+ fi->flags |= CAMEL_FOLDER_SYSTEM;
+
+ /* TODO: could look up count here ... */
+ /* ?? */
+ /*folder = camel_object_bag_get(store->folders, "INBOX");*/
+
+ return fi;
+}
+
+/*
+ a
+ a/b
+ a/b/c
+ a/d
+ b
+ c/d
+
+*/
+
+/* note, pname is the raw name, not the folderinfo name */
+/* note also this free's as we go, since we never go 'backwards' */
+static CamelFolderInfo *
+folders_build_rec(CamelURL *base, GPtrArray *folders, int *ip, CamelFolderInfo *pfi, char *pname)
+{
+ int plen = 0;
+ CamelFolderInfo *last = NULL, *first = NULL;
+
+ if (pfi)
+ plen = strlen(pname);
+
+ for(;(*ip)<(int)folders->len;) {
+ CamelFolderInfo *fi;
+ struct _list_info *li;
+
+ li = folders->pdata[*ip];
+ printf("checking '%s' is child of '%s'\n", li->name, pname);
+
+ /* is this a child of the parent? */
+ if (pfi != NULL
+ && (strncmp(pname, li->name, strlen(pname)) != 0
+ || li->name[plen] != li->separator)) {
+ printf(" nope\n");
+ break;
+ }
+ printf(" yep\n");
+
+ /* is this not an immediate child of the parent? */
+#if 0
+ char *p;
+ if (pfi != NULL
+ && li->separator != 0
+ && (p = strchr(li->name + plen + 1, li->separator)) != NULL) {
+ if (last == NULL) {
+ struct _list_info tli;
+
+ tli.flags = CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_CHILDREN;
+ tli.separator = li->separator;
+ tli.name = g_strndup(li->name, p-li->name+1);
+ fi = folders_build_info(base, &tli);
+ fi->parent = pfi;
+ if (pfi && pfi->child == NULL)
+ pfi->child = fi;
+ i = folders_build_rec(folders, i, fi, tli.name);
+ break;
+ }
+ }
+#endif
+
+ fi = folders_build_info(base, li);
+ fi->parent = pfi;
+ if (last != NULL)
+ last->next = fi;
+ last = fi;
+ if (first == NULL)
+ first = fi;
+
+ (*ip)++;
+ fi->child = folders_build_rec(base, folders, ip, fi, li->name);
+ imap_free_list(li);
+ }
+
+ return first;
+}
+
+static void
+folder_info_dump(CamelFolderInfo *fi, int depth)
+{
+ char *s;
+
+ s = alloca(depth+1);
+ memset(s, ' ', depth);
+ s[depth] = 0;
+ while (fi) {
+ printf("%s%s (%s)\n", s, fi->name, fi->uri);
+ if (fi->child)
+ folder_info_dump(fi->child, depth+2);
+ fi = fi->next;
+ }
+
+}
+
+static CamelFolderInfo *
+imap_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
+ CamelFolderInfo * fi= NULL;
+ char *name;
+
+ /* FIXME: temporary, since this is not a disco store */
+ if (istore->driver == NULL
+ && !camel_service_connect((CamelService *)store, ex))
+ return NULL;
+
+ name = (char *)top;
+ if (name == NULL || name[0] == 0) {
+ /* namespace? */
+ name = "";
+ }
+
+ name = "";
+
+ CAMEL_TRY {
+ CamelURL *base;
+ int i;
+ GPtrArray *folders;
+
+ /* FIXME: subscriptions? lsub? */
+ folders = camel_imapp_driver_list(istore->driver, name, flags);
+
+ /* this greatly simplifies the tree algorithm ... but it might
+ be faster just to use a hashtable to find parents? */
+ qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), folders_build_cmp);
+
+ i = 0;
+ base = camel_url_copy(((CamelService *)store)->url);
+ fi = folders_build_rec(base, folders, &i, NULL, NULL);
+ camel_url_free(base);
+ g_ptr_array_free(folders, TRUE);
+ } CAMEL_CATCH(e) {
+ camel_exception_xfer(ex, e);
+ } CAMEL_DONE;
+
+ printf("built folder info:\n");
+ folder_info_dump(fi, 2);
+
+ return fi;
+
+#if 0
+ if (top == NULL || !g_ascii_strcasecmp(top, "inbox")) {
+ CamelURL *uri = camel_url_copy(((CamelService *)store)->url);
+
+ camel_url_set_path(uri, "/INBOX");
+ fi = g_malloc0(sizeof(*fi));
+ fi->url = camel_url_to_string(uri, CAMEL_URL_HIDE_ALL);
+ camel_url_free(uri);
+ fi->name = g_strdup("INBOX");
+ fi->full_name = g_strdup("INBOX");
+ fi->path = g_strdup("/INBOX");
+ fi->unread_message_count = -1;
+ fi->flags = 0;
+
+ folder = camel_object_bag_get(store->folders, "INBOX");
+ if (folder) {
+ /*if (!cflags & FAST)*/
+ camel_imapp_driver_update(istore->driver, (CamelIMAPPFolder *)folder);
+ fi->unread_message_count = camel_folder_get_unread_message_count(folder);
+ camel_object_unref(folder);
+ }
+ } else {
+ camel_exception_setv(ex, 1, "not implemented");
+ }
+#endif
+ return fi;
+
+#if 0
+ istore->pending_list = g_ptr_array_new();
+
+ CAMEL_TRY {
+ ic = camel_imapp_engine_command_new(istore->driver->engine, "LIST", NULL, "LIST \"\" %f", top);
+ camel_imapp_engine_command_queue(istore->driver->engine, ic);
+ while (camel_imapp_engine_iterate(istore->driver->engine, ic) > 0)
+ ;
+
+ if (ic->status->result != IMAP_OK)
+ camel_exception_throw(1, "list failed: %s", ic->status->text);
+ } CAMEL_CATCH (e) {
+ camel_exception_xfer(ex, e);
+ } CAMEL_DONE;
+
+ camel_imapp_engine_command_free(istore->driver->engine, ic);
+
+ printf("got folder list:\n");
+ for (i=0;i<(int)istore->pending_list->len;i++) {
+ struct _list_info *linfo = istore->pending_list->pdata[i];
+
+ printf("%s (%c)\n", linfo->name, linfo->separator);
+ imap_free_list(linfo);
+ }
+ istore->pending_list = NULL;
+
+ return NULL;
+#endif
+}
+
+static void
+imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ camel_exception_setv(ex, 1, "delete_folder::unimplemented");
+}
+
+static void
+imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
+{
+ camel_exception_setv(ex, 1, "rename_folder::unimplemented");
+}
+
+static CamelFolderInfo *
+imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
+{
+ camel_exception_setv(ex, 1, "create_folder::unimplemented");
+ return NULL;
+}
+
+/* ********************************************************************** */
+#if 0
+static int store_resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ struct _fetch_info *finfo;
+ CamelIMAPPStore *istore = data;
+ CamelMessageInfo *info;
+ struct _pending_fetch *pending;
+
+ finfo = imap_parse_fetch(ie->stream);
+ if (istore->selected) {
+ if ((finfo->got & FETCH_UID) == 0) {
+ printf("didn't get uid in fetch response?\n");
+ } else {
+ info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, id-1);
+ /* exists, check/update */
+ if (info) {
+ if (strcmp(finfo->uid, camel_message_info_uid(info)) != 0) {
+ printf("summary at index %d has uid %s expected %s\n", id, camel_message_info_uid(info), finfo->uid);
+ /* uid mismatch??? try do it based on uid instead? try to reorder? i dont know? */
+ camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
+ info = camel_folder_summary_uid(((CamelFolder *)istore->selected)->summary, finfo->uid);
+ }
+ }
+
+ if (info) {
+ if (finfo->got & (FETCH_FLAGS)) {
+ printf("updating flags for uid '%s'\n", finfo->uid);
+ info->flags = finfo->flags;
+ camel_folder_change_info_change_uid(istore->selected->changes, finfo->uid);
+ }
+ if (finfo->got & FETCH_MINFO) {
+ printf("got envelope unexpectedly?\n");
+ }
+ /* other things go here, like body fetches */
+ } else {
+ pending = g_hash_table_lookup(istore->pending_fetch_table, finfo->uid);
+
+ /* we need to create a new info, we only care about flags and minfo */
+
+ if (pending)
+ info = pending->info;
+ else {
+ info = camel_folder_summary_info_new(((CamelFolder *)istore->selected)->summary);
+ camel_message_info_set_uid(info, g_strdup(finfo->uid));
+ }
+
+ if (finfo->got & FETCH_FLAGS)
+ info->flags = finfo->flags;
+
+ if (finfo->got & FETCH_MINFO) {
+ /* if we only use ENVELOPE? */
+ camel_message_info_set_subject(info, g_strdup(camel_message_info_subject(finfo->minfo)));
+ camel_message_info_set_from(info, g_strdup(camel_message_info_from(finfo->minfo)));
+ camel_message_info_set_to(info, g_strdup(camel_message_info_to(finfo->minfo)));
+ camel_message_info_set_cc(info, g_strdup(camel_message_info_cc(finfo->minfo)));
+ info->date_sent = finfo->minfo->date_sent;
+ camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info);
+ camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid);
+ if (pending) {
+ e_dlist_remove((EDListNode *)pending);
+ g_hash_table_remove(istore->pending_fetch_table, finfo->uid);
+ /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/
+ }
+ } else if (finfo->got & FETCH_HEADER) {
+ /* if we only use HEADER? */
+ CamelMimeParser *mp;
+
+ if (pending == NULL)
+ camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
+ mp = camel_mime_parser_new();
+ camel_mime_parser_init_with_stream(mp, finfo->header);
+ info = camel_folder_summary_info_new_from_parser(((CamelFolder *)istore->selected)->summary, mp);
+ camel_object_unref(mp);
+ camel_message_info_set_uid(info, g_strdup(finfo->uid));
+
+ camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info);
+ camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid);
+ if (pending) {
+ /* FIXME: use a dlist */
+ e_dlist_remove((EDListNode *)pending);
+ g_hash_table_remove(istore->pending_fetch_table, camel_message_info_uid(pending->info));
+ camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, pending->info);
+ /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/
+ }
+ } else if (finfo->got & FETCH_FLAGS) {
+ if (pending == NULL) {
+ pending = e_memchunk_alloc(istore->pending_fetch_chunks);
+ pending->info = info;
+ g_hash_table_insert(istore->pending_fetch_table, (char *)camel_message_info_uid(info), pending);
+ e_dlist_addtail(&istore->pending_fetch_list, (EDListNode *)pending);
+ }
+ } else {
+ if (pending == NULL)
+ camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
+ printf("got unexpected fetch response?\n");
+ imap_dump_fetch(finfo);
+ }
+ }
+ }
+ } else {
+ printf("unexpected fetch response, no folder selected?\n");
+ }
+ /*imap_dump_fetch(finfo);*/
+ imap_free_fetch(finfo);
+
+ return camel_imapp_engine_skip(ie);
+}
+#endif
+
+/* ********************************************************************** */
+
+/* should be moved to imapp-utils?
+ stuff in imapp-utils should be moved to imapp-parse? */
+
+/* ********************************************************************** */
+
+#if 0
+void
+camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select)
+{
+ CamelIMAPPCommand * volatile ic = NULL;
+ CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
+ int i;
+ struct _uidset_state ss;
+ GPtrArray *fetch;
+ CamelMessageInfo *info;
+ struct _pending_fetch *fw, *fn;
+
+ printf("imap folder selected\n");
+
+ if (select->uidvalidity == folder->uidvalidity
+ && select->exists == folder->exists
+ && select->recent == folder->recent
+ && select->unseen == folder->unseen) {
+ /* no work to do? */
+ return;
+ }
+
+ istore->pending_fetch_table = g_hash_table_new(g_str_hash, g_str_equal);
+ istore->pending_fetch_chunks = e_memchunk_new(256, sizeof(struct _pending_fetch));
+
+ /* perform an update - flags first (and see what we have) */
+ CAMEL_TRY {
+ ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "FETCH 1:%d (UID FLAGS)", select->exists);
+ camel_imapp_engine_command_queue(istore->engine, ic);
+ while (camel_imapp_engine_iterate(istore->engine, ic) > 0)
+ ;
+
+ if (ic->status->result != IMAP_OK)
+ camel_exception_throw(1, "fetch failed: %s", ic->status->text);
+
+ /* pending_fetch_list now contains any new messages */
+ /* FIXME: how do we work out no-longer present messages? */
+ printf("now fetching info for messages?\n");
+ uidset_init(&ss, store->engine);
+ ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH ");
+ fw = (struct _pending_fetch *)istore->pending_fetch_list.head;
+ fn = fw->next;
+ while (fn) {
+ info = fw->info;
+ /* if the uid set fills, then flush the command out */
+ if (uidset_add(&ss, ic, camel_message_info_uid(info))
+ || (fn->next == NULL && uidset_done(&ss, ic))) {
+ camel_imapp_engine_command_add(istore->engine, ic, " (FLAGS RFC822.HEADER)");
+ camel_imapp_engine_command_queue(istore->engine, ic);
+ while (camel_imapp_engine_iterate(istore->engine, ic) > 0)
+ ;
+ if (ic->status->result != IMAP_OK)
+ camel_exception_throw(1, "fetch failed: %s", ic->status->text);
+ /* if not end ... */
+ camel_imapp_engine_command_free(istore->engine, ic);
+ ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH ");
+ }
+ fw = fn;
+ fn = fn->next;
+ }
+
+ printf("The pending list should now be empty: %s\n", e_dlist_empty(&istore->pending_fetch_list)?"TRUE":"FALSE");
+ for (i=0;i<10;i++) {
+ info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, i);
+ if (info) {
+ printf("message info [%d] =\n", i);
+ camel_message_info_dump(info);
+ camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info);
+ }
+ }
+ } CAMEL_CATCH (e) {
+ /* FIXME: cleanup */
+ camel_exception_throw_ex(e);
+ } CAMEL_DONE;
+
+ g_hash_table_destroy(istore->pending_fetch_table);
+ istore->pending_fetch_table = NULL;
+ e_memchunk_destroy(istore->pending_fetch_chunks);
+
+ camel_imapp_engine_command_free(istore->engine, ic);
+}
+#endif
+
+#if 0
+/*char *uids[] = {"1", "2", "4", "5", "6", "7", "9", "11", "12", 0};*/
+/*char *uids[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", 0};*/
+char *uids[] = {"1", "3", "5", "7", "9", "11", "12", "13", "14", "15", "20", "21", "24", "25", "26", 0};
+
+void
+uidset_test(CamelIMAPPEngine *ie)
+{
+ struct _uidset_state ss;
+ CamelIMAPPCommand *ic;
+ int i;
+
+ /*ic = camel_imapp_engine_command_new(ie, 0, "FETCH", NULL, "FETCH ");*/
+ uidset_init(&ss, 0, 0);
+ for (i=0;uids[i];i++) {
+ if (uidset_add(&ss, uids[i])) {
+ printf("\n[%d] flushing uids\n", i);
+ }
+ }
+
+ if (uidset_done(&ss)) {
+ printf("\nflushing uids\n");
+ }
+}
+#endif
diff --git a/camel/providers/local/camel-local-folder.c b/camel/providers/local/camel-local-folder.c
new file mode 100644
index 0000000000..b86d23221a
--- /dev/null
+++ b/camel/providers/local/camel-local-folder.c
@@ -0,0 +1,631 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#ifndef _POSIX_PATH_MAX
+#include <posix1_lim.h>
+#endif
+
+#include "camel-local-folder.h"
+#include "camel-local-store.h"
+#include "camel-stream-fs.h"
+#include "camel-local-summary.h"
+#include "camel-data-wrapper.h"
+#include "camel-mime-message.h"
+#include "camel-stream-filter.h"
+#include "camel-mime-filter-from.h"
+#include "camel-exception.h"
+
+#include "camel-local-private.h"
+
+#include "camel-text-index.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+#ifndef PATH_MAX
+#define PATH_MAX _POSIX_PATH_MAX
+#endif
+
+static CamelFolderClass *parent_class;
+static GSList *local_folder_properties;
+
+/* Returns the class for a CamelLocalFolder */
+#define CLOCALF_CLASS(so) CAMEL_LOCAL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CLOCALS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static int local_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args);
+static int local_setv(CamelObject *object, CamelException *ex, CamelArgV *args);
+
+static int local_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex);
+static void local_unlock(CamelLocalFolder *lf);
+
+static char *local_get_full_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name);
+static char *local_get_meta_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext);
+
+static void local_refresh_info(CamelFolder *folder, CamelException *ex);
+
+static void local_sync(CamelFolder *folder, gboolean expunge, CamelException *ex);
+static void local_expunge(CamelFolder *folder, CamelException *ex);
+
+static GPtrArray *local_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex);
+static GPtrArray *local_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex);
+static void local_search_free(CamelFolder *folder, GPtrArray * result);
+
+static void local_delete(CamelFolder *folder);
+static void local_rename(CamelFolder *folder, const char *newname);
+
+static void local_finalize(CamelObject * object);
+
+static void
+camel_local_folder_class_init(CamelLocalFolderClass * camel_local_folder_class)
+{
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_local_folder_class);
+ CamelObjectClass *oklass = (CamelObjectClass *)camel_local_folder_class;
+
+ /* virtual method definition */
+
+ /* virtual method overload */
+ oklass->getv = local_getv;
+ oklass->setv = local_setv;
+
+ camel_folder_class->refresh_info = local_refresh_info;
+ camel_folder_class->sync = local_sync;
+ camel_folder_class->expunge = local_expunge;
+
+ camel_folder_class->search_by_expression = local_search_by_expression;
+ camel_folder_class->search_by_uids = local_search_by_uids;
+ camel_folder_class->search_free = local_search_free;
+
+ camel_folder_class->delete = local_delete;
+ camel_folder_class->rename = local_rename;
+
+ camel_local_folder_class->get_full_path = local_get_full_path;
+ camel_local_folder_class->get_meta_path = local_get_meta_path;
+
+ camel_local_folder_class->lock = local_lock;
+ camel_local_folder_class->unlock = local_unlock;
+}
+
+static void
+local_init(gpointer object, gpointer klass)
+{
+ CamelFolder *folder = object;
+ CamelLocalFolder *local_folder = object;
+
+ folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY |
+ CAMEL_FOLDER_HAS_SEARCH_CAPABILITY);
+
+ folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
+ CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT |
+ CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_ANSWERED_ALL | CAMEL_MESSAGE_USER;
+
+ folder->summary = NULL;
+ local_folder->search = NULL;
+
+ local_folder->priv = g_malloc0(sizeof(*local_folder->priv));
+ local_folder->priv->search_lock = g_mutex_new();
+}
+
+static void
+local_finalize(CamelObject * object)
+{
+ CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER(object);
+ CamelFolder *folder = (CamelFolder *)object;
+
+ if (folder->summary) {
+ camel_local_summary_sync((CamelLocalSummary *)folder->summary, FALSE, local_folder->changes, NULL);
+ camel_object_unref((CamelObject *)folder->summary);
+ folder->summary = NULL;
+ }
+
+ if (local_folder->search) {
+ camel_object_unref((CamelObject *)local_folder->search);
+ }
+
+ if (local_folder->index)
+ camel_object_unref((CamelObject *)local_folder->index);
+
+ while (local_folder->locked> 0)
+ camel_local_folder_unlock(local_folder);
+
+ g_free(local_folder->base_path);
+ g_free(local_folder->folder_path);
+ g_free(local_folder->summary_path);
+ g_free(local_folder->index_path);
+
+ camel_folder_change_info_free(local_folder->changes);
+
+ g_mutex_free(local_folder->priv->search_lock);
+
+ g_free(local_folder->priv);
+}
+
+static CamelProperty local_property_list[] = {
+ { CAMEL_LOCAL_FOLDER_INDEX_BODY, "index_body", N_("Index message body data") },
+};
+
+CamelType
+camel_local_folder_get_type(void)
+{
+ static CamelType camel_local_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_local_folder_type == CAMEL_INVALID_TYPE) {
+ int i;
+
+ parent_class = (CamelFolderClass *)camel_folder_get_type();
+ camel_local_folder_type = camel_type_register(camel_folder_get_type(), "CamelLocalFolder",
+ sizeof(CamelLocalFolder),
+ sizeof(CamelLocalFolderClass),
+ (CamelObjectClassInitFunc) camel_local_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) local_init,
+ (CamelObjectFinalizeFunc) local_finalize);
+
+ for (i=0;i<sizeof(local_property_list)/sizeof(local_property_list[0]);i++) {
+ local_property_list[i].description = _(local_property_list[i].description);
+ local_folder_properties = g_slist_prepend(local_folder_properties, &local_property_list[i]);
+ }
+ }
+
+ return camel_local_folder_type;
+}
+
+CamelLocalFolder *
+camel_local_folder_construct(CamelLocalFolder *lf, CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *fi;
+ CamelFolder *folder;
+ const char *root_dir_path, *name;
+ char *tmp, *statepath;
+ char folder_path[PATH_MAX];
+ struct stat st;
+ int forceindex, len;
+ CamelURL *url;
+
+ folder = (CamelFolder *)lf;
+
+ name = strrchr(full_name, '/');
+ if (name)
+ name++;
+ else
+ name = full_name;
+
+ camel_folder_construct(folder, parent_store, full_name, name);
+
+ root_dir_path = camel_local_store_get_toplevel_dir(CAMEL_LOCAL_STORE(folder->parent_store));
+ /* strip the trailing '/' which is always present */
+ len = strlen (root_dir_path);
+ tmp = g_alloca (len + 1);
+ strcpy (tmp, root_dir_path);
+ if (len>1 && tmp[len-1] == '/')
+ tmp[len-1] = 0;
+
+ lf->base_path = g_strdup(root_dir_path);
+
+ /* if the base store points to a file, then use that */
+ if (stat(tmp, &st) != -1 && S_ISREG(st.st_mode)) {
+ lf->folder_path = g_strdup(tmp);
+ /* not really sure to do with these for now? */
+ lf->summary_path = g_strdup_printf("%s.ev-summary", tmp);
+ lf->index_path = g_strdup_printf("%s.ibex", tmp);
+ statepath = g_strdup_printf("%s.cmeta", tmp);
+ } else {
+ lf->folder_path = CLOCALF_CLASS(lf)->get_full_path(lf, root_dir_path, full_name);
+ lf->summary_path = CLOCALF_CLASS(lf)->get_meta_path(lf, root_dir_path, full_name, ".ev-summary");
+ lf->index_path = CLOCALF_CLASS(lf)->get_meta_path(lf, root_dir_path, full_name, ".ibex");
+ statepath = CLOCALF_CLASS(lf)->get_meta_path(lf, root_dir_path, full_name, ".cmeta");
+ }
+ camel_object_set(lf, NULL, CAMEL_OBJECT_STATE_FILE, statepath, NULL);
+ g_free(statepath);
+
+ lf->flags = flags;
+
+ if (camel_object_state_read(lf) == -1) {
+ /* No metadata - load defaults and persitify */
+ camel_object_set(lf, NULL, CAMEL_LOCAL_FOLDER_INDEX_BODY, TRUE, 0);
+ camel_object_state_write(lf);
+ }
+
+ /* follow any symlinks to the mailbox */
+ if (lstat (lf->folder_path, &st) != -1 && S_ISLNK (st.st_mode) &&
+ realpath (lf->folder_path, folder_path) != NULL) {
+ g_free (lf->folder_path);
+ lf->folder_path = g_strdup (folder_path);
+ }
+
+ lf->changes = camel_folder_change_info_new();
+
+ /* TODO: Remove the following line, it is a temporary workaround to remove
+ the old-format 'ibex' files that might be lying around */
+ unlink(lf->index_path);
+
+ /* FIXME: Need to run indexing off of the setv method */
+
+ /* if we have no/invalid index file, force it */
+ forceindex = camel_text_index_check(lf->index_path) == -1;
+ if (lf->flags & CAMEL_STORE_FOLDER_BODY_INDEX) {
+ int flag = O_RDWR|O_CREAT;
+
+ if (forceindex)
+ flag |= O_TRUNC;
+
+ lf->index = (CamelIndex *)camel_text_index_new(lf->index_path, flag);
+ if (lf->index == NULL) {
+ /* yes, this isn't fatal at all */
+ g_warning("Could not open/create index file: %s: indexing not performed", strerror (errno));
+ forceindex = FALSE;
+ /* record that we dont have an index afterall */
+ lf->flags &= ~CAMEL_STORE_FOLDER_BODY_INDEX;
+ }
+ } else {
+ /* if we do have an index file, remove it (?) */
+ if (forceindex == FALSE)
+ camel_text_index_remove(lf->index_path);
+ forceindex = FALSE;
+ }
+
+ folder->summary = (CamelFolderSummary *)CLOCALF_CLASS(lf)->create_summary(lf, lf->summary_path, lf->folder_path, lf->index);
+ if (camel_local_summary_load((CamelLocalSummary *)folder->summary, forceindex, NULL) == -1) {
+ /* ? */
+ }
+
+ /*if (camel_local_summary_check((CamelLocalSummary *)folder->summary, lf->changes, ex) == -1) {*/
+ /* we sync here so that any hard work setting up the folder isn't lost */
+ if (camel_local_summary_sync((CamelLocalSummary *)folder->summary, FALSE, lf->changes, ex) == -1) {
+ camel_object_unref (CAMEL_OBJECT (folder));
+ return NULL;
+ }
+
+ /* TODO: This probably shouldn't be here? */
+ if ((flags & CAMEL_STORE_FOLDER_CREATE) != 0) {
+ url = camel_url_copy (((CamelService *) parent_store)->url);
+ camel_url_set_fragment (url, full_name);
+
+ fi = g_new0 (CamelFolderInfo, 1);
+ fi->full_name = g_strdup (full_name);
+ fi->name = g_strdup (name);
+ fi->uri = camel_url_to_string (url, 0);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->flags = CAMEL_FOLDER_NOCHILDREN;
+
+ camel_url_free (url);
+
+ camel_object_trigger_event(CAMEL_OBJECT (parent_store), "folder_created", fi);
+ camel_folder_info_free(fi);
+ }
+
+ return lf;
+}
+
+/* lock the folder, may be called repeatedly (with matching unlock calls),
+ with type the same or less than the first call */
+int camel_local_folder_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex)
+{
+ if (lf->locked > 0) {
+ /* lets be anal here - its important the code knows what its doing */
+ g_assert(lf->locktype == type || lf->locktype == CAMEL_LOCK_WRITE);
+ } else {
+ if (CLOCALF_CLASS(lf)->lock(lf, type, ex) == -1)
+ return -1;
+ lf->locktype = type;
+ }
+
+ lf->locked++;
+
+ return 0;
+}
+
+/* unlock folder */
+int camel_local_folder_unlock(CamelLocalFolder *lf)
+{
+ g_assert(lf->locked>0);
+ lf->locked--;
+ if (lf->locked == 0)
+ CLOCALF_CLASS(lf)->unlock(lf);
+
+ return 0;
+}
+
+static int
+local_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ CamelFolder *folder = (CamelFolder *)object;
+ int i;
+ guint32 tag;
+
+ for (i=0;i<args->argc;i++) {
+ CamelArgGet *arg = &args->argv[i];
+
+ tag = arg->tag;
+
+ switch (tag & CAMEL_ARG_TAG) {
+ case CAMEL_OBJECT_ARG_DESCRIPTION:
+ if (folder->description == NULL) {
+ char *tmp, *path;
+
+ /* check some common prefixes to shorten the name */
+ tmp = ((CamelService *)folder->parent_store)->url->path;
+ if (tmp == NULL)
+ goto skip;
+
+ path = g_alloca (strlen (tmp) + strlen (folder->full_name) + 1);
+ sprintf (path, "%s/%s", tmp, folder->full_name);
+
+ if ((tmp = getenv("HOME")) && strncmp(tmp, path, strlen(tmp)) == 0)
+ /* $HOME relative path + protocol string */
+ folder->description = g_strdup_printf(_("~%s (%s)"), path+strlen(tmp),
+ ((CamelService *)folder->parent_store)->url->protocol);
+ else if ((tmp = "/var/spool/mail") && strncmp(tmp, path, strlen(tmp)) == 0)
+ /* /var/spool/mail relative path + protocol */
+ folder->description = g_strdup_printf(_("mailbox:%s (%s)"), path+strlen(tmp),
+ ((CamelService *)folder->parent_store)->url->protocol);
+ else if ((tmp = "/var/mail") && strncmp(tmp, path, strlen(tmp)) == 0)
+ folder->description = g_strdup_printf(_("mailbox:%s (%s)"), path+strlen(tmp),
+ ((CamelService *)folder->parent_store)->url->protocol);
+ else
+ /* a full path + protocol */
+ folder->description = g_strdup_printf(_("%s (%s)"), path,
+ ((CamelService *)folder->parent_store)->url->protocol);
+ }
+ *arg->ca_str = folder->description;
+ break;
+
+ case CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES:
+ case CAMEL_FOLDER_ARG_PROPERTIES: {
+ CamelArgGetV props;
+
+ props.argc = 1;
+ props.argv[0] = *arg;
+ ((CamelObjectClass *)parent_class)->getv(object, ex, &props);
+ *arg->ca_ptr = g_slist_concat(*arg->ca_ptr, g_slist_copy(local_folder_properties));
+
+ break; }
+
+ case CAMEL_LOCAL_FOLDER_ARG_INDEX_BODY:
+ /* FIXME: remove this from sotre flags */
+ *arg->ca_int = (((CamelLocalFolder *)folder)->flags & CAMEL_STORE_FOLDER_BODY_INDEX) != 0;
+ break;
+
+ default: skip:
+ continue;
+ }
+
+ arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+ }
+
+ return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
+}
+
+static int
+local_setv(CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+ int i;
+ guint32 tag;
+
+ for (i=0;i<args->argc;i++) {
+ CamelArg *arg = &args->argv[i];
+
+ tag = arg->tag;
+
+ switch (tag & CAMEL_ARG_TAG) {
+ case CAMEL_LOCAL_FOLDER_ARG_INDEX_BODY:
+ /* FIXME: implement */
+ /* TODO: When turning on (off?) the index, we want to launch a task for it,
+ and make sure we dont have multiple tasks doing the same job */
+ if (arg->ca_int)
+ ((CamelLocalFolder *)object)->flags |= CAMEL_STORE_FOLDER_BODY_INDEX;
+ else
+ ((CamelLocalFolder *)object)->flags &= ~CAMEL_STORE_FOLDER_BODY_INDEX;
+ break;
+ default:
+ continue;
+ }
+
+ arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+ }
+
+ return ((CamelObjectClass *)parent_class)->setv(object, ex, args);
+}
+
+static char *
+local_get_full_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name)
+{
+ return g_strdup_printf("%s/%s", toplevel_dir, full_name);
+}
+
+static char *
+local_get_meta_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext)
+{
+ return g_strdup_printf("%s/%s%s", toplevel_dir, full_name, ext);
+}
+
+static int
+local_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex)
+{
+ return 0;
+}
+
+static void
+local_unlock(CamelLocalFolder *lf)
+{
+ /* nothing */
+}
+
+/* for auto-check to work */
+static void
+local_refresh_info(CamelFolder *folder, CamelException *ex)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+
+ if (camel_local_summary_check((CamelLocalSummary *)folder->summary, lf->changes, ex) == -1)
+ return;
+
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+}
+
+static void
+local_sync(CamelFolder *folder, gboolean expunge, CamelException *ex)
+{
+ CamelLocalFolder *lf = CAMEL_LOCAL_FOLDER(folder);
+
+ d(printf("local sync '%s' , expunge=%s\n", folder->full_name, expunge?"true":"false"));
+
+ if (camel_local_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
+ return;
+
+ camel_object_state_write(lf);
+
+ /* if sync fails, we'll pass it up on exit through ex */
+ camel_local_summary_sync((CamelLocalSummary *)folder->summary, expunge, lf->changes, ex);
+ camel_local_folder_unlock(lf);
+
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event(CAMEL_OBJECT(folder), "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+}
+
+static void
+local_expunge(CamelFolder *folder, CamelException *ex)
+{
+ d(printf("expunge\n"));
+
+ /* Just do a sync with expunge, serves the same purpose */
+ /* call the callback directly, to avoid locking problems */
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, TRUE, ex);
+}
+
+static void
+local_delete(CamelFolder *folder)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+
+ if (lf->index)
+ camel_index_delete(lf->index);
+
+ parent_class->delete(folder);
+}
+
+static void
+local_rename(CamelFolder *folder, const char *newname)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ char *statepath;
+
+ d(printf("renaming local folder paths to '%s'\n", newname));
+
+ /* Sync? */
+
+ g_free(lf->folder_path);
+ g_free(lf->summary_path);
+ g_free(lf->index_path);
+
+ lf->folder_path = CLOCALF_CLASS(lf)->get_full_path(lf, lf->base_path, newname);
+ lf->summary_path = CLOCALF_CLASS(lf)->get_meta_path(lf, lf->base_path, newname, ".ev-summary");
+ lf->index_path = CLOCALF_CLASS(lf)->get_meta_path(lf, lf->base_path, newname, ".ibex");
+ statepath = CLOCALF_CLASS(lf)->get_meta_path(lf, lf->base_path, newname, ".cmeta");
+ camel_object_set(lf, NULL, CAMEL_OBJECT_STATE_FILE, statepath, NULL);
+ g_free(statepath);
+
+ /* FIXME: Poke some internals, sigh */
+ camel_folder_summary_set_filename(folder->summary, lf->summary_path);
+ g_free(((CamelLocalSummary *)folder->summary)->folder_path);
+ ((CamelLocalSummary *)folder->summary)->folder_path = g_strdup(lf->folder_path);
+
+ parent_class->rename(folder, newname);
+}
+
+static GPtrArray *
+local_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex)
+{
+ CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER(folder);
+ GPtrArray *matches;
+
+ CAMEL_LOCAL_FOLDER_LOCK(folder, search_lock);
+
+ if (local_folder->search == NULL)
+ local_folder->search = camel_folder_search_new();
+
+ camel_folder_search_set_folder(local_folder->search, folder);
+ camel_folder_search_set_body_index(local_folder->search, local_folder->index);
+ matches = camel_folder_search_search(local_folder->search, expression, NULL, ex);
+
+ CAMEL_LOCAL_FOLDER_UNLOCK(folder, search_lock);
+
+ return matches;
+}
+
+static GPtrArray *
+local_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
+{
+ CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER(folder);
+ GPtrArray *matches;
+
+ if (uids->len == 0)
+ return g_ptr_array_new();
+
+ CAMEL_LOCAL_FOLDER_LOCK(folder, search_lock);
+
+ if (local_folder->search == NULL)
+ local_folder->search = camel_folder_search_new();
+
+ camel_folder_search_set_folder(local_folder->search, folder);
+ camel_folder_search_set_body_index(local_folder->search, local_folder->index);
+ matches = camel_folder_search_search(local_folder->search, expression, uids, ex);
+
+ CAMEL_LOCAL_FOLDER_UNLOCK(folder, search_lock);
+
+ return matches;
+}
+
+static void
+local_search_free(CamelFolder *folder, GPtrArray * result)
+{
+ CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER(folder);
+
+ /* we need to lock this free because of the way search_free_result works */
+ /* FIXME: put the lock inside search_free_result */
+ CAMEL_LOCAL_FOLDER_LOCK(folder, search_lock);
+
+ camel_folder_search_free_result(local_folder->search, result);
+
+ CAMEL_LOCAL_FOLDER_UNLOCK(folder, search_lock);
+}
diff --git a/camel/providers/local/camel-local-folder.h b/camel/providers/local/camel-local-folder.h
new file mode 100644
index 0000000000..4aec420813
--- /dev/null
+++ b/camel/providers/local/camel-local-folder.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Author: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999 Ximian (www.ximian.com/).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef CAMEL_LOCAL_FOLDER_H
+#define CAMEL_LOCAL_FOLDER_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus }*/
+
+#include <camel/camel-folder.h>
+#include <camel/camel-folder-search.h>
+#include <camel/camel-index.h>
+#include "camel-local-summary.h"
+#include "camel-lock.h"
+
+/* #include "camel-store.h" */
+
+#define CAMEL_LOCAL_FOLDER_TYPE (camel_local_folder_get_type ())
+#define CAMEL_LOCAL_FOLDER(obj) (CAMEL_CHECK_CAST((obj), CAMEL_LOCAL_FOLDER_TYPE, CamelLocalFolder))
+#define CAMEL_LOCAL_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_LOCAL_FOLDER_TYPE, CamelLocalFolderClass))
+#define CAMEL_IS_LOCAL_FOLDER(o) (CAMEL_CHECK_TYPE((o), CAMEL_LOCAL_FOLDER_TYPE))
+
+enum {
+ CAMEL_LOCAL_FOLDER_ARG_INDEX_BODY = CAMEL_FOLDER_ARG_LAST,
+
+ CAMEL_LOCAL_FOLDER_ARG_LAST = CAMEL_FOLDER_ARG_LAST + 0x100
+};
+
+enum {
+ CAMEL_LOCAL_FOLDER_INDEX_BODY = CAMEL_LOCAL_FOLDER_ARG_INDEX_BODY | CAMEL_ARG_BOO,
+};
+
+typedef struct {
+ CamelFolder parent_object;
+ struct _CamelLocalFolderPrivate *priv;
+
+ guint32 flags; /* open mode flags */
+
+ int locked; /* lock counter */
+ CamelLockType locktype; /* what type of lock we have */
+
+ char *base_path; /* base path of the local folder */
+ char *folder_path; /* the path to the folder itself */
+ char *summary_path; /* where the summary lives */
+ char *index_path; /* where the index file lives */
+
+ CamelIndex *index; /* index for this folder */
+ CamelFolderSearch *search; /* used to run searches, we just use the real thing (tm) */
+ CamelFolderChangeInfo *changes; /* used to store changes to the folder during processing */
+} CamelLocalFolder;
+
+typedef struct {
+ CamelFolderClass parent_class;
+
+ /* Virtual methods */
+
+ /* path construction, only used at init */
+ char * (* get_full_path)(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name);
+ char * (* get_meta_path)(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext);
+
+ /* summary factory, only used at init */
+ CamelLocalSummary *(*create_summary)(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index);
+
+ /* Lock the folder for my operations */
+ int (*lock)(CamelLocalFolder *, CamelLockType type, CamelException *ex);
+
+ /* Unlock the folder for my operations */
+ void (*unlock)(CamelLocalFolder *);
+} CamelLocalFolderClass;
+
+
+/* public methods */
+/* flags are taken from CAMEL_STORE_FOLDER_* flags */
+CamelLocalFolder *camel_local_folder_construct(CamelLocalFolder *lf, CamelStore *parent_store,
+ const char *full_name, guint32 flags, CamelException *ex);
+
+/* Standard Camel function */
+CamelType camel_local_folder_get_type(void);
+
+/* Lock the folder for internal use. May be called repeatedly */
+/* UNIMPLEMENTED */
+int camel_local_folder_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex);
+int camel_local_folder_unlock(CamelLocalFolder *lf);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_LOCAL_FOLDER_H */
diff --git a/camel/providers/local/camel-maildir-folder.c b/camel/providers/local/camel-maildir-folder.c
new file mode 100644
index 0000000000..ba42b75f28
--- /dev/null
+++ b/camel/providers/local/camel-maildir-folder.c
@@ -0,0 +1,278 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999, 2003 Ximian Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "camel-maildir-folder.h"
+#include "camel-maildir-store.h"
+#include "camel-stream-fs.h"
+#include "camel-maildir-summary.h"
+#include "camel-data-wrapper.h"
+#include "camel-mime-message.h"
+#include "camel-exception.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+static CamelLocalFolderClass *parent_class = NULL;
+
+/* Returns the class for a CamelMaildirFolder */
+#define CMAILDIRF_CLASS(so) CAMEL_MAILDIR_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CMAILDIRS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static CamelLocalSummary *maildir_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index);
+
+static void maildir_append_message(CamelFolder * folder, CamelMimeMessage * message, const CamelMessageInfo *info, char **appended_uid, CamelException * ex);
+static CamelMimeMessage *maildir_get_message(CamelFolder * folder, const gchar * uid, CamelException * ex);
+
+static void maildir_finalize(CamelObject * object);
+
+static int
+maildir_folder_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ CamelFolder *folder = (CamelFolder *)object;
+ int i;
+ guint32 tag;
+
+ for (i=0;i<args->argc;i++) {
+ CamelArgGet *arg = &args->argv[i];
+
+ tag = arg->tag;
+
+ switch (tag & CAMEL_ARG_TAG) {
+ case CAMEL_FOLDER_ARG_NAME:
+ if (!strcmp(folder->full_name, "."))
+ *arg->ca_str = _("Inbox");
+ else
+ *arg->ca_str = folder->name;
+ break;
+ default:
+ continue;
+ }
+
+ arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+ }
+
+ return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
+}
+
+static void camel_maildir_folder_class_init(CamelObjectClass * camel_maildir_folder_class)
+{
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_maildir_folder_class);
+ CamelLocalFolderClass *lclass = (CamelLocalFolderClass *)camel_maildir_folder_class;
+
+ parent_class = CAMEL_LOCAL_FOLDER_CLASS (camel_type_get_global_classfuncs(camel_local_folder_get_type()));
+
+ /* virtual method definition */
+
+ /* virtual method overload */
+ ((CamelObjectClass *)camel_folder_class)->getv = maildir_folder_getv;
+
+ camel_folder_class->append_message = maildir_append_message;
+ camel_folder_class->get_message = maildir_get_message;
+
+ lclass->create_summary = maildir_create_summary;
+}
+
+static void maildir_init(gpointer object, gpointer klass)
+{
+ /*CamelFolder *folder = object;
+ CamelMaildirFolder *maildir_folder = object;*/
+}
+
+static void maildir_finalize(CamelObject * object)
+{
+ /*CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER(object);*/
+}
+
+CamelType camel_maildir_folder_get_type(void)
+{
+ static CamelType camel_maildir_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_maildir_folder_type == CAMEL_INVALID_TYPE) {
+ camel_maildir_folder_type = camel_type_register(CAMEL_LOCAL_FOLDER_TYPE, "CamelMaildirFolder",
+ sizeof(CamelMaildirFolder),
+ sizeof(CamelMaildirFolderClass),
+ (CamelObjectClassInitFunc) camel_maildir_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) maildir_init,
+ (CamelObjectFinalizeFunc) maildir_finalize);
+ }
+
+ return camel_maildir_folder_type;
+}
+
+CamelFolder *
+camel_maildir_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ d(printf("Creating maildir folder: %s\n", full_name));
+
+ folder = (CamelFolder *)camel_object_new(CAMEL_MAILDIR_FOLDER_TYPE);
+
+ if (parent_store->flags & CAMEL_STORE_FILTER_INBOX
+ && strcmp(full_name, ".") == 0)
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+
+ folder = (CamelFolder *)camel_local_folder_construct((CamelLocalFolder *)folder,
+ parent_store, full_name, flags, ex);
+
+ return folder;
+}
+
+static CamelLocalSummary *maildir_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index)
+{
+ return (CamelLocalSummary *)camel_maildir_summary_new(path, folder, index);
+}
+
+static void
+maildir_append_message (CamelFolder *folder, CamelMimeMessage *message, const CamelMessageInfo *info, char **appended_uid, CamelException *ex)
+{
+ CamelMaildirFolder *maildir_folder = (CamelMaildirFolder *)folder;
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ CamelStream *output_stream;
+ CamelMessageInfo *mi;
+ CamelMaildirMessageInfo *mdi;
+ char *name, *dest = NULL;
+
+ d(printf("Appending message\n"));
+
+ /* add it to the summary/assign the uid, etc */
+ mi = camel_local_summary_add((CamelLocalSummary *)folder->summary, message, info, lf->changes, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ mdi = (CamelMaildirMessageInfo *)mi;
+
+ d(printf("Appending message: uid is %s filename is %s\n", camel_message_info_uid(mi), mdi->filename));
+
+ /* write it out to tmp, use the uid we got from the summary */
+ name = g_strdup_printf ("%s/tmp/%s", lf->folder_path, camel_message_info_uid(mi));
+ output_stream = camel_stream_fs_new_with_name (name, O_WRONLY|O_CREAT, 0600);
+ if (output_stream == NULL)
+ goto fail_write;
+
+ if (camel_data_wrapper_write_to_stream ((CamelDataWrapper *)message, output_stream) == -1
+ || camel_stream_close (output_stream) == -1)
+ goto fail_write;
+
+ /* now move from tmp to cur (bypass new, does it matter?) */
+ dest = g_strdup_printf("%s/cur/%s", lf->folder_path, camel_maildir_info_filename (mdi));
+ if (rename (name, dest) == 1)
+ goto fail_write;
+
+ g_free (dest);
+ g_free (name);
+
+ camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed",
+ ((CamelLocalFolder *)maildir_folder)->changes);
+ camel_folder_change_info_clear (((CamelLocalFolder *)maildir_folder)->changes);
+
+ if (appended_uid)
+ *appended_uid = g_strdup(camel_message_info_uid(mi));
+
+ return;
+
+ fail_write:
+
+ /* remove the summary info so we are not out-of-sync with the mh folder */
+ camel_folder_summary_remove_uid (CAMEL_FOLDER_SUMMARY (folder->summary),
+ camel_message_info_uid (mi));
+
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Maildir append message cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot append message to maildir folder: %s: %s"),
+ name, g_strerror (errno));
+
+ if (output_stream) {
+ camel_object_unref (CAMEL_OBJECT (output_stream));
+ unlink (name);
+ }
+
+ g_free (name);
+ g_free (dest);
+}
+
+static CamelMimeMessage *maildir_get_message(CamelFolder * folder, const gchar * uid, CamelException * ex)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ CamelStream *message_stream = NULL;
+ CamelMimeMessage *message = NULL;
+ CamelMessageInfo *info;
+ char *name;
+ CamelMaildirMessageInfo *mdi;
+
+ d(printf("getting message: %s\n", uid));
+
+ /* get the message summary info */
+ if ((info = camel_folder_summary_uid(folder->summary, uid)) == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s from folder %s\n %s"),
+ uid, lf->folder_path, _("No such message"));
+ return NULL;
+ }
+
+ mdi = (CamelMaildirMessageInfo *)info;
+
+ /* what do we do if the message flags (and :info data) changes? filename mismatch - need to recheck I guess */
+ name = g_strdup_printf("%s/cur/%s", lf->folder_path, camel_maildir_info_filename(mdi));
+
+ camel_folder_summary_info_free(folder->summary, info);
+
+ if ((message_stream = camel_stream_fs_new_with_name(name, O_RDONLY, 0)) == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get message: %s from folder %s\n %s"),
+ uid, lf->folder_path, g_strerror(errno));
+ g_free(name);
+ return NULL;
+ }
+
+ message = camel_mime_message_new();
+ if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)message, message_stream) == -1) {
+ camel_exception_setv(ex, (errno==EINTR)?CAMEL_EXCEPTION_USER_CANCEL:CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get message: %s from folder %s\n %s"),
+ uid, lf->folder_path, _("Invalid message contents"));
+ g_free(name);
+ camel_object_unref((CamelObject *)message_stream);
+ camel_object_unref((CamelObject *)message);
+ return NULL;
+
+ }
+ camel_object_unref((CamelObject *)message_stream);
+ g_free(name);
+
+ return message;
+}
diff --git a/camel/providers/local/camel-maildir-store.c b/camel/providers/local/camel-maildir-store.c
new file mode 100644
index 0000000000..21ad98694f
--- /dev/null
+++ b/camel/providers/local/camel-maildir-store.c
@@ -0,0 +1,544 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2000 Ximian, Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dirent.h>
+
+#include "camel-maildir-store.h"
+#include "camel-maildir-folder.h"
+#include "camel-exception.h"
+#include "camel-url.h"
+#include "camel-private.h"
+#include "camel-maildir-summary.h"
+
+#define d(x)
+
+static CamelLocalStoreClass *parent_class = NULL;
+
+/* Returns the class for a CamelMaildirStore */
+#define CMAILDIRS_CLASS(so) CAMEL_MAILDIR_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CMAILDIRF_CLASS(so) CAMEL_MAILDIR_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
+static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
+static void delete_folder(CamelStore * store, const char *folder_name, CamelException * ex);
+static void maildir_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
+
+static CamelFolderInfo * get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+
+static gboolean maildir_compare_folder_name(const void *a, const void *b);
+static guint maildir_hash_folder_name(const void *a);
+
+static void camel_maildir_store_class_init(CamelObjectClass * camel_maildir_store_class)
+{
+ CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_maildir_store_class);
+ /*CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_maildir_store_class);*/
+
+ parent_class = (CamelLocalStoreClass *)camel_type_get_global_classfuncs(camel_local_store_get_type());
+
+ /* virtual method overload, use defaults for most */
+ camel_store_class->hash_folder_name = maildir_hash_folder_name;
+ camel_store_class->compare_folder_name = maildir_compare_folder_name;
+ camel_store_class->get_folder = get_folder;
+ camel_store_class->get_inbox = get_inbox;
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = maildir_rename_folder;
+
+ camel_store_class->get_folder_info = get_folder_info;
+ camel_store_class->free_folder_info = camel_store_free_folder_info_full;
+}
+
+CamelType camel_maildir_store_get_type(void)
+{
+ static CamelType camel_maildir_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_maildir_store_type == CAMEL_INVALID_TYPE) {
+ camel_maildir_store_type = camel_type_register(CAMEL_LOCAL_STORE_TYPE, "CamelMaildirStore",
+ sizeof(CamelMaildirStore),
+ sizeof(CamelMaildirStoreClass),
+ (CamelObjectClassInitFunc) camel_maildir_store_class_init,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ return camel_maildir_store_type;
+}
+
+/* This fixes up some historical cruft of names starting with "./" */
+static const char *
+md_canon_name(const char *a)
+{
+ if (a != NULL) {
+ if (a[0] == '/')
+ a++;
+ if (a[0] == '.' && a[1] == '/')
+ a+=2;
+ }
+ return a;
+}
+
+static guint maildir_hash_folder_name(const void *a)
+{
+ return g_str_hash(md_canon_name(a));
+}
+
+static gboolean maildir_compare_folder_name(const void *a, const void *b)
+{
+ return g_str_equal(md_canon_name(a), md_canon_name(b));
+}
+
+static CamelFolder *
+get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
+{
+ char *name, *tmp, *cur, *new;
+ struct stat st;
+ CamelFolder *folder = NULL;
+
+ folder_name = md_canon_name(folder_name);
+
+ if (!((CamelStoreClass *)parent_class)->get_folder(store, folder_name, flags, ex))
+ return NULL;
+
+ name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
+ tmp = g_strdup_printf("%s/tmp", name);
+ cur = g_strdup_printf("%s/cur", name);
+ new = g_strdup_printf("%s/new", name);
+
+ if (!strcmp(folder_name, ".")) {
+ /* special case "." (aka inbox), may need to be created */
+ if (stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)
+ || stat(cur, &st) != 0 || !S_ISDIR(st.st_mode)
+ || stat(new, &st) != 0 || !S_ISDIR(st.st_mode)) {
+ if (mkdir(tmp, 0700) != 0
+ || mkdir(cur, 0700) != 0
+ || mkdir(new, 0700) != 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': %s"),
+ folder_name, g_strerror(errno));
+ rmdir(tmp);
+ rmdir(cur);
+ rmdir(new);
+ goto fail;
+ }
+ }
+ folder = camel_maildir_folder_new(store, folder_name, flags, ex);
+ } else if (stat(name, &st) == -1) {
+ /* folder doesn't exist, see if we should create it */
+ if (errno != ENOENT) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get folder `%s': %s"),
+ folder_name, g_strerror (errno));
+ } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot get folder `%s': folder does not exist."),
+ folder_name);
+ } else {
+ if (mkdir(name, 0700) != 0
+ || mkdir(tmp, 0700) != 0
+ || mkdir(cur, 0700) != 0
+ || mkdir(new, 0700) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': %s"),
+ folder_name, g_strerror (errno));
+ rmdir(tmp);
+ rmdir(cur);
+ rmdir(new);
+ rmdir(name);
+ } else {
+ folder = camel_maildir_folder_new(store, folder_name, flags, ex);
+ }
+ }
+ } else if (!S_ISDIR(st.st_mode)
+ || stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)
+ || stat(cur, &st) != 0 || !S_ISDIR(st.st_mode)
+ || stat(new, &st) != 0 || !S_ISDIR(st.st_mode)) {
+ /* folder exists, but not maildir */
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get folder `%s': not a maildir directory."), name);
+ } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': folder exists."),
+ folder_name);
+ } else {
+ folder = camel_maildir_folder_new(store, folder_name, flags, ex);
+ }
+fail:
+ g_free(name);
+ g_free(tmp);
+ g_free(cur);
+ g_free(new);
+
+ return folder;
+}
+
+static CamelFolder *
+get_inbox (CamelStore *store, CamelException *ex)
+{
+ return camel_store_get_folder(store, ".", CAMEL_STORE_FOLDER_CREATE, ex);
+}
+
+static void delete_folder(CamelStore * store, const char *folder_name, CamelException * ex)
+{
+ char *name, *tmp, *cur, *new;
+ struct stat st;
+
+ if (strcmp(folder_name, ".") == 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot delete folder: %s: Invalid operation"), _("Inbox"));
+ return;
+ }
+
+ name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
+
+ tmp = g_strdup_printf("%s/tmp", name);
+ cur = g_strdup_printf("%s/cur", name);
+ new = g_strdup_printf("%s/new", name);
+
+ if (stat(name, &st) == -1 || !S_ISDIR(st.st_mode)
+ || stat(tmp, &st) == -1 || !S_ISDIR(st.st_mode)
+ || stat(cur, &st) == -1 || !S_ISDIR(st.st_mode)
+ || stat(new, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder `%s': %s"),
+ folder_name, errno ? g_strerror (errno) :
+ _("not a maildir directory"));
+ } else {
+ int err = 0;
+
+ /* remove subdirs first - will fail if not empty */
+ if (rmdir(cur) == -1 || rmdir(new) == -1) {
+ err = errno;
+ } else {
+ DIR *dir;
+ struct dirent *d;
+
+ /* for tmp (only), its contents is irrelevant */
+ dir = opendir(tmp);
+ if (dir) {
+ while ( (d=readdir(dir)) ) {
+ char *name = d->d_name, *file;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ continue;
+ file = g_strdup_printf("%s/%s", tmp, name);
+ unlink(file);
+ g_free(file);
+ }
+ closedir(dir);
+ }
+ if (rmdir(tmp) == -1 || rmdir(name) == -1)
+ err = errno;
+ }
+
+ if (err != 0) {
+ /* easier just to mkdir all (and let them fail), than remember what we got to */
+ mkdir(name, 0700);
+ mkdir(cur, 0700);
+ mkdir(new, 0700);
+ mkdir(tmp, 0700);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder `%s': %s"),
+ folder_name, g_strerror (err));
+ } else {
+ /* and remove metadata */
+ ((CamelStoreClass *)parent_class)->delete_folder(store, folder_name, ex);
+ }
+ }
+
+ g_free(name);
+ g_free(tmp);
+ g_free(cur);
+ g_free(new);
+}
+
+static void
+maildir_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
+{
+ if (strcmp(old, ".") == 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot rename folder: %s: Invalid operation"), _("Inbox"));
+ return;
+ }
+
+ ((CamelStoreClass *)parent_class)->rename_folder(store, old, new, ex);
+}
+
+static void
+fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
+{
+ CamelFolder *folder;
+
+ folder = camel_object_bag_get(store->folders, fi->full_name);
+
+ if (folder == NULL
+ && (flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ folder = camel_store_get_folder(store, fi->full_name, 0, NULL);
+
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ camel_folder_refresh_info(folder, NULL);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ camel_object_unref(folder);
+ } else {
+ char *path, *folderpath;
+ CamelFolderSummary *s;
+ const char *root;
+
+ /* This should be fast enough not to have to test for INFO_FAST */
+ root = camel_local_store_get_toplevel_dir((CamelLocalStore *)store);
+ path = g_strdup_printf("%s/%s.ev-summary", root, fi->full_name);
+ folderpath = g_strdup_printf("%s/%s", root, fi->full_name);
+ s = (CamelFolderSummary *)camel_maildir_summary_new(path, folderpath, NULL);
+ if (camel_folder_summary_header_load(s) != -1) {
+ fi->unread = s->unread_count;
+ fi->total = s->saved_count;
+ }
+ camel_object_unref(s);
+ g_free(folderpath);
+ g_free(path);
+ }
+}
+
+struct _scan_node {
+ struct _scan_node *next;
+ struct _scan_node *prev;
+
+ CamelFolderInfo *fi;
+
+ dev_t dnode;
+ ino_t inode;
+};
+
+static guint scan_hash(const void *d)
+{
+ const struct _scan_node *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean scan_equal(const void *a, const void *b)
+{
+ const struct _scan_node *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void scan_free(void *k, void *v, void *d)
+{
+ g_free(k);
+}
+
+static CamelFolderInfo *scan_fi(CamelStore *store, guint32 flags, CamelURL *url, const char *full, const char *name)
+{
+ CamelFolderInfo *fi;
+ char *tmp, *cur, *new;
+ struct stat st;
+
+ fi = g_malloc0(sizeof(*fi));
+ fi->full_name = g_strdup(full);
+ fi->name = g_strdup(name);
+ camel_url_set_fragment(url, fi->full_name);
+ fi->uri = camel_url_to_string(url, 0);
+ fi->unread = -1;
+ fi->total = -1;
+ /* we only calculate nochildren properly if we're recursive */
+ if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0))
+ fi->flags = CAMEL_FOLDER_NOCHILDREN;
+
+ d(printf("Adding maildir info: '%s' '%s' '%s'\n", fi->name, fi->full_name, fi->uri));
+
+ tmp = g_build_filename(url->path, fi->full_name, "tmp", NULL);
+ cur = g_build_filename(url->path, fi->full_name, "cur", NULL);
+ new = g_build_filename(url->path, fi->full_name, "new", NULL);
+
+ if (!(stat(tmp, &st) == 0 && S_ISDIR(st.st_mode)
+ && stat(cur, &st) == 0 && S_ISDIR(st.st_mode)
+ && stat(new, &st) == 0 && S_ISDIR(st.st_mode)))
+ fi->flags |= CAMEL_FOLDER_NOSELECT;
+
+ g_free(new);
+ g_free(cur);
+ g_free(tmp);
+
+ fill_fi(store, fi, flags);
+
+ return fi;
+}
+
+static int
+scan_dirs(CamelStore *store, guint32 flags, CamelFolderInfo *topfi, CamelURL *url, CamelException *ex)
+{
+ EDList queue = E_DLIST_INITIALISER(queue);
+ struct _scan_node *sn;
+ const char *root = ((CamelService *)store)->url->path;
+ char *tmp;
+ GHashTable *visited;
+ struct stat st;
+ int res = -1;
+
+ visited = g_hash_table_new(scan_hash, scan_equal);
+
+ sn = g_malloc0(sizeof(*sn));
+ sn->fi = topfi;
+ e_dlist_addtail(&queue, (EDListNode *)sn);
+ g_hash_table_insert(visited, sn, sn);
+
+ while (!e_dlist_empty(&queue)) {
+ char *name;
+ DIR *dir;
+ struct dirent *d;
+ CamelFolderInfo *last;
+
+ sn = (struct _scan_node *)e_dlist_remhead(&queue);
+
+ last = (CamelFolderInfo *)&sn->fi->child;
+
+ if (!strcmp(sn->fi->full_name, "."))
+ name = g_strdup(root);
+ else
+ name = g_build_filename(root, sn->fi->full_name, NULL);
+
+ dir = opendir(name);
+ if (dir == NULL) {
+ g_free(name);
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not scan folder `%s': %s"),
+ root, g_strerror(errno));
+ goto fail;
+ }
+
+ while ( (d = readdir(dir)) ) {
+ if (strcmp(d->d_name, "tmp") == 0
+ || strcmp(d->d_name, "cur") == 0
+ || strcmp(d->d_name, "new") == 0
+ || strcmp(d->d_name, ".") == 0
+ || strcmp(d->d_name, "..") == 0)
+ continue;
+
+ tmp = g_build_filename(name, d->d_name, NULL);
+ if (stat(tmp, &st) == 0 && S_ISDIR(st.st_mode)) {
+ struct _scan_node in;
+
+ in.dnode = st.st_dev;
+ in.inode = st.st_ino;
+
+ /* see if we've visited already */
+ if (g_hash_table_lookup(visited, &in) == NULL) {
+ struct _scan_node *snew = g_malloc(sizeof(*snew));
+ char *full;
+
+ snew->dnode = in.dnode;
+ snew->inode = in.inode;
+
+ if (!strcmp(sn->fi->full_name, "."))
+ full = g_strdup(d->d_name);
+ else
+ full = g_strdup_printf("%s/%s", sn->fi->full_name, d->d_name);
+ snew->fi = scan_fi(store, flags, url, full, d->d_name);
+ g_free(full);
+
+ last->next = snew->fi;
+ last = snew->fi;
+ snew->fi->parent = sn->fi;
+
+ sn->fi->flags &= ~CAMEL_FOLDER_NOCHILDREN;
+ sn->fi->flags |= CAMEL_FOLDER_CHILDREN;
+
+ g_hash_table_insert(visited, snew, snew);
+
+ if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0))
+ e_dlist_addtail(&queue, (EDListNode *)snew);
+ }
+ }
+ g_free(tmp);
+ }
+ closedir(dir);
+ }
+
+ res = 0;
+fail:
+ g_hash_table_foreach(visited, scan_free, NULL);
+ g_hash_table_destroy(visited);
+
+ return res;
+}
+
+static CamelFolderInfo *
+get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *fi = NULL;
+ CamelLocalStore *local_store = (CamelLocalStore *)store;
+ CamelURL *url;
+
+ url = camel_url_new("maildir:", NULL);
+ camel_url_set_path(url, ((CamelService *)local_store)->url->path);
+
+ if (top == NULL || top[0] == 0) {
+ CamelFolderInfo *scan;
+
+ /* create a dummy "." parent inbox, use to scan, then put back at the top level */
+ fi = scan_fi(store, flags, url, ".", _("Inbox"));
+ if (scan_dirs(store, flags, fi, url, ex) == -1)
+ goto fail;
+ fi->next = fi->child;
+ scan = fi->child;
+ fi->child = NULL;
+ while (scan) {
+ scan->parent = NULL;
+ scan = scan->next;
+ }
+ fi->flags &= ~CAMEL_FOLDER_CHILDREN;
+ fi->flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS;
+ } else if (!strcmp(top, ".")) {
+ fi = scan_fi(store, flags, url, ".", _("Inbox"));
+ fi->flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS;
+ } else {
+ const char *name = strrchr(top, '/');
+
+ fi = scan_fi(store, flags, url, top, name?name+1:top);
+ if (scan_dirs(store, flags, fi, url, ex) == -1)
+ goto fail;
+ }
+
+ camel_url_free(url);
+
+ return fi;
+
+fail:
+ if (fi)
+ camel_store_free_folder_info_full(store, fi);
+
+ camel_url_free(url);
+
+ return NULL;
+}
diff --git a/camel/providers/local/camel-mbox-folder.c b/camel/providers/local/camel-mbox-folder.c
new file mode 100644
index 0000000000..fa8f0b645d
--- /dev/null
+++ b/camel/providers/local/camel-mbox-folder.c
@@ -0,0 +1,557 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 1999, 2003 Ximian Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "camel-mbox-folder.h"
+#include "camel-mbox-store.h"
+#include "camel-stream-fs.h"
+#include "camel-mbox-summary.h"
+#include "camel-data-wrapper.h"
+#include "camel-mime-message.h"
+#include "camel-stream-filter.h"
+#include "camel-mime-filter-from.h"
+#include "camel-exception.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+static CamelLocalFolderClass *parent_class = NULL;
+
+/* Returns the class for a CamelMboxFolder */
+#define CMBOXF_CLASS(so) CAMEL_MBOX_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CMBOXS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static int mbox_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex);
+static void mbox_unlock(CamelLocalFolder *lf);
+
+#ifdef STATUS_PINE
+static gboolean mbox_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
+#endif
+
+static void mbox_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value);
+static void mbox_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value);
+
+static void mbox_append_message(CamelFolder *folder, CamelMimeMessage * message, const CamelMessageInfo * info, char **appended_uid, CamelException *ex);
+static CamelMimeMessage *mbox_get_message(CamelFolder *folder, const gchar * uid, CamelException *ex);
+static CamelLocalSummary *mbox_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index);
+
+static void mbox_finalise(CamelObject * object);
+
+static void
+camel_mbox_folder_class_init(CamelMboxFolderClass * camel_mbox_folder_class)
+{
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_mbox_folder_class);
+ CamelLocalFolderClass *lclass = (CamelLocalFolderClass *)camel_mbox_folder_class;
+
+ parent_class = (CamelLocalFolderClass *)camel_type_get_global_classfuncs(camel_local_folder_get_type());
+
+ /* virtual method definition */
+
+ /* virtual method overload */
+ camel_folder_class->append_message = mbox_append_message;
+ camel_folder_class->get_message = mbox_get_message;
+
+#ifdef STATUS_PINE
+ camel_folder_class->set_message_flags = mbox_set_message_flags;
+#endif
+ camel_folder_class->set_message_user_flag = mbox_set_message_user_flag;
+ camel_folder_class->set_message_user_tag = mbox_set_message_user_tag;
+
+ lclass->get_full_path = camel_mbox_folder_get_full_path;
+ lclass->get_meta_path = camel_mbox_folder_get_meta_path;
+ lclass->create_summary = mbox_create_summary;
+ lclass->lock = mbox_lock;
+ lclass->unlock = mbox_unlock;
+}
+
+static void
+mbox_init(gpointer object, gpointer klass)
+{
+ /*CamelFolder *folder = object;*/
+ CamelMboxFolder *mbox_folder = object;
+
+ mbox_folder->lockfd = -1;
+}
+
+static void
+mbox_finalise(CamelObject * object)
+{
+ CamelMboxFolder *mbox_folder = (CamelMboxFolder *)object;
+
+ g_assert(mbox_folder->lockfd == -1);
+}
+
+CamelType camel_mbox_folder_get_type(void)
+{
+ static CamelType camel_mbox_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_mbox_folder_type == CAMEL_INVALID_TYPE) {
+ camel_mbox_folder_type = camel_type_register(CAMEL_LOCAL_FOLDER_TYPE, "CamelMboxFolder",
+ sizeof(CamelMboxFolder),
+ sizeof(CamelMboxFolderClass),
+ (CamelObjectClassInitFunc) camel_mbox_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) mbox_init,
+ (CamelObjectFinalizeFunc) mbox_finalise);
+ }
+
+ return camel_mbox_folder_type;
+}
+
+CamelFolder *
+camel_mbox_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ d(printf("Creating mbox folder: %s in %s\n", full_name, camel_local_store_get_toplevel_dir((CamelLocalStore *)parent_store)));
+
+ folder = (CamelFolder *)camel_object_new(CAMEL_MBOX_FOLDER_TYPE);
+ folder = (CamelFolder *)camel_local_folder_construct((CamelLocalFolder *)folder,
+ parent_store, full_name, flags, ex);
+
+ return folder;
+}
+
+char *
+camel_mbox_folder_get_full_path (CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name)
+{
+ const char *inptr = full_name;
+ int subdirs = 0;
+ char *path, *p;
+
+ while (*inptr != '\0') {
+ if (*inptr == '/')
+ subdirs++;
+ inptr++;
+ }
+
+ path = g_malloc (strlen (toplevel_dir) + (inptr - full_name) + (4 * subdirs) + 1);
+ p = g_stpcpy (path, toplevel_dir);
+
+ inptr = full_name;
+ while (*inptr != '\0') {
+ while (*inptr != '/' && *inptr != '\0')
+ *p++ = *inptr++;
+
+ if (*inptr == '/') {
+ p = g_stpcpy (p, ".sbd/");
+ inptr++;
+
+ /* strip extranaeous '/'s */
+ while (*inptr == '/')
+ inptr++;
+ }
+ }
+
+ *p = '\0';
+
+ return path;
+}
+
+char *
+camel_mbox_folder_get_meta_path (CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext)
+{
+/*#define USE_HIDDEN_META_FILES*/
+#ifdef USE_HIDDEN_META_FILES
+ char *name, *slash;
+
+ name = g_alloca (strlen (full_name) + strlen (ext) + 2);
+ if ((slash = strrchr (full_name, '/')))
+ sprintf (name, "%.*s.%s%s", slash - full_name + 1, full_name, slash + 1, ext);
+ else
+ sprintf (name, ".%s%s", full_name, ext);
+
+ return camel_mbox_folder_get_full_path (lf, toplevel_dir, name);
+#else
+ char *full_path, *path;
+
+ full_path = camel_mbox_folder_get_full_path (lf, toplevel_dir, full_name);
+ path = g_strdup_printf ("%s%s", full_path, ext);
+ g_free (full_path);
+
+ return path;
+#endif
+}
+
+static CamelLocalSummary *mbox_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index)
+{
+ return (CamelLocalSummary *)camel_mbox_summary_new(path, folder, index);
+}
+
+static int mbox_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex)
+{
+ CamelMboxFolder *mf = (CamelMboxFolder *)lf;
+
+ /* make sure we have matching unlocks for locks, camel-local-folder class should enforce this */
+ g_assert(mf->lockfd == -1);
+
+ mf->lockfd = open(lf->folder_path, O_RDWR, 0);
+ if (mf->lockfd == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder lock on %s: %s"),
+ lf->folder_path, g_strerror (errno));
+ return -1;
+ }
+
+ if (camel_lock_folder(lf->folder_path, mf->lockfd, type, ex) == -1) {
+ close(mf->lockfd);
+ mf->lockfd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void mbox_unlock(CamelLocalFolder *lf)
+{
+ CamelMboxFolder *mf = (CamelMboxFolder *)lf;
+
+ g_assert(mf->lockfd != -1);
+ camel_unlock_folder(lf->folder_path, mf->lockfd);
+ close(mf->lockfd);
+ mf->lockfd = -1;
+}
+
+static void
+mbox_append_message(CamelFolder *folder, CamelMimeMessage * message, const CamelMessageInfo * info, char **appended_uid, CamelException *ex)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ CamelStream *output_stream = NULL, *filter_stream = NULL;
+ CamelMimeFilter *filter_from = NULL;
+ CamelMboxSummary *mbs = (CamelMboxSummary *)folder->summary;
+ CamelMessageInfo *mi;
+ char *fromline = NULL;
+ int fd, retval;
+ struct stat st;
+#if 0
+ char *xev;
+#endif
+ /* If we can't lock, dont do anything */
+ if (camel_local_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
+ return;
+
+ d(printf("Appending message\n"));
+
+ /* first, check the summary is correct (updates folder_size too) */
+ retval = camel_local_summary_check ((CamelLocalSummary *)folder->summary, lf->changes, ex);
+ if (retval == -1)
+ goto fail;
+
+ /* add it to the summary/assign the uid, etc */
+ mi = camel_local_summary_add((CamelLocalSummary *)folder->summary, message, info, lf->changes, ex);
+ if (mi == NULL)
+ goto fail;
+
+ d(printf("Appending message: uid is %s\n", camel_message_info_uid(mi)));
+
+ output_stream = camel_stream_fs_new_with_name(lf->folder_path, O_WRONLY|O_APPEND, 0600);
+ if (output_stream == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot open mailbox: %s: %s\n"),
+ lf->folder_path, g_strerror (errno));
+ goto fail;
+ }
+
+ /* and we need to set the frompos/XEV explicitly */
+ ((CamelMboxMessageInfo *)mi)->frompos = mbs->folder_size;
+#if 0
+ xev = camel_local_summary_encode_x_evolution((CamelLocalSummary *)folder->summary, mi);
+ if (xev) {
+ /* the x-ev header should match the 'current' flags, no problem, so store as much */
+ camel_medium_set_header((CamelMedium *)message, "X-Evolution", xev);
+ mi->flags &= ~ CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_FLAGGED;
+ g_free(xev);
+ }
+#endif
+
+ /* we must write this to the non-filtered stream ... */
+ fromline = camel_mime_message_build_mbox_from(message);
+ if (camel_stream_write(output_stream, fromline, strlen(fromline)) == -1)
+ goto fail_write;
+
+ /* and write the content to the filtering stream, that translates '\nFrom' into '\n>From' */
+ filter_stream = (CamelStream *) camel_stream_filter_new_with_stream(output_stream);
+ filter_from = (CamelMimeFilter *) camel_mime_filter_from_new();
+ camel_stream_filter_add((CamelStreamFilter *) filter_stream, filter_from);
+ if (camel_data_wrapper_write_to_stream((CamelDataWrapper *)message, filter_stream) == -1
+ || camel_stream_write(filter_stream, "\n", 1) == -1
+ || camel_stream_close(filter_stream) == -1)
+ goto fail_write;
+
+ /* filter stream ref's the output stream itself, so we need to unref it too */
+ camel_object_unref((CamelObject *)filter_from);
+ camel_object_unref((CamelObject *)filter_stream);
+ camel_object_unref((CamelObject *)output_stream);
+ g_free(fromline);
+
+ /* now we 'fudge' the summary to tell it its uptodate, because its idea of uptodate has just changed */
+ /* the stat really shouldn't fail, we just wrote to it */
+ if (stat(lf->folder_path, &st) == 0) {
+ mbs->folder_size = st.st_size;
+ ((CamelFolderSummary *)mbs)->time = st.st_mtime;
+ }
+
+ /* unlock as soon as we can */
+ camel_local_folder_unlock(lf);
+
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+
+ if (appended_uid)
+ *appended_uid = g_strdup(camel_message_info_uid(mi));
+
+ return;
+
+fail_write:
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Mail append cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot append message to mbox file: %s: %s"),
+ lf->folder_path, g_strerror (errno));
+
+ if (filter_stream)
+ camel_object_unref(CAMEL_OBJECT(filter_stream));
+
+ if (output_stream)
+ camel_object_unref(CAMEL_OBJECT(output_stream));
+
+ if (filter_from)
+ camel_object_unref(CAMEL_OBJECT(filter_from));
+
+ g_free(fromline);
+
+ /* reset the file to original size */
+ fd = open(lf->folder_path, O_WRONLY, 0600);
+ if (fd != -1) {
+ ftruncate(fd, mbs->folder_size);
+ close(fd);
+ }
+
+ /* remove the summary info so we are not out-of-sync with the mbox */
+ camel_folder_summary_remove_uid (CAMEL_FOLDER_SUMMARY (mbs), camel_message_info_uid (mi));
+
+ /* and tell the summary its uptodate */
+ if (stat(lf->folder_path, &st) == 0) {
+ mbs->folder_size = st.st_size;
+ ((CamelFolderSummary *)mbs)->time = st.st_mtime;
+ }
+
+fail:
+ /* make sure we unlock the folder - before we start triggering events into appland */
+ camel_local_folder_unlock(lf);
+
+ /* cascade the changes through, anyway, if there are any outstanding */
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+}
+
+static CamelMimeMessage *
+mbox_get_message(CamelFolder *folder, const gchar * uid, CamelException *ex)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ CamelMimeMessage *message = NULL;
+ CamelMboxMessageInfo *info;
+ CamelMimeParser *parser = NULL;
+ int fd, retval;
+ int retried = FALSE;
+ off_t frompos;
+
+ d(printf("Getting message %s\n", uid));
+
+ /* lock the folder first, burn if we can't, need write lock for summary check */
+ if (camel_local_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
+ return NULL;
+
+ /* check for new messages always */
+ if (camel_local_summary_check((CamelLocalSummary *)folder->summary, lf->changes, ex) == -1) {
+ camel_local_folder_unlock(lf);
+ return NULL;
+ }
+
+retry:
+ /* get the message summary info */
+ info = (CamelMboxMessageInfo *) camel_folder_summary_uid(folder->summary, uid);
+
+ if (info == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s from folder %s\n %s"),
+ uid, lf->folder_path, _("No such message"));
+ goto fail;
+ }
+
+ /* no frompos, its an error in the library (and we can't do anything with it) */
+ g_assert(info->frompos != -1);
+
+ frompos = info->frompos;
+ camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)info);
+
+ /* we use an fd instead of a normal stream here - the reason is subtle, camel_mime_part will cache
+ the whole message in memory if the stream is non-seekable (which it is when built from a parser
+ with no stream). This means we dont have to lock the mbox for the life of the message, but only
+ while it is being created. */
+
+ fd = open(lf->folder_path, O_RDONLY);
+ if (fd == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get message: %s from folder %s\n %s"),
+ uid, lf->folder_path, g_strerror (errno));
+ goto fail;
+ }
+
+ /* we use a parser to verify the message is correct, and in the correct position */
+ parser = camel_mime_parser_new();
+ camel_mime_parser_init_with_fd(parser, fd);
+ camel_mime_parser_scan_from(parser, TRUE);
+
+ camel_mime_parser_seek(parser, frompos, SEEK_SET);
+ if (camel_mime_parser_step(parser, NULL, NULL) != CAMEL_MIME_PARSER_STATE_FROM
+ || camel_mime_parser_tell_start_from(parser) != frompos) {
+
+ g_warning("Summary doesn't match the folder contents! eek!\n"
+ " expecting offset %ld got %ld, state = %d", (long int)frompos,
+ (long int)camel_mime_parser_tell_start_from(parser),
+ camel_mime_parser_state(parser));
+
+ camel_object_unref((CamelObject *)parser);
+ parser = NULL;
+
+ if (!retried) {
+ retried = TRUE;
+ camel_local_summary_check_force((CamelLocalSummary *)folder->summary);
+ retval = camel_local_summary_check((CamelLocalSummary *)folder->summary, lf->changes, ex);
+ if (retval != -1)
+ goto retry;
+ }
+
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("Cannot get message: %s from folder %s\n %s"), uid, lf->folder_path,
+ _("The folder appears to be irrecoverably corrupted."));
+ goto fail;
+ }
+
+ message = camel_mime_message_new();
+ if (camel_mime_part_construct_from_parser((CamelMimePart *)message, parser) == -1) {
+ camel_exception_setv(ex, errno==EINTR?CAMEL_EXCEPTION_USER_CANCEL:CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get message: %s from folder %s\n %s"), uid, lf->folder_path,
+ _("Message construction failed."));
+ camel_object_unref((CamelObject *)message);
+ message = NULL;
+ goto fail;
+ }
+
+ camel_medium_remove_header((CamelMedium *)message, "X-Evolution");
+fail:
+ /* and unlock now we're finished with it */
+ camel_local_folder_unlock(lf);
+
+ if (parser)
+ camel_object_unref((CamelObject *)parser);
+
+ /* use the opportunity to notify of changes (particularly if we had a rebuild) */
+ if (camel_folder_change_info_changed(lf->changes)) {
+ camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
+ camel_folder_change_info_clear(lf->changes);
+ }
+
+ return message;
+}
+
+#ifdef STATUS_PINE
+static gboolean
+mbox_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
+{
+ /* Basically, if anything could change the Status line, presume it does */
+ if (((CamelMboxSummary *)folder->summary)->xstatus
+ && (flags & (CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_ANSWERED|CAMEL_MESSAGE_DELETED))) {
+ flags |= CAMEL_MESSAGE_FOLDER_XEVCHANGE|CAMEL_MESSAGE_FOLDER_FLAGGED;
+ set |= CAMEL_MESSAGE_FOLDER_XEVCHANGE|CAMEL_MESSAGE_FOLDER_FLAGGED;
+ }
+
+ return ((CamelFolderClass *)parent_class)->set_message_flags(folder, uid, flags, set);
+}
+#endif
+
+static void
+mbox_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value)
+{
+ CamelMessageInfo *info;
+
+ g_return_if_fail(folder->summary != NULL);
+
+ info = camel_folder_summary_uid(folder->summary, uid);
+ if (info == NULL)
+ return;
+
+ if (camel_flag_set(&info->user_flags, name, value)) {
+ CamelFolderChangeInfo *changes = camel_folder_change_info_new();
+
+ info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE;
+ camel_folder_summary_touch(folder->summary);
+
+ camel_folder_change_info_change_uid(changes, uid);
+ camel_object_trigger_event(folder, "folder_changed", changes);
+ camel_folder_change_info_free(changes);
+ }
+ camel_folder_summary_info_free(folder->summary, info);
+}
+
+static void
+mbox_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value)
+{
+ CamelMessageInfo *info;
+
+ g_return_if_fail(folder->summary != NULL);
+
+ info = camel_folder_summary_uid(folder->summary, uid);
+ if (info == NULL)
+ return;
+
+ if (camel_tag_set(&info->user_tags, name, value)) {
+ CamelFolderChangeInfo *changes = camel_folder_change_info_new();
+
+ info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE;
+ camel_folder_summary_touch(folder->summary);
+
+ camel_folder_change_info_change_uid(changes, uid);
+ camel_object_trigger_event (folder, "folder_changed", changes);
+ camel_folder_change_info_free(changes);
+ }
+ camel_folder_summary_info_free(folder->summary, info);
+}
diff --git a/camel/providers/local/camel-mbox-folder.h b/camel/providers/local/camel-mbox-folder.h
new file mode 100644
index 0000000000..fa76001849
--- /dev/null
+++ b/camel/providers/local/camel-mbox-folder.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999 Ximian .
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef CAMEL_MBOX_FOLDER_H
+#define CAMEL_MBOX_FOLDER_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus }*/
+
+#include "camel-local-folder.h"
+#include "camel-mbox-summary.h"
+
+#define CAMEL_MBOX_FOLDER_TYPE (camel_mbox_folder_get_type ())
+#define CAMEL_MBOX_FOLDER(obj) (CAMEL_CHECK_CAST((obj), CAMEL_MBOX_FOLDER_TYPE, CamelMboxFolder))
+#define CAMEL_MBOX_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_MBOX_FOLDER_TYPE, CamelMboxFolderClass))
+#define CAMEL_IS_MBOX_FOLDER(o) (CAMEL_CHECK_TYPE((o), CAMEL_MBOX_FOLDER_TYPE))
+
+typedef struct {
+ CamelLocalFolder parent_object;
+
+ int lockfd; /* for when we have a lock on the folder */
+} CamelMboxFolder;
+
+typedef struct {
+ CamelLocalFolderClass parent_class;
+
+ /* Virtual methods */
+
+} CamelMboxFolderClass;
+
+/* public methods */
+/* flags are taken from CAMEL_STORE_FOLDER_* flags */
+CamelFolder *camel_mbox_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex);
+
+/* Standard Camel function */
+CamelType camel_mbox_folder_get_type(void);
+
+/* utilities */
+char *camel_mbox_folder_get_full_path (CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name);
+char *camel_mbox_folder_get_meta_path (CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_MBOX_FOLDER_H */
diff --git a/camel/providers/local/camel-mbox-store.c b/camel/providers/local/camel-mbox-store.c
new file mode 100644
index 0000000000..1277407dbb
--- /dev/null
+++ b/camel/providers/local/camel-mbox-store.c
@@ -0,0 +1,837 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright(C) 2000 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "camel-mbox-store.h"
+#include "camel-mbox-folder.h"
+#include "camel-file-utils.h"
+#include "camel-text-index.h"
+#include "camel-exception.h"
+#include "camel-url.h"
+
+#define d(x)
+
+static CamelLocalStoreClass *parent_class = NULL;
+
+/* Returns the class for a CamelMboxStore */
+#define CMBOXS_CLASS(so) CAMEL_MBOX_STORE_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+#define CMBOXF_CLASS(so) CAMEL_MBOX_FOLDER_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+
+static CamelFolder *get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
+static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
+static CamelFolderInfo *create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
+static CamelFolderInfo *get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+
+static void
+camel_mbox_store_class_init(CamelMboxStoreClass *camel_mbox_store_class)
+{
+ CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_mbox_store_class);
+
+ parent_class =(CamelLocalStoreClass *)camel_type_get_global_classfuncs(camel_local_store_get_type());
+
+ /* virtual method overload */
+ camel_store_class->get_folder = get_folder;
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = rename_folder;
+ camel_store_class->create_folder = create_folder;
+
+ camel_store_class->get_folder_info = get_folder_info;
+ camel_store_class->free_folder_info = camel_store_free_folder_info_full;
+}
+
+CamelType
+camel_mbox_store_get_type(void)
+{
+ static CamelType camel_mbox_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_mbox_store_type == CAMEL_INVALID_TYPE) {
+ camel_mbox_store_type = camel_type_register(CAMEL_LOCAL_STORE_TYPE, "CamelMboxStore",
+ sizeof(CamelMboxStore),
+ sizeof(CamelMboxStoreClass),
+ (CamelObjectClassInitFunc) camel_mbox_store_class_init,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ return camel_mbox_store_type;
+}
+
+static char *
+mbox_folder_name_to_path(CamelStore *store, const char *folder_name)
+{
+ const char *toplevel_dir = CAMEL_LOCAL_STORE(store)->toplevel_dir;
+
+ return camel_mbox_folder_get_full_path(NULL, toplevel_dir, folder_name);
+}
+
+static char *
+mbox_folder_name_to_meta_path(CamelStore *store, const char *folder_name, const char *ext)
+{
+ const char *toplevel_dir = CAMEL_LOCAL_STORE(store)->toplevel_dir;
+
+ return camel_mbox_folder_get_meta_path(NULL, toplevel_dir, folder_name, ext);
+}
+
+static char *extensions[] = {
+ ".msf", ".ev-summary", ".ibex.index", ".ibex.index.data", ".cmeta", ".lock"
+};
+
+static gboolean
+ignore_file(const char *filename, gboolean sbd)
+{
+ int flen, len, i;
+
+ /* TODO: Should probably just be 1 regex */
+ flen = strlen(filename);
+ if (flen > 0 && filename[flen-1] == '~')
+ return TRUE;
+
+ for (i = 0; i <(sizeof(extensions) / sizeof(extensions[0])); i++) {
+ len = strlen(extensions[i]);
+ if (len < flen && !strcmp(filename + flen - len, extensions[i]))
+ return TRUE;
+ }
+
+ if (sbd && flen > 4 && !strcmp(filename + flen - 4, ".sbd"))
+ return TRUE;
+
+ return FALSE;
+}
+
+static CamelFolder *
+get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ struct stat st;
+ char *name;
+
+ if (!((CamelStoreClass *) parent_class)->get_folder(store, folder_name, flags, ex))
+ return NULL;
+
+ name = mbox_folder_name_to_path(store, folder_name);
+
+ if (stat(name, &st) == -1) {
+ const char *basename;
+ char *dirname;
+ int fd;
+
+ if (errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get folder `%s': %s"),
+ folder_name, g_strerror (errno));
+ g_free(name);
+ return NULL;
+ }
+
+ if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot get folder `%s': folder does not exist."),
+ folder_name);
+ g_free(name);
+ return NULL;
+ }
+
+ /* sanity check the folder name */
+ if (!(basename = strrchr (folder_name, '/')))
+ basename = folder_name;
+ else
+ basename++;
+
+ if (basename[0] == '.' || ignore_file (basename, TRUE)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create a folder by this name."));
+ g_free (name);
+ return NULL;
+ }
+
+ dirname = g_path_get_dirname(name);
+ if (camel_mkdir(dirname, 0777) == -1 && errno != EEXIST) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': %s"),
+ folder_name, g_strerror (errno));
+ g_free(dirname);
+ g_free(name);
+ return NULL;
+ }
+
+ g_free(dirname);
+
+ fd = open(name, O_WRONLY | O_CREAT | O_APPEND, 0666);
+ if (fd == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': %s"),
+ folder_name, g_strerror (errno));
+ g_free(name);
+ return NULL;
+ }
+
+ g_free(name);
+ close(fd);
+ } else if (!S_ISREG(st.st_mode)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get folder `%s': not a regular file."),
+ folder_name);
+ g_free(name);
+ return NULL;
+ } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': folder exists."),
+ folder_name);
+ g_free (name);
+ return NULL;
+ } else
+ g_free(name);
+
+ return camel_mbox_folder_new(store, folder_name, flags, ex);
+}
+
+static void
+delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelFolderInfo *fi;
+ CamelException lex;
+ CamelFolder *lf;
+ char *name, *path;
+ struct stat st;
+
+ name = mbox_folder_name_to_path(store, folder_name);
+ path = g_strdup_printf("%s.sbd", name);
+
+ if (rmdir(path) == -1 && errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder `%s':\n%s"),
+ folder_name, g_strerror(errno));
+ g_free(path);
+ g_free(name);
+ return;
+ }
+
+ g_free(path);
+
+ if (stat(name, &st) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder `%s':\n%s"),
+ folder_name, g_strerror(errno));
+ g_free(name);
+ return;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("`%s' is not a regular file."), name);
+ g_free(name);
+ return;
+ }
+
+ if (st.st_size != 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_NON_EMPTY,
+ _("Folder `%s' is not empty. Not deleted."),
+ folder_name);
+ g_free(name);
+ return;
+ }
+
+ if (unlink(name) == -1 && errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder `%s':\n%s"),
+ name, g_strerror(errno));
+ g_free(name);
+ return;
+ }
+
+ /* FIXME: we have to do our own meta cleanup here rather than
+ * calling our parent class' delete_folder() method since our
+ * naming convention is different. Need to find a way for
+ * CamelLocalStore to be able to construct the folder & meta
+ * paths itself */
+ path = mbox_folder_name_to_meta_path(store, folder_name, ".ev-summary");
+ if (unlink(path) == -1 && errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder summary file `%s': %s"),
+ path, g_strerror(errno));
+ g_free(path);
+ g_free(name);
+ return;
+ }
+
+ g_free(path);
+
+ path = mbox_folder_name_to_meta_path(store, folder_name, ".ibex");
+ if (camel_text_index_remove(path) == -1 && errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder index file `%s': %s"),
+ path, g_strerror(errno));
+ g_free(path);
+ g_free(name);
+ return;
+ }
+
+ g_free(path);
+
+ path = NULL;
+ camel_exception_init(&lex);
+ if ((lf = camel_store_get_folder(store, folder_name, 0, &lex))) {
+ camel_object_get(lf, NULL, CAMEL_OBJECT_STATE_FILE, &path, NULL);
+ camel_object_set(lf, NULL, CAMEL_OBJECT_STATE_FILE, NULL, NULL);
+ camel_object_unref(lf);
+ } else {
+ camel_exception_clear(&lex);
+ }
+
+ if (path == NULL)
+ path = mbox_folder_name_to_meta_path(store, folder_name, ".cmeta");
+
+ if (unlink(path) == -1 && errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not delete folder meta file `%s': %s"),
+ path, g_strerror(errno));
+
+ g_free(path);
+ g_free(name);
+ return;
+ }
+
+ g_free(path);
+ g_free(name);
+
+ fi = g_new0(CamelFolderInfo, 1);
+ fi->full_name = g_strdup(folder_name);
+ fi->name = g_path_get_basename(folder_name);
+ fi->uri = g_strdup_printf("mbox:%s#%s",((CamelService *) store)->url->path, folder_name);
+ fi->unread = -1;
+
+ camel_object_trigger_event(store, "folder_deleted", fi);
+
+ camel_folder_info_free(fi);
+}
+
+static CamelFolderInfo *
+create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
+{
+ /* FIXME: this is almost an exact copy of CamelLocalStore::create_folder() except that we use
+ * different path schemes... need to find a way to share parent's code? */
+ const char *toplevel_dir =((CamelLocalStore *) store)->toplevel_dir;
+ CamelFolderInfo *info = NULL;
+ char *path, *name, *dir;
+ CamelFolder *folder;
+ struct stat st;
+
+ if (toplevel_dir[0] != '/') {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Store root %s is not an absolute path"), toplevel_dir);
+ return NULL;
+ }
+
+ if (folder_name[0] == '.' || ignore_file(folder_name, TRUE)) {
+ camel_exception_set(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create a folder by this name."));
+ return NULL;
+ }
+
+ if (parent_name && *parent_name)
+ name = g_strdup_printf("%s/%s", parent_name, folder_name);
+ else
+ name = g_strdup(folder_name);
+
+ path = mbox_folder_name_to_path(store, name);
+
+ dir = g_path_get_dirname(path);
+ if (camel_mkdir(dir, 0777) == -1 && errno != EEXIST) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create directory `%s': %s."),
+ dir, g_strerror(errno));
+
+ g_free(path);
+ g_free(name);
+ g_free(dir);
+
+ return NULL;
+ }
+
+ g_free(dir);
+
+ if (stat(path, &st) == 0 || errno != ENOENT) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Cannot create folder: %s: %s"),
+ path, errno ? g_strerror(errno) :
+ _("Folder already exists"));
+
+ g_free(path);
+ g_free(name);
+
+ return NULL;
+ }
+
+ g_free(path);
+
+ folder =((CamelStoreClass *)((CamelObject *) store)->klass)->get_folder(store, name, CAMEL_STORE_FOLDER_CREATE, ex);
+ if (folder) {
+ camel_object_unref(folder);
+ info =((CamelStoreClass *)((CamelObject *) store)->klass)->get_folder_info(store, name, 0, ex);
+ }
+
+ g_free(name);
+
+ return info;
+}
+
+static int
+xrename(CamelStore *store, const char *old_name, const char *new_name, const char *ext, gboolean missingok)
+{
+ const char *toplevel_dir =((CamelLocalStore *) store)->toplevel_dir;
+ char *oldpath, *newpath;
+ struct stat st;
+ int ret = -1;
+ int err = 0;
+
+ if (ext != NULL) {
+ oldpath = camel_mbox_folder_get_meta_path(NULL, toplevel_dir, old_name, ext);
+ newpath = camel_mbox_folder_get_meta_path(NULL, toplevel_dir, new_name, ext);
+ } else {
+ oldpath = camel_mbox_folder_get_full_path(NULL, toplevel_dir, old_name);
+ newpath = camel_mbox_folder_get_full_path(NULL, toplevel_dir, new_name);
+ }
+
+ if (stat(oldpath, &st) == -1) {
+ if (missingok && errno == ENOENT) {
+ ret = 0;
+ } else {
+ err = errno;
+ ret = -1;
+ }
+ } else if (S_ISDIR(st.st_mode)) {
+ /* use rename for dirs */
+ if (rename(oldpath, newpath) == 0 || stat(newpath, &st) == 0) {
+ ret = 0;
+ } else {
+ err = errno;
+ ret = -1;
+ }
+ } else if (link(oldpath, newpath) == 0 /* and link for files */
+ ||(stat(newpath, &st) == 0 && st.st_nlink == 2)) {
+ if (unlink(oldpath) == 0) {
+ ret = 0;
+ } else {
+ err = errno;
+ unlink(newpath);
+ ret = -1;
+ }
+ } else {
+ err = errno;
+ ret = -1;
+ }
+
+ g_free(oldpath);
+ g_free(newpath);
+
+ return ret;
+}
+
+static void
+rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
+{
+ CamelLocalFolder *folder = NULL;
+ char *oldibex, *newibex, *newdir;
+ int errnosav;
+
+ if (new[0] == '.' || ignore_file(new, TRUE)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("The new folder name is illegal."));
+ return;
+ }
+
+ /* try to rollback failures, has obvious races */
+
+ oldibex = mbox_folder_name_to_meta_path(store, old, ".ibex");
+ newibex = mbox_folder_name_to_meta_path(store, new, ".ibex");
+
+ newdir = g_path_get_dirname(newibex);
+ if (camel_mkdir(newdir, 0777) == -1) {
+ if (errno != EEXIST) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not rename `%s': `%s': %s"),
+ old, new, g_strerror(errno));
+ g_free(oldibex);
+ g_free(newibex);
+ g_free(newdir);
+
+ return;
+ }
+
+ g_free(newdir);
+ newdir = NULL;
+ }
+
+ folder = camel_object_bag_get(store->folders, old);
+ if (folder && folder->index) {
+ if (camel_index_rename(folder->index, newibex) == -1 && errno != ENOENT) {
+ errnosav = errno;
+ goto ibex_failed;
+ }
+ } else {
+ /* TODO: camel_text_index_rename should find out if we have an active index itself? */
+ if (camel_text_index_rename(oldibex, newibex) == -1 && errno != ENOENT) {
+ errnosav = errno;
+ goto ibex_failed;
+ }
+ }
+
+ if (xrename(store, old, new, ".ev-summary", TRUE) == -1) {
+ errnosav = errno;
+ goto summary_failed;
+ }
+
+ if (xrename(store, old, new, ".cmeta", TRUE) == -1) {
+ errnosav = errno;
+ goto cmeta_failed;
+ }
+
+ if (xrename(store, old, new, ".sbd", TRUE) == -1) {
+ errnosav = errno;
+ goto subdir_failed;
+ }
+
+ if (xrename(store, old, new, NULL, FALSE) == -1) {
+ errnosav = errno;
+ goto base_failed;
+ }
+
+ g_free(oldibex);
+ g_free(newibex);
+
+ if (folder)
+ camel_object_unref(folder);
+
+ return;
+
+base_failed:
+ xrename(store, new, old, ".sbd", TRUE);
+subdir_failed:
+ xrename(store, new, old, ".cmeta", TRUE);
+cmeta_failed:
+ xrename(store, new, old, ".ev-summary", TRUE);
+summary_failed:
+ if (folder) {
+ if (folder->index)
+ camel_index_rename(folder->index, oldibex);
+ } else
+ camel_text_index_rename(newibex, oldibex);
+ibex_failed:
+ if (newdir) {
+ /* newdir is only non-NULL if we needed to mkdir */
+ rmdir(newdir);
+ g_free(newdir);
+ }
+
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not rename '%s' to %s: %s"),
+ old, new, g_strerror(errnosav));
+
+ g_free(newibex);
+ g_free(oldibex);
+
+ if (folder)
+ camel_object_unref(folder);
+}
+
+/* used to find out where we've visited already */
+struct _inode {
+ dev_t dnode;
+ ino_t inode;
+};
+
+static guint
+inode_hash(const void *d)
+{
+ const struct _inode *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean
+inode_equal(const void *a, const void *b)
+{
+ const struct _inode *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void
+inode_free(void *k, void *v, void *d)
+{
+ g_free(k);
+}
+
+/* NB: duplicated in maildir store */
+static void
+fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
+{
+ CamelFolder *folder;
+
+ fi->unread = -1;
+ fi->total = -1;
+ folder = camel_object_bag_get(store->folders, fi->full_name);
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ camel_folder_refresh_info(folder, NULL);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ camel_object_unref(folder);
+ } else {
+ char *path, *folderpath;
+ CamelMboxSummary *mbs;
+ const char *root;
+
+ /* This should be fast enough not to have to test for INFO_FAST */
+ root = camel_local_store_get_toplevel_dir((CamelLocalStore *)store);
+ path = camel_mbox_folder_get_meta_path(NULL, root, fi->full_name, ".ev-summary");
+ folderpath = camel_mbox_folder_get_full_path(NULL, root, fi->full_name);
+
+ mbs = (CamelMboxSummary *)camel_mbox_summary_new(path, folderpath, NULL);
+ if (camel_folder_summary_header_load((CamelFolderSummary *)mbs) != -1) {
+ fi->unread = ((CamelFolderSummary *)mbs)->unread_count;
+ fi->total = ((CamelFolderSummary *)mbs)->saved_count;
+ }
+
+ camel_object_unref(mbs);
+ g_free(folderpath);
+ g_free(path);
+ }
+}
+
+static CamelFolderInfo *
+scan_dir(CamelStore *store, CamelURL *url, GHashTable *visited, CamelFolderInfo *parent, const char *root,
+ const char *name, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *folders, *tail, *fi;
+ GHashTable *folder_hash;
+ struct dirent *dent;
+ DIR *dir;
+
+ tail = folders = NULL;
+
+ if (!(dir = opendir(root)))
+ return NULL;
+
+ folder_hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ /* FIXME: it would be better if we queue'd up the recursive
+ * scans till the end so that we can limit the number of
+ * directory descriptors open at any given time... */
+
+ while ((dent = readdir(dir))) {
+ char *short_name, *full_name, *path, *ext;
+ struct stat st;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ if (ignore_file(dent->d_name, FALSE))
+ continue;
+
+ path = g_strdup_printf("%s/%s", root, dent->d_name);
+ if (stat(path, &st) == -1) {
+ g_free(path);
+ continue;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ struct _inode in = { st.st_dev, st.st_ino };
+
+ if (g_hash_table_lookup(visited, &in)) {
+ g_free(path);
+ continue;
+ }
+ }
+
+ short_name = g_strdup(dent->d_name);
+ if ((ext = strrchr(short_name, '.')) && !strcmp(ext, ".sbd"))
+ *ext = '\0';
+
+ if (name != NULL)
+ full_name = g_strdup_printf("%s/%s", name, short_name);
+ else
+ full_name = g_strdup(short_name);
+
+ if ((fi = g_hash_table_lookup(folder_hash, short_name)) != NULL) {
+ g_free(short_name);
+ g_free(full_name);
+
+ if (S_ISDIR(st.st_mode)) {
+ fi->flags =(fi->flags & ~CAMEL_FOLDER_NOCHILDREN) | CAMEL_FOLDER_CHILDREN;
+ } else {
+ fi->flags &= ~CAMEL_FOLDER_NOSELECT;
+ }
+ } else {
+ fi = g_new0(CamelFolderInfo, 1);
+ fi->parent = parent;
+
+ camel_url_set_fragment (url, full_name);
+
+ fi->uri = camel_url_to_string (url, 0);
+ fi->name = short_name;
+ fi->full_name = full_name;
+ fi->unread = -1;
+ fi->total = -1;
+
+ if (S_ISDIR(st.st_mode))
+ fi->flags = CAMEL_FOLDER_NOSELECT;
+ else
+ fi->flags = CAMEL_FOLDER_NOCHILDREN;
+
+ if (tail == NULL)
+ folders = fi;
+ else
+ tail->next = fi;
+
+ tail = fi;
+
+ g_hash_table_insert(folder_hash, fi->name, fi);
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ fill_fi(store, fi, flags);
+ } else if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)) {
+ struct _inode in = { st.st_dev, st.st_ino };
+
+ if (g_hash_table_lookup(visited, &in) == NULL) {
+ struct _inode *inew = g_new(struct _inode, 1);
+
+ *inew = in;
+
+ g_hash_table_insert(visited, inew, inew);
+
+ if ((fi->child = scan_dir (store, url, visited, fi, path, fi->full_name, flags, ex)))
+ fi->flags |= CAMEL_FOLDER_CHILDREN;
+ else
+ fi->flags =(fi->flags & ~CAMEL_FOLDER_CHILDREN) | CAMEL_FOLDER_NOCHILDREN;
+ }
+ }
+
+ g_free(path);
+ }
+
+ closedir(dir);
+
+ g_hash_table_destroy(folder_hash);
+
+ return folders;
+}
+
+static CamelFolderInfo *
+get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ GHashTable *visited;
+ struct _inode *inode;
+ char *path, *subdir;
+ CamelFolderInfo *fi;
+ const char *base;
+ struct stat st;
+ CamelURL *url;
+
+ top = top ? top : "";
+ path = mbox_folder_name_to_path(store, top);
+
+ if (*top == '\0') {
+ /* requesting root dir scan */
+ if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ g_free(path);
+ return NULL;
+ }
+
+ visited = g_hash_table_new(inode_hash, inode_equal);
+
+ inode = g_malloc0(sizeof(*inode));
+ inode->dnode = st.st_dev;
+ inode->inode = st.st_ino;
+
+ g_hash_table_insert(visited, inode, inode);
+
+ url = camel_url_copy (((CamelService *) store)->url);
+ fi = scan_dir (store, url, visited, NULL, path, NULL, flags, ex);
+ g_hash_table_foreach(visited, inode_free, NULL);
+ g_hash_table_destroy(visited);
+ camel_url_free (url);
+ g_free (path);
+
+ return fi;
+ }
+
+ /* requesting scan of specific folder */
+ if (stat(path, &st) == -1 || !S_ISREG(st.st_mode)) {
+ g_free(path);
+ return NULL;
+ }
+
+ visited = g_hash_table_new(inode_hash, inode_equal);
+
+ if (!(base = strrchr(top, '/')))
+ base = top;
+ else
+ base++;
+
+ url = camel_url_copy (((CamelService *) store)->url);
+ camel_url_set_fragment (url, top);
+
+ fi = g_new0(CamelFolderInfo, 1);
+ fi->parent = NULL;
+ fi->uri = camel_url_to_string (url, 0);
+ fi->name = g_strdup(base);
+ fi->full_name = g_strdup(top);
+ fi->unread = -1;
+ fi->total = -1;
+
+ subdir = g_strdup_printf("%s.sbd", path);
+ if (stat(subdir, &st) == 0) {
+ if (S_ISDIR(st.st_mode))
+ fi->child = scan_dir (store, url, visited, fi, subdir, top, flags, ex);
+ else
+ fill_fi(store, fi, flags);
+ } else
+ fill_fi(store, fi, flags);
+
+ camel_url_free (url);
+
+ if (fi->child)
+ fi->flags |= CAMEL_FOLDER_CHILDREN;
+ else
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+
+ g_free(subdir);
+
+ g_hash_table_foreach(visited, inode_free, NULL);
+ g_hash_table_destroy(visited);
+ g_free(path);
+
+ return fi;
+}
diff --git a/camel/providers/local/camel-mh-folder.c b/camel/providers/local/camel-mh-folder.c
new file mode 100644
index 0000000000..78456b6daf
--- /dev/null
+++ b/camel/providers/local/camel-mh-folder.c
@@ -0,0 +1,233 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999, 2003 Ximian Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "camel-mh-folder.h"
+#include "camel-mh-store.h"
+#include "camel-stream-fs.h"
+#include "camel-mh-summary.h"
+#include "camel-data-wrapper.h"
+#include "camel-mime-message.h"
+#include "camel-exception.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+static CamelLocalFolderClass *parent_class = NULL;
+
+/* Returns the class for a CamelMhFolder */
+#define CMHF_CLASS(so) CAMEL_MH_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CMHS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static CamelLocalSummary *mh_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index);
+
+static void mh_append_message(CamelFolder * folder, CamelMimeMessage * message, const CamelMessageInfo *info, char **appended_uid, CamelException * ex);
+static CamelMimeMessage *mh_get_message(CamelFolder * folder, const gchar * uid, CamelException * ex);
+
+static void mh_finalize(CamelObject * object);
+
+static void camel_mh_folder_class_init(CamelObjectClass * camel_mh_folder_class)
+{
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_mh_folder_class);
+ CamelLocalFolderClass *lclass = (CamelLocalFolderClass *)camel_mh_folder_class;
+
+ parent_class = CAMEL_LOCAL_FOLDER_CLASS (camel_type_get_global_classfuncs(camel_local_folder_get_type()));
+
+ /* virtual method definition */
+
+ /* virtual method overload */
+ camel_folder_class->append_message = mh_append_message;
+ camel_folder_class->get_message = mh_get_message;
+
+ lclass->create_summary = mh_create_summary;
+}
+
+static void mh_init(gpointer object, gpointer klass)
+{
+ /*CamelFolder *folder = object;
+ CamelMhFolder *mh_folder = object;*/
+}
+
+static void mh_finalize(CamelObject * object)
+{
+ /*CamelMhFolder *mh_folder = CAMEL_MH_FOLDER(object);*/
+}
+
+CamelType camel_mh_folder_get_type(void)
+{
+ static CamelType camel_mh_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_mh_folder_type == CAMEL_INVALID_TYPE) {
+ camel_mh_folder_type = camel_type_register(CAMEL_LOCAL_FOLDER_TYPE, "CamelMhFolder",
+ sizeof(CamelMhFolder),
+ sizeof(CamelMhFolderClass),
+ (CamelObjectClassInitFunc) camel_mh_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) mh_init,
+ (CamelObjectFinalizeFunc) mh_finalize);
+ }
+
+ return camel_mh_folder_type;
+}
+
+CamelFolder *
+camel_mh_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ d(printf("Creating mh folder: %s\n", full_name));
+
+ folder = (CamelFolder *)camel_object_new(CAMEL_MH_FOLDER_TYPE);
+ folder = (CamelFolder *)camel_local_folder_construct((CamelLocalFolder *)folder,
+ parent_store, full_name, flags, ex);
+
+ return folder;
+}
+
+static CamelLocalSummary *mh_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index)
+{
+ return (CamelLocalSummary *)camel_mh_summary_new(path, folder, index);
+}
+
+static void
+mh_append_message (CamelFolder *folder, CamelMimeMessage *message, const CamelMessageInfo *info, char **appended_uid, CamelException *ex)
+{
+ CamelMhFolder *mh_folder = (CamelMhFolder *)folder;
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ CamelStream *output_stream;
+ CamelMessageInfo *mi;
+ char *name;
+
+ /* FIXME: probably needs additional locking (although mh doesn't appear do do it) */
+
+ d(printf("Appending message\n"));
+
+ /* add it to the summary/assign the uid, etc */
+ mi = camel_local_summary_add((CamelLocalSummary *)folder->summary, message, info, lf->changes, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ d(printf("Appending message: uid is %s\n", camel_message_info_uid(mi)));
+
+ /* write it out, use the uid we got from the summary */
+ name = g_strdup_printf("%s/%s", lf->folder_path, camel_message_info_uid(mi));
+ output_stream = camel_stream_fs_new_with_name(name, O_WRONLY|O_CREAT, 0600);
+ if (output_stream == NULL)
+ goto fail_write;
+
+ if (camel_data_wrapper_write_to_stream ((CamelDataWrapper *)message, output_stream) == -1
+ || camel_stream_close (output_stream) == -1)
+ goto fail_write;
+
+ /* close this? */
+ camel_object_unref (CAMEL_OBJECT (output_stream));
+
+ g_free(name);
+
+ camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed",
+ ((CamelLocalFolder *)mh_folder)->changes);
+ camel_folder_change_info_clear (((CamelLocalFolder *)mh_folder)->changes);
+
+ if (appended_uid)
+ *appended_uid = g_strdup(camel_message_info_uid(mi));
+
+ return;
+
+ fail_write:
+
+ /* remove the summary info so we are not out-of-sync with the mh folder */
+ camel_folder_summary_remove_uid (CAMEL_FOLDER_SUMMARY (folder->summary),
+ camel_message_info_uid (mi));
+
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("MH append message cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot append message to mh folder: %s: %s"),
+ name, g_strerror (errno));
+
+ if (output_stream) {
+ camel_object_unref (CAMEL_OBJECT (output_stream));
+ unlink (name);
+ }
+
+ g_free (name);
+}
+
+static CamelMimeMessage *mh_get_message(CamelFolder * folder, const gchar * uid, CamelException * ex)
+{
+ CamelLocalFolder *lf = (CamelLocalFolder *)folder;
+ CamelStream *message_stream = NULL;
+ CamelMimeMessage *message = NULL;
+ CamelMessageInfo *info;
+ char *name;
+
+ d(printf("getting message: %s\n", uid));
+
+ /* get the message summary info */
+ if ((info = camel_folder_summary_uid(folder->summary, uid)) == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+ _("Cannot get message: %s from folder %s\n %s"), uid, lf->folder_path,
+ _("No such message"));
+ return NULL;
+ }
+
+ /* we only need it to check the message exists */
+ camel_folder_summary_info_free(folder->summary, info);
+
+ name = g_strdup_printf("%s/%s", lf->folder_path, uid);
+ if ((message_stream = camel_stream_fs_new_with_name(name, O_RDONLY, 0)) == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get message: %s from folder %s\n %s"), name, lf->folder_path,
+ g_strerror (errno));
+ g_free(name);
+ return NULL;
+ }
+
+ message = camel_mime_message_new();
+ if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)message, message_stream) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot get message: %s from folder %s\n %s"), name, lf->folder_path,
+ _("Message construction failed."));
+ g_free(name);
+ camel_object_unref((CamelObject *)message_stream);
+ camel_object_unref((CamelObject *)message);
+ return NULL;
+
+ }
+ camel_object_unref((CamelObject *)message_stream);
+ g_free(name);
+
+ return message;
+}
diff --git a/camel/providers/local/camel-spool-folder.c b/camel/providers/local/camel-spool-folder.c
new file mode 100644
index 0000000000..6a1bbf798a
--- /dev/null
+++ b/camel/providers/local/camel-spool-folder.c
@@ -0,0 +1,217 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "camel-spool-folder.h"
+#include "camel-spool-store.h"
+#include "camel-stream-fs.h"
+#include "camel-spool-summary.h"
+#include "camel-data-wrapper.h"
+#include "camel-mime-message.h"
+#include "camel-stream-filter.h"
+#include "camel-mime-filter-from.h"
+#include "camel-exception.h"
+#include "camel-session.h"
+#include "camel-file-utils.h"
+#include "camel-lock-client.h"
+
+#include "camel-local-private.h"
+
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+
+static CamelFolderClass *parent_class = NULL;
+
+/* Returns the class for a CamelSpoolFolder */
+#define CSPOOLF_CLASS(so) CAMEL_SPOOL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CSPOOLS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static char *spool_get_full_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name);
+static char *spool_get_meta_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext);
+static CamelLocalSummary *spool_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index);
+
+static int spool_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex);
+static void spool_unlock(CamelLocalFolder *lf);
+
+static void spool_finalize(CamelObject * object);
+
+static void
+camel_spool_folder_class_init(CamelSpoolFolderClass *klass)
+{
+ CamelLocalFolderClass *lklass = (CamelLocalFolderClass *)klass;
+
+ parent_class = (CamelFolderClass *)camel_mbox_folder_get_type();
+
+ lklass->get_full_path = spool_get_full_path;
+ lklass->get_meta_path = spool_get_meta_path;
+ lklass->create_summary = spool_create_summary;
+ lklass->lock = spool_lock;
+ lklass->unlock = spool_unlock;
+}
+
+static void
+spool_init(gpointer object, gpointer klass)
+{
+ CamelSpoolFolder *spool_folder = object;
+
+ spool_folder->lockid = -1;
+}
+
+static void
+spool_finalize(CamelObject * object)
+{
+ /*CamelSpoolFolder *spool_folder = CAMEL_SPOOL_FOLDER(object);*/
+}
+
+CamelType camel_spool_folder_get_type(void)
+{
+ static CamelType camel_spool_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_spool_folder_type == CAMEL_INVALID_TYPE) {
+ camel_spool_folder_type = camel_type_register(camel_mbox_folder_get_type(), "CamelSpoolFolder",
+ sizeof(CamelSpoolFolder),
+ sizeof(CamelSpoolFolderClass),
+ (CamelObjectClassInitFunc) camel_spool_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) spool_init,
+ (CamelObjectFinalizeFunc) spool_finalize);
+ }
+
+ return camel_spool_folder_type;
+}
+
+CamelFolder *
+camel_spool_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
+{
+ CamelFolder *folder;
+
+ d(printf("Creating spool folder: %s in %s\n", full_name, camel_local_store_get_toplevel_dir((CamelLocalStore *)parent_store)));
+
+ folder = (CamelFolder *)camel_object_new(CAMEL_SPOOL_FOLDER_TYPE);
+
+ if (parent_store->flags & CAMEL_STORE_FILTER_INBOX
+ && strcmp(full_name, "INBOX") == 0)
+ folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+ flags &= ~CAMEL_STORE_FOLDER_BODY_INDEX;
+
+ folder = (CamelFolder *)camel_local_folder_construct((CamelLocalFolder *)folder, parent_store, full_name, flags, ex);
+ if (folder) {
+ if (camel_url_get_param(((CamelService *)parent_store)->url, "xstatus"))
+ camel_mbox_summary_xstatus((CamelMboxSummary *)folder->summary, TRUE);
+ }
+
+ return folder;
+}
+
+static char *
+spool_get_full_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name)
+{
+ return g_strdup_printf ("%s/%s", toplevel_dir, full_name);
+}
+
+static char *
+spool_get_meta_path(CamelLocalFolder *lf, const char *toplevel_dir, const char *full_name, const char *ext)
+{
+ CamelService *service = (CamelService *)((CamelFolder *)lf)->parent_store;
+ char *root = camel_session_get_storage_path(service->session, service, NULL);
+ char *path;
+
+ if (root == NULL)
+ return NULL;
+
+
+ camel_mkdir(root, 0777);
+ path = g_strdup_printf("%s/%s%s", root, full_name, ext);
+ g_free(root);
+
+ return path;
+}
+
+static CamelLocalSummary *
+spool_create_summary(CamelLocalFolder *lf, const char *path, const char *folder, CamelIndex *index)
+{
+ return (CamelLocalSummary *)camel_spool_summary_new(folder);
+}
+
+static int
+spool_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex)
+{
+ int retry = 0;
+ CamelMboxFolder *mf = (CamelMboxFolder *)lf;
+ CamelSpoolFolder *sf = (CamelSpoolFolder *)lf;
+
+ mf->lockfd = open(lf->folder_path, O_RDWR, 0);
+ if (mf->lockfd == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder lock on %s: %s"),
+ lf->folder_path, g_strerror (errno));
+ return -1;
+ }
+
+ while (retry < CAMEL_LOCK_RETRY) {
+ if (retry > 0)
+ sleep(CAMEL_LOCK_DELAY);
+
+ camel_exception_clear(ex);
+
+ if (camel_lock_fcntl(mf->lockfd, type, ex) == 0) {
+ if (camel_lock_flock(mf->lockfd, type, ex) == 0) {
+ if ((sf->lockid = camel_lock_helper_lock(lf->folder_path, ex)) != -1)
+ return 0;
+ camel_unlock_flock(mf->lockfd);
+ }
+ camel_unlock_fcntl(mf->lockfd);
+ }
+ retry++;
+ }
+
+ close (mf->lockfd);
+ mf->lockfd = -1;
+
+ return -1;
+}
+
+static void
+spool_unlock(CamelLocalFolder *lf)
+{
+ CamelMboxFolder *mf = (CamelMboxFolder *)lf;
+ CamelSpoolFolder *sf = (CamelSpoolFolder *)lf;
+
+ camel_lock_helper_unlock(sf->lockid);
+ sf->lockid = -1;
+ camel_unlock_flock(mf->lockfd);
+ camel_unlock_fcntl(mf->lockfd);
+
+ close(mf->lockfd);
+ mf->lockfd = -1;
+}
diff --git a/camel/providers/local/camel-spool-store.c b/camel/providers/local/camel-spool-store.c
new file mode 100644
index 0000000000..2e3d9ea777
--- /dev/null
+++ b/camel/providers/local/camel-spool-store.c
@@ -0,0 +1,465 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001 Ximian Inc (www.ximian.com/)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+
+#include "camel-spool-store.h"
+#include "camel-spool-folder.h"
+#include "camel-exception.h"
+#include "camel-url.h"
+#include "camel-private.h"
+
+#define d(x)
+
+/* Returns the class for a CamelSpoolStore */
+#define CSPOOLS_CLASS(so) CAMEL_SPOOL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
+static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
+static char *get_name(CamelService *service, gboolean brief);
+static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
+static void rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+static void free_folder_info (CamelStore *store, CamelFolderInfo *fi);
+
+static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
+static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
+
+static CamelStoreClass *parent_class = NULL;
+
+static void
+camel_spool_store_class_init (CamelSpoolStoreClass *camel_spool_store_class)
+{
+ CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_spool_store_class);
+ CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_spool_store_class);
+
+ parent_class = CAMEL_STORE_CLASS(camel_mbox_store_get_type());
+
+ /* virtual method overload */
+ camel_service_class->construct = construct;
+ camel_service_class->get_name = get_name;
+ camel_store_class->get_folder = get_folder;
+ camel_store_class->get_inbox = get_inbox;
+ camel_store_class->get_folder_info = get_folder_info;
+ camel_store_class->free_folder_info = free_folder_info;
+
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = rename_folder;
+}
+
+CamelType
+camel_spool_store_get_type (void)
+{
+ static CamelType camel_spool_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_spool_store_type == CAMEL_INVALID_TYPE) {
+ camel_spool_store_type = camel_type_register (camel_mbox_store_get_type(), "CamelSpoolStore",
+ sizeof (CamelSpoolStore),
+ sizeof (CamelSpoolStoreClass),
+ (CamelObjectClassInitFunc) camel_spool_store_class_init,
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ return camel_spool_store_type;
+}
+
+static void
+construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
+{
+ struct stat st;
+
+ d(printf("constructing store of type %s '%s:%s'\n",
+ camel_type_to_name(((CamelObject *)service)->s.type), url->protocol, url->path));
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ if (service->url->path[0] != '/') {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Store root %s is not an absolute path"), service->url->path);
+ return;
+ }
+
+ if (stat(service->url->path, &st) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Spool `%s' cannot be opened: %s"),
+ service->url->path, g_strerror (errno));
+ return;
+ }
+
+ if (S_ISREG(st.st_mode))
+ ((CamelSpoolStore *)service)->type = CAMEL_SPOOL_STORE_MBOX;
+ else if (S_ISDIR(st.st_mode))
+ /* we could check here for slight variations */
+ ((CamelSpoolStore *)service)->type = CAMEL_SPOOL_STORE_ELM;
+ else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Spool `%s' is not a regular file or directory"),
+ service->url->path);
+ return;
+ }
+}
+
+static CamelFolder *
+get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
+{
+ CamelFolder *folder = NULL;
+ struct stat st;
+ char *name;
+
+ d(printf("opening folder %s on path %s\n", folder_name, path));
+
+ /* we only support an 'INBOX' in mbox mode */
+ if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX) {
+ if (strcmp(folder_name, "INBOX") != 0) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Folder `%s/%s' does not exist."),
+ ((CamelService *)store)->url->path, folder_name);
+ } else {
+ folder = camel_spool_folder_new(store, folder_name, flags, ex);
+ }
+ } else {
+ name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
+ if (stat(name, &st) == -1) {
+ if (errno != ENOENT) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not open folder `%s':\n%s"),
+ folder_name, g_strerror (errno));
+ } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Folder `%s' does not exist."),
+ folder_name);
+ } else {
+ if (creat (name, 0600) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not create folder `%s':\n%s"),
+ folder_name, g_strerror (errno));
+ } else {
+ folder = camel_spool_folder_new(store, folder_name, flags, ex);
+ }
+ }
+ } else if (!S_ISREG(st.st_mode)) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("`%s' is not a mailbox file."), name);
+ } else {
+ folder = camel_spool_folder_new(store, folder_name, flags, ex);
+ }
+ g_free(name);
+ }
+
+ return folder;
+}
+
+static CamelFolder *
+get_inbox(CamelStore *store, CamelException *ex)
+{
+ if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX)
+ return get_folder (store, "INBOX", CAMEL_STORE_FOLDER_CREATE, ex);
+ else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("Store does not support an INBOX"));
+ return NULL;
+ }
+}
+
+static char *
+get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup(service->url->path);
+ else
+ return g_strdup_printf(((CamelSpoolStore *)service)->type == CAMEL_SPOOL_STORE_MBOX?
+ _("Spool mail file %s"):_("Spool folder tree %s"), service->url->path);
+}
+
+/* default implementation, rename all */
+static void
+rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
+{
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Spool folders cannot be renamed"));
+}
+
+/* default implementation, only delete metadata */
+static void
+delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Spool folders cannot be deleted"));
+}
+
+static void free_folder_info (CamelStore *store, CamelFolderInfo *fi)
+{
+ if (fi) {
+ g_free(fi->uri);
+ g_free(fi->name);
+ g_free(fi->full_name);
+ g_free(fi);
+ }
+}
+
+/* partially copied from mbox */
+static void
+spool_fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
+{
+ CamelFolder *folder;
+
+ fi->unread = -1;
+ fi->total = -1;
+ folder = camel_object_bag_get(store->folders, fi->full_name);
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ camel_folder_refresh_info(folder, NULL);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ camel_object_unref(folder);
+ }
+}
+
+static CamelFolderInfo *
+spool_new_fi(CamelStore *store, CamelFolderInfo *parent, CamelFolderInfo **fip, const char *full, guint32 flags)
+{
+ CamelFolderInfo *fi;
+ const char *name;
+ CamelURL *url;
+
+ name = strrchr(full, '/');
+ if (name)
+ name++;
+ else
+ name = full;
+
+ fi = g_malloc0(sizeof(*fi));
+ url = camel_url_copy(((CamelService *)store)->url);
+ camel_url_set_fragment(url, full);
+ fi->uri = camel_url_to_string(url, 0);
+ camel_url_free(url);
+ fi->full_name = g_strdup(full);
+ fi->name = g_strdup(name);
+ fi->unread = -1;
+ fi->total = -1;
+ fi->flags = flags;
+
+ fi->parent = parent;
+ fi->next = *fip;
+ *fip = fi;
+
+ d(printf("Adding spoold info: '%s' '%s' '%s' '%s'\n", fi->path, fi->name, fi->full_name, fi->url));
+
+ return fi;
+}
+
+/* used to find out where we've visited already */
+struct _inode {
+ dev_t dnode;
+ ino_t inode;
+};
+
+/* returns number of records found at or below this level */
+static int scan_dir(CamelStore *store, GHashTable *visited, char *root, const char *path, guint32 flags, CamelFolderInfo *parent, CamelFolderInfo **fip, CamelException *ex)
+{
+ DIR *dir;
+ struct dirent *d;
+ char *name, *tmp, *fname;
+ CamelFolderInfo *fi = NULL;
+ struct stat st;
+ CamelFolder *folder;
+ char from[80];
+ FILE *fp;
+
+ d(printf("checking dir '%s' part '%s' for mbox content\n", root, path));
+
+ /* look for folders matching the right structure, recursively */
+ if (path) {
+ name = alloca(strlen(root) + strlen(path) + 2);
+ sprintf(name, "%s/%s", root, path);
+ } else
+ name = root;
+
+ if (stat(name, &st) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not scan folder `%s': %s"),
+ name, g_strerror (errno));
+ } else if (S_ISREG(st.st_mode)) {
+ /* incase we start scanning from a file. messy duplication :-/ */
+ if (path) {
+ fi = spool_new_fi(store, parent, fip, path, CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_NOCHILDREN);
+ spool_fill_fi(store, fi, flags);
+ }
+ return 0;
+ }
+
+ dir = opendir(name);
+ if (dir == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not scan folder `%s': %s"),
+ name, g_strerror (errno));
+ return -1;
+ }
+
+ if (path != NULL) {
+ fi = spool_new_fi(store, parent, fip, path, CAMEL_FOLDER_NOSELECT);
+ fip = &fi->child;
+ parent = fi;
+ }
+
+ while ( (d = readdir(dir)) ) {
+ if (strcmp(d->d_name, ".") == 0
+ || strcmp(d->d_name, "..") == 0)
+ continue;
+
+ tmp = g_strdup_printf("%s/%s", name, d->d_name);
+ if (stat(tmp, &st) == 0) {
+ if (path)
+ fname = g_strdup_printf("%s/%s", path, d->d_name);
+ else
+ fname = g_strdup(d->d_name);
+
+ if (S_ISREG(st.st_mode)) {
+ int isfolder = FALSE;
+
+ /* first, see if we already have it open */
+ folder = camel_object_bag_get(store->folders, fname);
+ if (folder == NULL) {
+ fp = fopen(tmp, "r");
+ if (fp != NULL) {
+ isfolder = (st.st_size == 0
+ || (fgets(from, sizeof(from), fp) != NULL
+ && strncmp(from, "From ", 5) == 0));
+ fclose(fp);
+ }
+ }
+
+ if (folder != NULL || isfolder) {
+ fi = spool_new_fi(store, parent, fip, fname, CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_NOCHILDREN);
+ spool_fill_fi(store, fi, flags);
+ }
+ if (folder)
+ camel_object_unref(folder);
+
+ } else if (S_ISDIR(st.st_mode)) {
+ struct _inode in = { st.st_dev, st.st_ino };
+
+ /* see if we've visited already */
+ if (g_hash_table_lookup(visited, &in) == NULL) {
+ struct _inode *inew = g_malloc(sizeof(*inew));
+
+ *inew = in;
+ g_hash_table_insert(visited, inew, inew);
+
+ if (scan_dir(store, visited, root, fname, flags, parent, fip, ex) == -1) {
+ g_free(tmp);
+ g_free(fname);
+ closedir(dir);
+ return -1;
+ }
+ }
+ }
+ g_free(fname);
+
+ }
+ g_free(tmp);
+ }
+ closedir(dir);
+
+ return 0;
+}
+
+static guint inode_hash(const void *d)
+{
+ const struct _inode *v = d;
+
+ return v->inode ^ v->dnode;
+}
+
+static gboolean inode_equal(const void *a, const void *b)
+{
+ const struct _inode *v1 = a, *v2 = b;
+
+ return v1->inode == v2->inode && v1->dnode == v2->dnode;
+}
+
+static void inode_free(void *k, void *v, void *d)
+{
+ g_free(k);
+}
+
+static CamelFolderInfo *
+get_folder_info_elm(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *fi = NULL;
+ GHashTable *visited;
+
+ visited = g_hash_table_new(inode_hash, inode_equal);
+
+ if (scan_dir(store, visited, ((CamelService *)store)->url->path, top, flags, NULL, &fi, ex) == -1 && fi != NULL) {
+ camel_store_free_folder_info_full(store, fi);
+ fi = NULL;
+ }
+
+ g_hash_table_foreach(visited, inode_free, NULL);
+ g_hash_table_destroy(visited);
+
+ return fi;
+}
+
+static CamelFolderInfo *
+get_folder_info_mbox(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelFolderInfo *fi = NULL, *fip = NULL;
+
+ if (top == NULL || strcmp(top, "INBOX") == 0) {
+ fi = spool_new_fi(store, NULL, &fip, "INBOX", CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_SYSTEM);
+ g_free(fi->name);
+ fi->name = g_strdup(_("Inbox"));
+ spool_fill_fi(store, fi, flags);
+ }
+
+ return fi;
+}
+
+static CamelFolderInfo *
+get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ if (((CamelSpoolStore *)store)->type == CAMEL_SPOOL_STORE_MBOX)
+ return get_folder_info_mbox(store, top, flags, ex);
+ else
+ return get_folder_info_elm(store, top, flags, ex);
+}
diff --git a/camel/providers/nntp/camel-nntp-folder.c b/camel/providers/nntp/camel-nntp-folder.c
new file mode 100644
index 0000000000..ffb1b29742
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-folder.c
@@ -0,0 +1,532 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-folder.c : Class for a news folder
+ *
+ * Authors : Chris Toshok <toshok@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2001-2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "camel/camel-file-utils.h"
+#include "camel/camel-stream-mem.h"
+#include "camel/camel-data-wrapper.h"
+#include "camel/camel-mime-message.h"
+#include "camel/camel-folder-search.h"
+#include "camel/camel-exception.h"
+#include "camel/camel-session.h"
+#include "camel/camel-data-cache.h"
+
+#include "camel/camel-mime-filter-crlf.h"
+#include "camel/camel-stream-filter.h"
+#include "camel/camel-mime-message.h"
+#include "camel/camel-multipart.h"
+#include "camel/camel-mime-part.h"
+#include "camel/camel-stream-buffer.h"
+#include "camel/camel-private.h"
+
+#include "camel-nntp-summary.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-folder.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-private.h"
+
+static CamelFolderClass *folder_class = NULL;
+static CamelDiscoFolderClass *parent_class = NULL;
+
+/* Returns the class for a CamelNNTPFolder */
+#define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+void
+camel_nntp_folder_selected(CamelNNTPFolder *folder, char *line, CamelException *ex)
+{
+ camel_nntp_summary_check((CamelNNTPSummary *)((CamelFolder *)folder)->summary,
+ (CamelNNTPStore *)((CamelFolder *)folder)->parent_store,
+ line, folder->changes, ex);
+}
+
+static void
+nntp_folder_refresh_info_online (CamelFolder *folder, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store;
+ CamelFolderChangeInfo *changes = NULL;
+ CamelNNTPFolder *nntp_folder;
+ char *line;
+
+ nntp_store = (CamelNNTPStore *) folder->parent_store;
+ nntp_folder = (CamelNNTPFolder *) folder;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ camel_nntp_command(nntp_store, ex, nntp_folder, &line, NULL);
+
+ if (camel_folder_change_info_changed(nntp_folder->changes)) {
+ changes = nntp_folder->changes;
+ nntp_folder->changes = camel_folder_change_info_new();
+ }
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+
+ if (changes) {
+ camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes);
+ camel_folder_change_info_free (changes);
+ }
+}
+
+static void
+nntp_folder_sync_online (CamelFolder *folder, CamelException *ex)
+{
+ CAMEL_SERVICE_LOCK(folder->parent_store, connect_lock);
+ camel_folder_summary_save (folder->summary);
+ CAMEL_SERVICE_UNLOCK(folder->parent_store, connect_lock);
+}
+
+static void
+nntp_folder_sync_offline (CamelFolder *folder, CamelException *ex)
+{
+ CAMEL_SERVICE_LOCK(folder->parent_store, connect_lock);
+ camel_folder_summary_save (folder->summary);
+ CAMEL_SERVICE_UNLOCK(folder->parent_store, connect_lock);
+}
+
+static gboolean
+nntp_folder_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
+{
+ return ((CamelFolderClass *) folder_class)->set_message_flags (folder, uid, flags, set);
+}
+
+static CamelStream *
+nntp_folder_download_message (CamelNNTPFolder *nntp_folder, const char *id, const char *msgid, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = (CamelNNTPStore *) ((CamelFolder *) nntp_folder)->parent_store;
+ CamelStream *stream = NULL;
+ int ret;
+ char *line;
+
+ ret = camel_nntp_command (nntp_store, ex, nntp_folder, &line, "article %s", id);
+ if (ret == 220) {
+ stream = camel_data_cache_add (nntp_store->cache, "cache", msgid, NULL);
+ if (stream) {
+ if (camel_stream_write_to_stream ((CamelStream *) nntp_store->stream, stream) == -1)
+ goto fail;
+ if (camel_stream_reset (stream) == -1)
+ goto fail;
+ } else {
+ stream = (CamelStream *) nntp_store->stream;
+ camel_object_ref (stream);
+ }
+ } else if (ret == 423 || ret == 430) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, _("Cannot get message %s: %s"), msgid, line);
+ } else if (ret != -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, line);
+ }
+
+ return stream;
+
+ fail:
+ if (errno == EINTR)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, g_strerror (errno));
+
+ return NULL;
+}
+
+
+static void
+nntp_folder_cache_message (CamelDiscoFolder *disco_folder, const char *uid, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = (CamelNNTPStore *)((CamelFolder *) disco_folder)->parent_store;
+ CamelStream *stream;
+ char *article, *msgid;
+
+ article = alloca(strlen(uid)+1);
+ strcpy(article, uid);
+ msgid = strchr(article, ',');
+ if (!msgid) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Internal error: uid in invalid format: %s"), uid);
+ return;
+ }
+ *msgid++ = 0;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ stream = nntp_folder_download_message ((CamelNNTPFolder *) disco_folder, article, msgid, ex);
+ if (stream)
+ camel_object_unref (stream);
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+}
+
+static CamelMimeMessage *
+nntp_folder_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
+{
+ CamelMimeMessage *message = NULL;
+ CamelNNTPStore *nntp_store;
+ CamelFolderChangeInfo *changes;
+ CamelNNTPFolder *nntp_folder;
+ CamelStream *stream = NULL;
+ char *article, *msgid;
+
+ nntp_store = (CamelNNTPStore *) folder->parent_store;
+ nntp_folder = (CamelNNTPFolder *) folder;
+
+ article = alloca(strlen(uid)+1);
+ strcpy(article, uid);
+ msgid = strchr (article, ',');
+ if (msgid == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Internal error: uid in invalid format: %s"), uid);
+ return NULL;
+ }
+ *msgid++ = 0;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ /* Lookup in cache, NEWS is global messageid's so use a global cache path */
+ stream = camel_data_cache_get (nntp_store->cache, "cache", msgid, NULL);
+ if (stream == NULL) {
+ if (camel_disco_store_status ((CamelDiscoStore *) nntp_store) == CAMEL_DISCO_STORE_OFFLINE) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("This message is not currently available"));
+ goto fail;
+ }
+
+ stream = nntp_folder_download_message (nntp_folder, article, msgid, ex);
+ if (stream == NULL)
+ goto fail;
+ }
+
+ message = camel_mime_message_new ();
+ if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream) == -1) {
+ if (errno == EINTR)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, g_strerror (errno));
+ camel_object_unref(message);
+ message = NULL;
+ }
+
+ camel_object_unref (stream);
+fail:
+ if (camel_folder_change_info_changed (nntp_folder->changes)) {
+ changes = nntp_folder->changes;
+ nntp_folder->changes = camel_folder_change_info_new ();
+ } else {
+ changes = NULL;
+ }
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+
+ if (changes) {
+ camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes);
+ camel_folder_change_info_free (changes);
+ }
+
+ return message;
+}
+
+static GPtrArray*
+nntp_folder_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex)
+{
+ CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
+ GPtrArray *matches;
+
+ CAMEL_NNTP_FOLDER_LOCK(nntp_folder, search_lock);
+
+ if (nntp_folder->search == NULL)
+ nntp_folder->search = camel_folder_search_new ();
+
+ camel_folder_search_set_folder (nntp_folder->search, folder);
+ matches = camel_folder_search_search(nntp_folder->search, expression, NULL, ex);
+
+ CAMEL_NNTP_FOLDER_UNLOCK(nntp_folder, search_lock);
+
+ return matches;
+}
+
+static GPtrArray *
+nntp_folder_search_by_uids (CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
+{
+ CamelNNTPFolder *nntp_folder = (CamelNNTPFolder *) folder;
+ GPtrArray *matches;
+
+ if (uids->len == 0)
+ return g_ptr_array_new();
+
+ CAMEL_NNTP_FOLDER_LOCK(folder, search_lock);
+
+ if (nntp_folder->search == NULL)
+ nntp_folder->search = camel_folder_search_new ();
+
+ camel_folder_search_set_folder (nntp_folder->search, folder);
+ matches = camel_folder_search_search(nntp_folder->search, expression, uids, ex);
+
+ CAMEL_NNTP_FOLDER_UNLOCK(folder, search_lock);
+
+ return matches;
+}
+
+static void
+nntp_folder_search_free (CamelFolder *folder, GPtrArray *result)
+{
+ CamelNNTPFolder *nntp_folder = CAMEL_NNTP_FOLDER (folder);
+
+ camel_folder_search_free_result (nntp_folder->search, result);
+}
+
+static void
+nntp_folder_append_message_online (CamelFolder *folder, CamelMimeMessage *mime_message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = (CamelNNTPStore *) folder->parent_store;
+ CamelStream *stream = (CamelStream*)nntp_store->stream;
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilter *crlffilter;
+ int ret;
+ unsigned int u;
+ struct _camel_header_raw *header, *savedhdrs, *n, *tail;
+ char *group, *line;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ /* send 'POST' command */
+ ret = camel_nntp_command (nntp_store, ex, NULL, &line, "post");
+ if (ret != 340) {
+ if (ret == 440)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION,
+ _("Posting failed: %s"), line);
+ else if (ret != -1)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Posting failed: %s"), line);
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+ return;
+ }
+
+ /* the 'Newsgroups: ' header */
+ group = g_strdup_printf ("Newsgroups: %s\r\n", folder->full_name);
+
+ /* setup stream filtering */
+ crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+ filtered_stream = camel_stream_filter_new_with_stream (stream);
+ camel_stream_filter_add (filtered_stream, crlffilter);
+ camel_object_unref (crlffilter);
+
+ /* remove mail 'To', 'CC', and 'BCC' headers */
+ savedhdrs = NULL;
+ tail = (struct _camel_header_raw *) &savedhdrs;
+
+ header = (struct _camel_header_raw *) &CAMEL_MIME_PART (mime_message)->headers;
+ n = header->next;
+ while (n != NULL) {
+ if (!g_ascii_strcasecmp (n->name, "To") || !g_ascii_strcasecmp (n->name, "Cc") || !g_ascii_strcasecmp (n->name, "Bcc")) {
+ header->next = n->next;
+ tail->next = n;
+ n->next = NULL;
+ tail = n;
+ } else {
+ header = n;
+ }
+
+ n = header->next;
+ }
+
+ /* write the message */
+ if (camel_stream_write(stream, group, strlen(group)) == -1
+ || camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message), CAMEL_STREAM (filtered_stream)) == -1
+ || camel_stream_flush (CAMEL_STREAM (filtered_stream)) == -1
+ || camel_stream_write (stream, "\r\n.\r\n", 5) == -1
+ || (ret = camel_nntp_stream_line (nntp_store->stream, (unsigned char **)&line, &u)) == -1) {
+ if (errno == EINTR)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), g_strerror (errno));
+ } else if (atoi(line) != 240) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), line);
+ }
+
+ camel_object_unref (filtered_stream);
+ g_free(group);
+ header->next = savedhdrs;
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+
+ return;
+}
+
+static void
+nntp_folder_append_message_offline (CamelFolder *folder, CamelMimeMessage *mime_message,
+ const CamelMessageInfo *info, char **appended_uid,
+ CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("You cannot post NNTP messages while working offline!"));
+}
+
+/* I do not know what to do this exactly. Looking at the IMAP implementation for this, it
+ seems to assume the message is copied to a folder on the same store. In that case, an
+ NNTP implementation doesn't seem to make any sense. */
+static void
+nntp_folder_transfer_message (CamelFolder *source, GPtrArray *uids, CamelFolder *dest,
+ GPtrArray **transferred_uids, gboolean delete_orig, CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("You cannot copy messages from a NNTP folder!"));
+}
+
+static void
+nntp_folder_init (CamelNNTPFolder *nntp_folder, CamelNNTPFolderClass *klass)
+{
+ struct _CamelNNTPFolderPrivate *p;
+
+ nntp_folder->changes = camel_folder_change_info_new ();
+ p = nntp_folder->priv = g_malloc0 (sizeof (*nntp_folder->priv));
+ p->search_lock = g_mutex_new ();
+ p->cache_lock = g_mutex_new ();
+}
+
+static void
+nntp_folder_finalise (CamelNNTPFolder *nntp_folder)
+{
+ struct _CamelNNTPFolderPrivate *p;
+
+ camel_folder_summary_save (((CamelFolder*) nntp_folder)->summary);
+
+ p = nntp_folder->priv;
+ g_mutex_free (p->search_lock);
+ g_mutex_free (p->cache_lock);
+ g_free (p);
+}
+
+static void
+nntp_folder_class_init (CamelNNTPFolderClass *camel_nntp_folder_class)
+{
+ CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_nntp_folder_class);
+ CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_nntp_folder_class);
+
+ parent_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ()));
+ folder_class = CAMEL_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_folder_get_type ()));
+
+ /* virtual method definition */
+
+ /* virtual method overload */
+ camel_disco_folder_class->sync_online = nntp_folder_sync_online;
+ camel_disco_folder_class->sync_resyncing = nntp_folder_sync_offline;
+ camel_disco_folder_class->sync_offline = nntp_folder_sync_offline;
+ camel_disco_folder_class->cache_message = nntp_folder_cache_message;
+ camel_disco_folder_class->append_online = nntp_folder_append_message_online;
+ camel_disco_folder_class->append_resyncing = nntp_folder_append_message_online;
+ camel_disco_folder_class->append_offline = nntp_folder_append_message_offline;
+ camel_disco_folder_class->transfer_online = nntp_folder_transfer_message;
+ camel_disco_folder_class->transfer_resyncing = nntp_folder_transfer_message;
+ camel_disco_folder_class->transfer_offline = nntp_folder_transfer_message;
+ camel_disco_folder_class->refresh_info_online = nntp_folder_refresh_info_online;
+
+ camel_folder_class->set_message_flags = nntp_folder_set_message_flags;
+ camel_folder_class->get_message = nntp_folder_get_message;
+ camel_folder_class->search_by_expression = nntp_folder_search_by_expression;
+ camel_folder_class->search_by_uids = nntp_folder_search_by_uids;
+ camel_folder_class->search_free = nntp_folder_search_free;
+}
+
+CamelType
+camel_nntp_folder_get_type (void)
+{
+ static CamelType camel_nntp_folder_type = CAMEL_INVALID_TYPE;
+
+ if (camel_nntp_folder_type == CAMEL_INVALID_TYPE) {
+ camel_nntp_folder_type = camel_type_register (CAMEL_DISCO_FOLDER_TYPE, "CamelNNTPFolder",
+ sizeof (CamelNNTPFolder),
+ sizeof (CamelNNTPFolderClass),
+ (CamelObjectClassInitFunc) nntp_folder_class_init,
+ NULL,
+ (CamelObjectInitFunc) nntp_folder_init,
+ (CamelObjectFinalizeFunc) nntp_folder_finalise);
+ }
+
+ return camel_nntp_folder_type;
+}
+
+CamelFolder *
+camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelException *ex)
+{
+ CamelFolder *folder;
+ CamelNNTPFolder *nntp_folder;
+ char *root;
+ CamelService *service;
+ CamelStoreInfo *si;
+ gboolean subscribed = TRUE;
+
+ service = (CamelService *) parent;
+ root = camel_session_get_storage_path (service->session, service, ex);
+ if (root == NULL)
+ return NULL;
+
+ /* If this doesn't work, stuff wont save, but let it continue anyway */
+ camel_mkdir (root, 0777);
+
+ folder = (CamelFolder *) camel_object_new (CAMEL_NNTP_FOLDER_TYPE);
+ nntp_folder = (CamelNNTPFolder *)folder;
+
+ camel_folder_construct (folder, parent, folder_name, folder_name);
+ folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY|CAMEL_FOLDER_HAS_SEARCH_CAPABILITY;
+
+ nntp_folder->storage_path = g_build_filename (root, folder->full_name, NULL);
+ g_free (root);
+
+ root = g_strdup_printf ("%s.cmeta", nntp_folder->storage_path);
+ camel_object_set(nntp_folder, NULL, CAMEL_OBJECT_STATE_FILE, root, NULL);
+ camel_object_state_read(nntp_folder);
+ g_free(root);
+
+ root = g_strdup_printf("%s.ev-summary", nntp_folder->storage_path);
+ folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (root);
+ g_free(root);
+ camel_folder_summary_load (folder->summary);
+
+ si = camel_store_summary_path ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, folder_name);
+ if (si) {
+ subscribed = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_free ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, si);
+ }
+
+ if (subscribed) {
+ camel_folder_refresh_info(folder, ex);
+ if (camel_exception_is_set(ex)) {
+ camel_object_unref (folder);
+ folder = NULL;
+ }
+ }
+
+ return folder;
+}
diff --git a/camel/providers/nntp/camel-nntp-private.h b/camel/providers/nntp/camel-nntp-private.h
new file mode 100644
index 0000000000..253d4e2031
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-private.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * camel-nntp-private.h: Private info for nntp.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef CAMEL_NNTP_PRIVATE_H
+#define CAMEL_NNTP_PRIVATE_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+/* need a way to configure and save this data, if this header is to
+ be installed. For now, dont install it */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-util/e-msgport.h"
+
+struct _CamelNNTPStorePrivate {
+ int dummy;
+};
+
+#define CAMEL_NNTP_STORE_LOCK(f, l) (e_mutex_lock(((CamelNNTPStore *)f)->priv->l))
+#define CAMEL_NNTP_STORE_UNLOCK(f, l) (e_mutex_unlock(((CamelNNTPStore *)f)->priv->l))
+
+
+struct _CamelNNTPFolderPrivate {
+ GMutex *search_lock; /* for locking the search object */
+ GMutex *cache_lock; /* for locking the cache object */
+};
+
+#define CAMEL_NNTP_FOLDER_LOCK(f, l) (g_mutex_lock(((CamelNNTPFolder *)f)->priv->l))
+#define CAMEL_NNTP_FOLDER_UNLOCK(f, l) (g_mutex_unlock(((CamelNNTPFolder *)f)->priv->l))
+#else
+#define CAMEL_NNTP_FOLDER_LOCK(f, l)
+#define CAMEL_NNTP_FOLDER_UNLOCK(f, l)
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_NNTP_PRIVATE_H */
+
diff --git a/camel/providers/nntp/camel-nntp-store.c b/camel/providers/nntp/camel-nntp-store.c
new file mode 100644
index 0000000000..aa8541dc97
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-store.c
@@ -0,0 +1,1408 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ *
+ * Copyright (C) 2001-2003 Ximian, Inc. <www.ximain.com>
+ *
+ * Authors: Christopher Toshok <toshok@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include <camel/camel-url.h>
+#include <camel/camel-string-utils.h>
+#include <camel/camel-session.h>
+#include <camel/camel-tcp-stream-raw.h>
+#include <camel/camel-tcp-stream-ssl.h>
+
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-data-cache.h>
+
+#include <camel/camel-disco-store.h>
+#include <camel/camel-disco-diary.h>
+#include "camel/camel-private.h"
+#include <camel/camel-debug.h>
+
+#include "camel-nntp-summary.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-store-summary.h"
+#include "camel-nntp-folder.h"
+#include "camel-nntp-private.h"
+#include "camel-nntp-resp-codes.h"
+
+#define w(x)
+#define dd(x) (camel_debug("nntp")?(x):0)
+
+#define NNTP_PORT "119"
+#define NNTPS_PORT "563"
+
+#define DUMP_EXTENSIONS
+
+static CamelDiscoStoreClass *parent_class = NULL;
+static CamelServiceClass *service_class = NULL;
+
+/* Returns the class for a CamelNNTPStore */
+#define CNNTPS_CLASS(so) CAMEL_NNTP_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+#define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
+
+static int camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex);
+
+static void nntp_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+
+
+static gboolean
+nntp_can_work_offline(CamelDiscoStore *store)
+{
+ return TRUE;
+}
+
+enum {
+ USE_SSL_NEVER,
+ USE_SSL_ALWAYS,
+ USE_SSL_WHEN_POSSIBLE
+};
+
+static struct {
+ const char *name;
+ int type;
+} headers[] = {
+ { "subject", 0 },
+ { "from", 0 },
+ { "date", 0 },
+ { "message-id", 1 },
+ { "references", 0 },
+ { "bytes", 2 },
+};
+
+static int
+xover_setup(CamelNNTPStore *store, CamelException *ex)
+{
+ int ret, i;
+ char *line;
+ unsigned int len;
+ unsigned char c, *p;
+ struct _xover_header *xover, *last;
+
+ /* manual override */
+ if (store->xover || getenv("CAMEL_NNTP_DISABLE_XOVER") != NULL)
+ return 0;
+
+ ret = camel_nntp_raw_command_auth(store, ex, &line, "list overview.fmt");
+ if (ret == -1) {
+ return -1;
+ } else if (ret != 215)
+ /* unsupported command? ignore */
+ return 0;
+
+ last = (struct _xover_header *)&store->xover;
+
+ /* supported command */
+ while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
+ p = line;
+ xover = g_malloc0(sizeof(*xover));
+ last->next = xover;
+ last = xover;
+ while ((c = *p++)) {
+ if (c == ':') {
+ p[-1] = 0;
+ for (i=0;i<sizeof(headers)/sizeof(headers[0]);i++) {
+ if (strcmp(line, headers[i].name) == 0) {
+ xover->name = headers[i].name;
+ if (strncmp(p, "full", 4) == 0)
+ xover->skip = strlen(xover->name)+1;
+ else
+ xover->skip = 0;
+ xover->type = headers[i].type;
+ break;
+ }
+ }
+ break;
+ } else {
+ p[-1] = camel_tolower(c);
+ }
+ }
+ }
+
+ return ret;
+}
+
+static gboolean
+connect_to_server (CamelService *service, int ssl_mode, CamelException *ex)
+{
+ CamelNNTPStore *store = (CamelNNTPStore *) service;
+ CamelDiscoStore *disco_store = (CamelDiscoStore*) service;
+ CamelStream *tcp_stream;
+ gboolean retval = FALSE;
+ unsigned char *buf;
+ unsigned int len;
+ int ret;
+ char *path;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
+ const char *port = NULL;
+
+ CAMEL_SERVICE_LOCK(store, connect_lock);
+
+ /* setup store-wide cache */
+ if (store->cache == NULL) {
+ if (store->storage_path == NULL)
+ goto fail;
+
+ store->cache = camel_data_cache_new (store->storage_path, 0, ex);
+ if (store->cache == NULL)
+ goto fail;
+
+ /* Default cache expiry - 2 weeks old, or not visited in 5 days */
+ camel_data_cache_set_expire_age (store->cache, 60*60*24*14);
+ camel_data_cache_set_expire_access (store->cache, 60*60*24*5);
+ }
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else {
+ serv = "nntp";
+ port = NNTP_PORT;
+ }
+
+#ifdef HAVE_SSL
+ if (ssl_mode != USE_SSL_NEVER) {
+ if (service->url->port == 0) {
+ serv = "nntps";
+ port = NNTPS_PORT;
+ }
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3);
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+#else
+ tcp_stream = camel_tcp_stream_raw_new ();
+#endif /* HAVE_SSL */
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
+ camel_exception_clear(ex);
+ ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
+ }
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ goto fail;
+ }
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv, g_strerror (errno));
+
+ camel_object_unref (tcp_stream);
+
+ goto fail;
+ }
+
+ store->stream = (CamelNNTPStream *) camel_nntp_stream_new (tcp_stream);
+ camel_object_unref (tcp_stream);
+
+ /* Read the greeting, if any. */
+ if (camel_nntp_stream_line (store->stream, &buf, &len) == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not read greeting from %s: %s"),
+ service->url->host, g_strerror (errno));
+
+ camel_object_unref (store->stream);
+ store->stream = NULL;
+
+ goto fail;
+ }
+
+ len = strtoul (buf, (char **) &buf, 10);
+ if (len != 200 && len != 201) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("NNTP server %s returned error code %d: %s"),
+ service->url->host, len, buf);
+
+ camel_object_unref (store->stream);
+ store->stream = NULL;
+
+ goto fail;
+ }
+
+ /* if we have username, try it here */
+ if (service->url->user != NULL
+ && camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
+ goto fail;
+
+ /* set 'reader' mode & ignore return code, also ping the server, inn goes offline very quickly otherwise */
+ if (camel_nntp_raw_command_auth (store, ex, (char **) &buf, "mode reader") == -1
+ || camel_nntp_raw_command_auth (store, ex, (char **) &buf, "date") == -1)
+ goto fail;
+
+ if (xover_setup(store, ex) == -1)
+ goto fail;
+
+ path = g_build_filename (store->storage_path, ".ev-journal", NULL);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+
+ retval = TRUE;
+
+ g_free(store->current_folder);
+ store->current_folder = NULL;
+
+ fail:
+ CAMEL_SERVICE_UNLOCK(store, connect_lock);
+ return retval;
+}
+
+static struct {
+ char *value;
+ int mode;
+} ssl_options[] = {
+ { "", USE_SSL_ALWAYS },
+ { "always", USE_SSL_ALWAYS },
+ { "when-possible", USE_SSL_WHEN_POSSIBLE },
+ { "never", USE_SSL_NEVER },
+ { NULL, USE_SSL_NEVER },
+};
+
+static gboolean
+nntp_connect_online (CamelService *service, CamelException *ex)
+{
+#ifdef HAVE_SSL
+ const char *use_ssl;
+ int i, ssl_mode;
+
+ use_ssl = camel_url_get_param (service->url, "use_ssl");
+ if (use_ssl) {
+ for (i = 0; ssl_options[i].value; i++)
+ if (!strcmp (ssl_options[i].value, use_ssl))
+ break;
+ ssl_mode = ssl_options[i].mode;
+ } else
+ ssl_mode = USE_SSL_NEVER;
+
+ if (ssl_mode == USE_SSL_ALWAYS) {
+ /* Connect via SSL */
+ return connect_to_server (service, ssl_mode, ex);
+ } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ /* If the server supports SSL, use it */
+ if (!connect_to_server (service, ssl_mode, ex)) {
+ if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
+ /* The ssl port seems to be unavailable, fall back to plain NNTP */
+ camel_exception_clear (ex);
+ return connect_to_server (service, USE_SSL_NEVER, ex);
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else {
+ /* User doesn't care about SSL */
+ return connect_to_server (service, ssl_mode, ex);
+ }
+#else
+ return connect_to_server (service, USE_SSL_NEVER, ex);
+#endif
+}
+
+static gboolean
+nntp_connect_offline (CamelService *service, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
+ CamelDiscoStore *disco_store = (CamelDiscoStore *) nntp_store;
+ char *path;
+
+ if (nntp_store->storage_path == NULL)
+ return FALSE;
+
+ /* setup store-wide cache */
+ if (nntp_store->cache == NULL) {
+ nntp_store->cache = camel_data_cache_new (nntp_store->storage_path, 0, ex);
+ if (nntp_store->cache == NULL)
+ return FALSE;
+
+ /* Default cache expiry - 2 weeks old, or not visited in 5 days */
+ camel_data_cache_set_expire_age (nntp_store->cache, 60*60*24*14);
+ camel_data_cache_set_expire_access (nntp_store->cache, 60*60*24*5);
+ }
+
+ path = g_build_filename (nntp_store->storage_path, ".ev-journal", NULL);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+
+ if (!disco_store->diary)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+nntp_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelNNTPStore *store = CAMEL_NNTP_STORE (service);
+ char *line;
+
+ CAMEL_SERVICE_LOCK(store, connect_lock);
+
+ if (clean) {
+ camel_nntp_raw_command (store, ex, &line, "quit");
+ camel_exception_clear(ex);
+ }
+
+ if (!service_class->disconnect (service, clean, ex)) {
+ CAMEL_SERVICE_UNLOCK(store, connect_lock);
+ return FALSE;
+ }
+
+ camel_object_unref (store->stream);
+ store->stream = NULL;
+ g_free(store->current_folder);
+ store->current_folder = NULL;
+
+ CAMEL_SERVICE_UNLOCK(store, connect_lock);
+
+ return TRUE;
+}
+
+static gboolean
+nntp_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelDiscoStore *disco = CAMEL_DISCO_STORE(service);
+
+ if (!service_class->disconnect (service, clean, ex))
+ return FALSE;
+
+ if (disco->diary) {
+ camel_object_unref (disco->diary);
+ disco->diary = NULL;
+ }
+
+ return TRUE;
+}
+
+static char *
+nntp_store_get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup_printf ("%s", service->url->host);
+ else
+ return g_strdup_printf (_("USENET News via %s"), service->url->host);
+
+}
+
+extern CamelServiceAuthType camel_nntp_password_authtype;
+
+static GList *
+nntp_store_query_auth_types (CamelService *service, CamelException *ex)
+{
+ return g_list_append (NULL, &camel_nntp_password_authtype);
+}
+
+static CamelFolder *
+nntp_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
+ CamelFolder *folder;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ folder = camel_nntp_folder_new(store, folder_name, ex);
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+
+ return folder;
+}
+
+/*
+ * Converts a fully-fledged newsgroup name to a name in short dotted notation,
+ * e.g. nl.comp.os.linux.programmeren becomes n.c.o.l.programmeren
+ */
+
+static char *
+nntp_newsgroup_name_short (const char *name)
+{
+ char *resptr, *tmp;
+ const char *ptr2;
+
+ resptr = tmp = g_malloc0 (strlen (name) + 1);
+
+ while ((ptr2 = strchr (name, '.'))) {
+ if (ptr2 == name) {
+ name++;
+ continue;
+ }
+
+ *resptr++ = *name;
+ *resptr++ = '.';
+ name = ptr2 + 1;
+ }
+
+ strcpy (resptr, name);
+ return tmp;
+}
+
+/*
+ * This function converts a NNTPStoreSummary item to a FolderInfo item that
+ * can be returned by the get_folders() call to the store. Both structs have
+ * essentially the same fields.
+ */
+
+static CamelFolderInfo *
+nntp_folder_info_from_store_info (CamelNNTPStore *store, gboolean short_notation, CamelStoreInfo *si)
+{
+ CamelURL *base_url = ((CamelService *) store)->url;
+ CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
+ CamelURL *url;
+ char *path;
+
+ fi->full_name = g_strdup (si->path);
+
+ if (short_notation)
+ fi->name = nntp_newsgroup_name_short (si->path);
+ else
+ fi->name = g_strdup (si->path);
+
+ fi->unread = si->unread;
+ fi->total = si->total;
+ path = alloca(strlen(fi->full_name)+2);
+ sprintf(path, "/%s", fi->full_name);
+ url = camel_url_new_with_base (base_url, path);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+
+ return fi;
+}
+
+static CamelFolderInfo *
+nntp_folder_info_from_name (CamelNNTPStore *store, gboolean short_notation, const char *name)
+{
+ CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
+ CamelURL *base_url = ((CamelService *)store)->url;
+ CamelURL *url;
+ char *path;
+
+ fi->full_name = g_strdup (name);
+
+ if (short_notation)
+ fi->name = nntp_newsgroup_name_short (name);
+ else
+ fi->name = g_strdup (name);
+
+ fi->unread = -1;
+
+ path = alloca(strlen(fi->full_name)+2);
+ sprintf(path, "/%s", fi->full_name);
+ url = camel_url_new_with_base (base_url, path);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+
+ return fi;
+}
+
+/* handle list/newgroups response */
+static CamelNNTPStoreInfo *
+nntp_store_info_update(CamelNNTPStore *store, char *line)
+{
+ CamelStoreSummary *summ = (CamelStoreSummary *)store->summary;
+ CamelURL *base_url = ((CamelService *)store)->url;
+ CamelNNTPStoreInfo *si, *fsi;
+ CamelURL *url;
+ char *relpath, *tmp;
+ guint32 last = 0, first = 0, new = 0;
+
+ tmp = strchr(line, ' ');
+ if (tmp)
+ *tmp++ = 0;
+
+ fsi = si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, line);
+ if (si == NULL) {
+ si = (CamelNNTPStoreInfo*)camel_store_summary_info_new(summ);
+
+ relpath = g_alloca(strlen(line)+2);
+ sprintf(relpath, "/%s", line);
+ url = camel_url_new_with_base (base_url, relpath);
+ si->info.uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free (url);
+
+ si->info.path = g_strdup (line);
+ si->full_name = g_strdup (line); /* why do we keep this? */
+ camel_store_summary_add((CamelStoreSummary *)store->summary, &si->info);
+ } else {
+ first = si->first;
+ last = si->last;
+ }
+
+ if (tmp && *tmp >= '0' && *tmp <= '9') {
+ last = strtoul(tmp, &tmp, 10);
+ if (*tmp == ' ' && tmp[1] >= '0' && tmp[1] <= '9') {
+ first = strtoul(tmp+1, &tmp, 10);
+ if (*tmp == ' ' && tmp[1] != 'y')
+ si->info.flags |= CAMEL_STORE_INFO_FOLDER_READONLY;
+ }
+ }
+
+ printf("store info update '%s' first '%d' last '%d'\n", line, first, last);
+
+ if (si->last) {
+ if (last > si->last)
+ new = last-si->last;
+ } else {
+ if (last > first)
+ new = last - first;
+ }
+
+ si->info.total = last > first?last-first:0;
+ si->info.unread += new; /* this is a _guess_ */
+ si->last = last;
+ si->first = first;
+
+ if (fsi)
+ camel_store_summary_info_free((CamelStoreSummary *)store->summary, &fsi->info);
+ else /* TODO see if we really did touch it */
+ camel_store_summary_touch ((CamelStoreSummary *)store->summary);
+
+ return si;
+}
+
+static CamelFolderInfo *
+nntp_store_get_subscribed_folder_info (CamelNNTPStore *store, const char *top, guint flags, CamelException *ex)
+{
+ int i;
+ CamelStoreInfo *si;
+ CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
+
+ /* since we do not do a tree, any request that is not for root is sure to give no results */
+ if (top != NULL && top[0] != 0)
+ return NULL;
+
+ for (i=0;(si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i));i++) {
+ if (si == NULL)
+ continue;
+
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ /* slow mode? open and update the folder, always! this will implictly update
+ our storeinfo too; in a very round-about way */
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0) {
+ CamelNNTPFolder *folder;
+ char *line;
+
+ folder = (CamelNNTPFolder *)camel_store_get_folder((CamelStore *)store, si->path, 0, ex);
+ if (folder) {
+ CamelFolderChangeInfo *changes = NULL;
+
+ CAMEL_SERVICE_LOCK(store, connect_lock);
+ camel_nntp_command(store, ex, folder, &line, NULL);
+ if (camel_folder_change_info_changed(folder->changes)) {
+ changes = folder->changes;
+ folder->changes = camel_folder_change_info_new();
+ }
+ CAMEL_SERVICE_UNLOCK(store, connect_lock);
+ if (changes) {
+ camel_object_trigger_event((CamelObject *) folder, "folder_changed", changes);
+ camel_folder_change_info_free(changes);
+ }
+ camel_object_unref(folder);
+ }
+ camel_exception_clear(ex);
+ }
+ fi = nntp_folder_info_from_store_info (store, store->do_short_folder_notation, si);
+ fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_SYSTEM;
+ if (last)
+ last->next = fi;
+ else
+ first = fi;
+ last = fi;
+ }
+ camel_store_summary_info_free ((CamelStoreSummary *) store->summary, si);
+ }
+
+ return first;
+}
+
+/*
+ * get folder info, using the information in our StoreSummary
+ */
+static CamelFolderInfo *
+nntp_store_get_cached_folder_info (CamelNNTPStore *store, const char *orig_top, guint flags, CamelException *ex)
+{
+ int i;
+ int subscribed_or_flag = (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ? 0 : 1,
+ root_or_flag = (orig_top == NULL || orig_top[0] == '\0') ? 1 : 0,
+ recursive_flag = flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE;
+ CamelStoreInfo *si;
+ CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
+ char *tmpname;
+ char *top = g_strconcat(orig_top?orig_top:"", ".", NULL);
+ int toplen = strlen(top);
+
+ for (i = 0; (si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i)); i++) {
+ if ((subscribed_or_flag || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED))
+ && (root_or_flag || strncmp (si->path, top, toplen) == 0)) {
+ if (recursive_flag || strchr (si->path + toplen, '.') == NULL) {
+ /* add the item */
+ fi = nntp_folder_info_from_store_info(store, FALSE, si);
+ if (!fi)
+ continue;
+ if (store->folder_hierarchy_relative) {
+ g_free (fi->name);
+ fi->name = g_strdup (si->path + ((toplen == 1) ? 0 : toplen));
+ }
+ } else {
+ /* apparently, this is an indirect subitem. if it's not a subitem of
+ the item we added last, we need to add a portion of this item to
+ the list as a placeholder */
+ if (!last ||
+ strncmp(si->path, last->full_name, strlen(last->full_name)) != 0 ||
+ si->path[strlen(last->full_name)] != '.') {
+ tmpname = g_strdup(si->path);
+ *(strchr(tmpname + toplen, '.')) = '\0';
+ fi = nntp_folder_info_from_name(store, FALSE, tmpname);
+ fi->flags |= CAMEL_FOLDER_NOSELECT;
+ if (store->folder_hierarchy_relative) {
+ g_free(fi->name);
+ fi->name = g_strdup(tmpname + ((toplen==1) ? 0 : toplen));
+ }
+ g_free(tmpname);
+ } else {
+ continue;
+ }
+ }
+ if (last)
+ last->next = fi;
+ else
+ first = fi;
+ last = fi;
+ } else if (subscribed_or_flag && first) {
+ /* we have already added subitems, but this item is no longer a subitem */
+ camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
+ break;
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
+ }
+
+ g_free(top);
+ return first;
+}
+
+/* retrieves the date from the NNTP server */
+static gboolean
+nntp_get_date(CamelNNTPStore *nntp_store, CamelException *ex)
+{
+ unsigned char *line;
+ int ret = camel_nntp_command(nntp_store, ex, NULL, (char **)&line, "date");
+ char *ptr;
+
+ nntp_store->summary->last_newslist[0] = 0;
+
+ if (ret == 111) {
+ ptr = line + 3;
+ while (*ptr == ' ' || *ptr == '\t')
+ ptr++;
+
+ if (strlen (ptr) == NNTP_DATE_SIZE) {
+ memcpy (nntp_store->summary->last_newslist, ptr, NNTP_DATE_SIZE);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void
+store_info_remove(void *key, void *value, void *data)
+{
+ CamelStoreSummary *summary = data;
+ CamelStoreInfo *si = value;
+
+ camel_store_summary_remove(summary, si);
+}
+
+static gint
+store_info_sort (gconstpointer a, gconstpointer b)
+{
+ return strcmp ((*(CamelNNTPStoreInfo**) a)->full_name, (*(CamelNNTPStoreInfo**) b)->full_name);
+}
+
+static CamelFolderInfo *
+nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guint32 flags, gboolean online, CamelException *ex)
+{
+ CamelNNTPStoreSummary *summary = nntp_store->summary;
+ CamelNNTPStoreInfo *si;
+ unsigned int len;
+ unsigned char *line;
+ int ret = -1;
+ CamelFolderInfo *fi = NULL;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ if (top == NULL)
+ top = "";
+
+ if (online && (top == NULL || top[0] == 0)) {
+ /* we may need to update */
+ if (summary->last_newslist[0] != 0) {
+ char date[14];
+ memcpy(date, summary->last_newslist + 2, 6); /* YYMMDDD */
+ date[6] = ' ';
+ memcpy(date + 7, summary->last_newslist + 8, 6); /* HHMMSS */
+ date[13] = '\0';
+
+ if (!nntp_get_date (nntp_store, ex))
+ goto error;
+
+ ret = camel_nntp_command (nntp_store, ex, NULL, (char **) &line, "newgroups %s", date);
+ if (ret == -1)
+ goto error;
+ else if (ret != 231) {
+ /* newgroups not supported :S so reload the complete list */
+ summary->last_newslist[0] = 0;
+ goto do_complete_list;
+ }
+
+ while ((ret = camel_nntp_stream_line (nntp_store->stream, &line, &len)) > 0)
+ nntp_store_info_update(nntp_store, line);
+ } else {
+ GHashTable *all;
+ int i;
+
+ do_complete_list:
+ /* seems we do need a complete list */
+ /* at first, we do a DATE to find out the last load occasion */
+ if (!nntp_get_date (nntp_store, ex))
+ goto error;
+
+ ret = camel_nntp_command (nntp_store, ex, NULL, (char **)&line, "list");
+ if (ret == -1)
+ goto error;
+ else if (ret != 215) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID,
+ _("Error retrieving newsgroups:\n\n%s"), line);
+ goto error;
+ }
+
+ all = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i = 0; (si = (CamelNNTPStoreInfo *)camel_store_summary_index ((CamelStoreSummary *)nntp_store->summary, i)); i++)
+ g_hash_table_insert(all, si->info.path, si);
+
+ while ((ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) {
+ si = nntp_store_info_update(nntp_store, line);
+ g_hash_table_remove(all, si->info.path);
+ }
+
+ g_hash_table_foreach(all, store_info_remove, nntp_store->summary);
+ g_hash_table_destroy(all);
+ }
+
+ /* sort the list */
+ g_ptr_array_sort (CAMEL_STORE_SUMMARY (nntp_store->summary)->folders, store_info_sort);
+ if (ret < 0)
+ goto error;
+
+ camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
+ }
+
+ fi = nntp_store_get_cached_folder_info (nntp_store, top, flags, ex);
+ error:
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+
+ return fi;
+}
+
+static CamelFolderInfo *
+nntp_get_folder_info (CamelStore *store, const char *top, guint32 flags, gboolean online, CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
+ CamelFolderInfo *first = NULL;
+
+ dd(printf("g_f_i: fast %d subscr %d recursive %d online %d top \"%s\"\n",
+ flags & CAMEL_STORE_FOLDER_INFO_FAST,
+ flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
+ flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE,
+ online,
+ top?top:""));
+
+ if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
+ first = nntp_store_get_subscribed_folder_info (nntp_store, top, flags, ex);
+ else
+ first = nntp_store_get_folder_info_all (nntp_store, top, flags, online, ex);
+
+ return first;
+}
+
+static CamelFolderInfo *
+nntp_get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ return nntp_get_folder_info (store, top, flags, TRUE, ex);
+}
+
+static CamelFolderInfo *
+nntp_get_folder_info_offline(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ return nntp_get_folder_info (store, top, flags, FALSE, ex);
+}
+
+static gboolean
+nntp_store_folder_subscribed (CamelStore *store, const char *folder_name)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
+ CamelStoreInfo *si;
+ int truth = FALSE;
+
+ si = camel_store_summary_path ((CamelStoreSummary *) nntp_store->summary, folder_name);
+ if (si) {
+ truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_free ((CamelStoreSummary *) nntp_store->summary, si);
+ }
+
+ return truth;
+}
+
+static void
+nntp_store_subscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
+ CamelStoreInfo *si;
+ CamelFolderInfo *fi;
+
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ si = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
+ if (!si) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot subscribe to this newsgroup:\n\n"
+ "No such newsgroup. The selected item is a probably a parent folder."));
+ } else {
+ if (!(si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
+ si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ fi = nntp_folder_info_from_store_info(nntp_store, nntp_store->do_short_folder_notation, si);
+ fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN;
+ camel_store_summary_touch ((CamelStoreSummary *) nntp_store->summary);
+ camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+ camel_object_trigger_event ((CamelObject *) nntp_store, "folder_subscribed", fi);
+ camel_folder_info_free (fi);
+ return;
+ }
+ }
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+}
+
+static void
+nntp_store_unsubscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
+ CamelFolderInfo *fi;
+ CamelStoreInfo *fitem;
+ CAMEL_SERVICE_LOCK(nntp_store, connect_lock);
+
+ fitem = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
+
+ if (!fitem) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot unsubscribe to this newsgroup:\n\n"
+ "newsgroup does not exist!"));
+ } else {
+ if (fitem->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ fitem->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ fi = nntp_folder_info_from_store_info (nntp_store, nntp_store->do_short_folder_notation, fitem);
+ camel_store_summary_touch ((CamelStoreSummary *) nntp_store->summary);
+ camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+ camel_object_trigger_event ((CamelObject *) nntp_store, "folder_unsubscribed", fi);
+ camel_folder_info_free (fi);
+ return;
+ }
+ }
+
+ CAMEL_SERVICE_UNLOCK(nntp_store, connect_lock);
+}
+
+/* stubs for various folder operations we're not implementing */
+
+static CamelFolderInfo *
+nntp_create_folder (CamelStore *store, const char *parent_name,
+ const char *folder_name, CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot create a folder in a News store: subscribe instead."));
+ return NULL;
+}
+
+static void
+nntp_rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
+{
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot rename a folder in a News store."));
+}
+
+static void
+nntp_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ nntp_store_subscribe_folder (store, folder_name, ex);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("You cannot remove a folder in a News store: unsubscribe instead."));
+ return;
+}
+
+static void
+nntp_store_finalize (CamelObject *object)
+{
+ /* call base finalize */
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (object);
+ struct _CamelNNTPStorePrivate *p = nntp_store->priv;
+ struct _xover_header *xover, *xn;
+
+ camel_service_disconnect ((CamelService *)object, TRUE, NULL);
+
+ if (nntp_store->summary) {
+ camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
+ camel_object_unref (nntp_store->summary);
+ }
+
+ camel_object_unref (nntp_store->mem);
+ nntp_store->mem = NULL;
+ if (nntp_store->stream)
+ camel_object_unref (nntp_store->stream);
+
+ if (nntp_store->base_url)
+ g_free (nntp_store->base_url);
+ if (nntp_store->storage_path)
+ g_free (nntp_store->storage_path);
+
+ xover = nntp_store->xover;
+ while (xover) {
+ xn = xover->next;
+ g_free(xover);
+ xover = xn;
+ }
+
+ g_free(p);
+}
+
+static void
+nntp_store_class_init (CamelNNTPStoreClass *camel_nntp_store_class)
+{
+ CamelDiscoStoreClass *camel_disco_store_class = CAMEL_DISCO_STORE_CLASS (camel_nntp_store_class);
+ CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_nntp_store_class);
+ CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_nntp_store_class);
+
+ parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
+ service_class = CAMEL_SERVICE_CLASS (camel_type_get_global_classfuncs (camel_service_get_type ()));
+
+ /* virtual method overload */
+ camel_service_class->construct = nntp_construct;
+ camel_service_class->query_auth_types = nntp_store_query_auth_types;
+ camel_service_class->get_name = nntp_store_get_name;
+
+ camel_disco_store_class->can_work_offline = nntp_can_work_offline;
+ camel_disco_store_class->connect_online = nntp_connect_online;
+ camel_disco_store_class->connect_offline = nntp_connect_offline;
+ camel_disco_store_class->disconnect_online = nntp_disconnect_online;
+ camel_disco_store_class->disconnect_offline = nntp_disconnect_offline;
+ camel_disco_store_class->get_folder_online = nntp_get_folder;
+ camel_disco_store_class->get_folder_resyncing = nntp_get_folder;
+ camel_disco_store_class->get_folder_offline = nntp_get_folder;
+
+ camel_disco_store_class->get_folder_info_online = nntp_get_folder_info_online;
+ camel_disco_store_class->get_folder_info_resyncing = nntp_get_folder_info_online;
+ camel_disco_store_class->get_folder_info_offline = nntp_get_folder_info_offline;
+
+ camel_store_class->free_folder_info = camel_store_free_folder_info_full;
+
+ camel_store_class->folder_subscribed = nntp_store_folder_subscribed;
+ camel_store_class->subscribe_folder = nntp_store_subscribe_folder;
+ camel_store_class->unsubscribe_folder = nntp_store_unsubscribe_folder;
+
+ camel_store_class->create_folder = nntp_create_folder;
+ camel_store_class->delete_folder = nntp_delete_folder;
+ camel_store_class->rename_folder = nntp_rename_folder;
+}
+
+/* construction function in which we set some basic store properties */
+static void
+nntp_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
+ CamelURL *summary_url;
+ char *tmp;
+
+ /* construct the parent first */
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ /* find out the storage path, base url */
+ nntp_store->storage_path = camel_session_get_storage_path (session, service, ex);
+ if (!nntp_store->storage_path)
+ return;
+
+ /* FIXME */
+ nntp_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
+ CAMEL_URL_HIDE_PARAMS |
+ CAMEL_URL_HIDE_AUTH));
+
+ tmp = g_build_filename (nntp_store->storage_path, ".ev-store-summary", NULL);
+ nntp_store->summary = camel_nntp_store_summary_new ();
+ camel_store_summary_set_filename ((CamelStoreSummary *) nntp_store->summary, tmp);
+ summary_url = camel_url_new (nntp_store->base_url, NULL);
+ camel_store_summary_set_uri_base ((CamelStoreSummary *) nntp_store->summary, summary_url);
+ g_free (tmp);
+
+ camel_url_free (summary_url);
+ if (camel_store_summary_load ((CamelStoreSummary *)nntp_store->summary) == 0)
+ ;
+
+ /* get options */
+ if (camel_url_get_param (url, "show_short_notation"))
+ nntp_store->do_short_folder_notation = TRUE;
+ else
+ nntp_store->do_short_folder_notation = FALSE;
+ if (camel_url_get_param (url, "folder_hierarchy_relative"))
+ nntp_store->folder_hierarchy_relative = TRUE;
+ else
+ nntp_store->folder_hierarchy_relative = FALSE;
+}
+
+
+static void
+nntp_store_init (gpointer object, gpointer klass)
+{
+ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(object);
+ CamelStore *store = CAMEL_STORE (object);
+ struct _CamelNNTPStorePrivate *p;
+
+ store->flags = CAMEL_STORE_SUBSCRIPTIONS;
+
+ nntp_store->mem = (CamelStreamMem *)camel_stream_mem_new();
+
+ p = nntp_store->priv = g_malloc0(sizeof(*p));
+}
+
+CamelType
+camel_nntp_store_get_type (void)
+{
+ static CamelType camel_nntp_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_nntp_store_type == CAMEL_INVALID_TYPE) {
+ camel_nntp_store_type =
+ camel_type_register (CAMEL_DISCO_STORE_TYPE,
+ "CamelNNTPStore",
+ sizeof (CamelNNTPStore),
+ sizeof (CamelNNTPStoreClass),
+ (CamelObjectClassInitFunc) nntp_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) nntp_store_init,
+ (CamelObjectFinalizeFunc) nntp_store_finalize);
+ }
+
+ return camel_nntp_store_type;
+}
+
+static int
+camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex)
+{
+ CamelService *service = (CamelService *) store;
+ CamelSession *session = camel_service_get_session (service);
+ int ret;
+ char *line = NULL;
+
+ if (!service->url->user) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_INVALID_PARAM,
+ _("Authentication requested but no username provided"));
+ return -1;
+ }
+
+ /* if nessecary, prompt for the password */
+ if (!service->url->passwd) {
+ char *prompt, *base;
+ retry:
+ base = g_strdup_printf (_("Please enter the NNTP password for %s@%s"),
+ service->url->user,
+ service->url->host);
+ if (line) {
+ char *top = g_strdup_printf(_("Cannot authenticate to server: %s"), line);
+
+ prompt = g_strdup_printf("%s\n\n%s", top, base);
+ g_free(top);
+ } else {
+ prompt = base;
+ base = NULL;
+ }
+
+ service->url->passwd =
+ camel_session_get_password (session, service, NULL,
+ prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
+ g_free(prompt);
+ g_free(base);
+
+ if (!service->url->passwd)
+ return -1;
+ }
+
+ /* now, send auth info (currently, only authinfo user/pass is supported) */
+ ret = camel_nntp_raw_command(store, ex, &line, "authinfo user %s", service->url->user);
+ if (ret == NNTP_AUTH_CONTINUE)
+ ret = camel_nntp_raw_command(store, ex, &line, "authinfo pass %s", service->url->passwd);
+
+ if (ret != NNTP_AUTH_ACCEPTED) {
+ if (ret != -1) {
+ /* Need to forget the password here since we have no context on it */
+ camel_session_forget_password(session, service, NULL, "password", ex);
+ goto retry;
+ }
+ return -1;
+ }
+
+ return ret;
+}
+
+/* Enter owning lock */
+int
+camel_nntp_raw_commandv (CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, va_list ap)
+{
+ const unsigned char *p, *ps;
+ unsigned char c;
+ char *s;
+ int d;
+ unsigned int u, u2;
+
+ e_mutex_assert_locked(((CamelService *)store)->priv->connect_lock);
+ g_assert(store->stream->mode != CAMEL_NNTP_STREAM_DATA);
+
+ camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
+
+ ps = p = fmt;
+ while ((c = *p++)) {
+ switch (c) {
+ case '%':
+ c = *p++;
+ camel_stream_write ((CamelStream *) store->mem, ps, p - ps - (c == '%' ? 1 : 2));
+ ps = p;
+ switch (c) {
+ case 's':
+ s = va_arg(ap, char *);
+ camel_stream_write((CamelStream *)store->mem, s, strlen(s));
+ break;
+ case 'd':
+ d = va_arg(ap, int);
+ camel_stream_printf((CamelStream *)store->mem, "%d", d);
+ break;
+ case 'u':
+ u = va_arg(ap, unsigned int);
+ camel_stream_printf((CamelStream *)store->mem, "%u", u);
+ break;
+ case 'm':
+ s = va_arg(ap, char *);
+ camel_stream_printf((CamelStream *)store->mem, "<%s>", s);
+ break;
+ case 'r':
+ u = va_arg(ap, unsigned int);
+ u2 = va_arg(ap, unsigned int);
+ if (u == u2)
+ camel_stream_printf((CamelStream *)store->mem, "%u", u);
+ else
+ camel_stream_printf((CamelStream *)store->mem, "%u-%u", u, u2);
+ break;
+ default:
+ g_warning("Passing unknown format to nntp_command: %c\n", c);
+ g_assert(0);
+ }
+ }
+ }
+
+ camel_stream_write ((CamelStream *) store->mem, ps, p-ps-1);
+ dd(printf("NNTP_COMMAND: '%.*s'\n", (int)store->mem->buffer->len, store->mem->buffer->data));
+ camel_stream_write ((CamelStream *) store->mem, "\r\n", 2);
+
+ if (camel_stream_write((CamelStream *) store->stream, store->mem->buffer->data, store->mem->buffer->len) == -1)
+ goto ioerror;
+
+ /* FIXME: hack */
+ camel_stream_reset ((CamelStream *) store->mem);
+ g_byte_array_set_size (store->mem->buffer, 0);
+
+ if (camel_nntp_stream_line (store->stream, (unsigned char **) line, &u) == -1)
+ goto ioerror;
+
+ u = strtoul (*line, NULL, 10);
+
+ /* Handle all switching to data mode here, to make callers job easier */
+ if (u == 215 || (u >= 220 && u <=224) || (u >= 230 && u <= 231))
+ camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_DATA);
+
+ return u;
+
+ioerror:
+ if (errno == EINTR)
+ camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled."));
+ else
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("NNTP Command failed: %s"), g_strerror(errno));
+ return -1;
+}
+
+int
+camel_nntp_raw_command(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+/* use this where you also need auth to be handled, i.e. most cases where you'd try raw command */
+int
+camel_nntp_raw_command_auth(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...)
+{
+ int ret, retry, go;
+ va_list ap;
+
+ retry = 0;
+
+ do {
+ go = FALSE;
+ retry++;
+
+ va_start(ap, fmt);
+ ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
+ va_end(ap);
+
+ if (ret == NNTP_AUTH_REQUIRED) {
+ if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
+ return -1;
+ go = TRUE;
+ }
+ } while (retry < 3 && go);
+
+ return ret;
+}
+
+int
+camel_nntp_command (CamelNNTPStore *store, CamelException *ex, CamelNNTPFolder *folder, char **line, const char *fmt, ...)
+{
+ const unsigned char *p;
+ va_list ap;
+ int ret, retry;
+ unsigned int u;
+
+ e_mutex_assert_locked(((CamelService *)store)->priv->connect_lock);
+
+ if (((CamelDiscoStore *)store)->status == CAMEL_DISCO_STORE_OFFLINE) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
+ _("Not connected."));
+ return -1;
+ }
+
+ retry = 0;
+ do {
+ retry ++;
+
+ if (store->stream == NULL
+ && !camel_service_connect (CAMEL_SERVICE (store), ex))
+ return -1;
+
+ /* Check for unprocessed data, ! */
+ if (store->stream->mode == CAMEL_NNTP_STREAM_DATA) {
+ g_warning("Unprocessed data left in stream, flushing");
+ while (camel_nntp_stream_getd(store->stream, (unsigned char **)&p, &u) > 0)
+ ;
+ }
+ camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
+
+ if (folder != NULL
+ && (store->current_folder == NULL || strcmp(store->current_folder, ((CamelFolder *)folder)->full_name) != 0)) {
+ ret = camel_nntp_raw_command_auth(store, ex, line, "group %s", ((CamelFolder *)folder)->full_name);
+ if (ret == 211) {
+ g_free(store->current_folder);
+ store->current_folder = g_strdup(((CamelFolder *)folder)->full_name);
+ camel_nntp_folder_selected(folder, *line, ex);
+ if (camel_exception_is_set(ex)) {
+ ret = -1;
+ goto error;
+ }
+ } else {
+ goto error;
+ }
+ }
+
+ /* dummy fmt, we just wanted to select the folder */
+ if (fmt == NULL)
+ return 0;
+
+ va_start(ap, fmt);
+ ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
+ va_end(ap);
+ error:
+ switch (ret) {
+ case NNTP_AUTH_REQUIRED:
+ if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
+ return -1;
+ retry--;
+ ret = -1;
+ continue;
+ case 411: /* no such group */
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("No such folder: %s"), line);
+ return -1;
+ case 400: /* service discontinued */
+ case 401: /* wrong client state - this should quit but this is what the old code did */
+ case 503: /* information not available - this should quit but this is what the old code did (?) */
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ ret = -1;
+ continue;
+ case -1: /* i/o error */
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL || retry >= 3)
+ return -1;
+ camel_exception_clear(ex);
+ break;
+ }
+ } while (ret == -1 && retry < 3);
+
+ return ret;
+}
diff --git a/camel/providers/nntp/camel-nntp-store.h b/camel/providers/nntp/camel-nntp-store.h
new file mode 100644
index 0000000000..a9bd682cbd
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-store.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-nntp-store.h : class for an nntp store */
+
+/*
+ *
+ * Copyright (C) 2000 Ximian, Inc. <toshok@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifndef CAMEL_NNTP_STORE_H
+#define CAMEL_NNTP_STORE_H 1
+
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#include <camel/camel-disco-store.h>
+
+#include "camel-nntp-stream.h"
+#include "camel-nntp-store-summary.h"
+
+struct _CamelNNTPFolder;
+struct _CamelException;
+
+#define CAMEL_NNTP_STORE_TYPE (camel_nntp_store_get_type ())
+#define CAMEL_NNTP_STORE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_NNTP_STORE_TYPE, CamelNNTPStore))
+#define CAMEL_NNTP_STORE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_NNTP_STORE_TYPE, CamelNNTPStoreClass))
+#define CAMEL_IS_NNTP_STORE(o) (CAMEL_CHECK_TYPE((o), CAMEL_NNTP_STORE_TYPE))
+
+#define CAMEL_NNTP_EXT_SEARCH (1<<0)
+#define CAMEL_NNTP_EXT_SETGET (1<<1)
+#define CAMEL_NNTP_EXT_OVER (1<<2)
+#define CAMEL_NNTP_EXT_XPATTEXT (1<<3)
+#define CAMEL_NNTP_EXT_XACTIVE (1<<4)
+#define CAMEL_NNTP_EXT_LISTMOTD (1<<5)
+#define CAMEL_NNTP_EXT_LISTSUBSCR (1<<6)
+#define CAMEL_NNTP_EXT_LISTPNAMES (1<<7)
+
+typedef struct _CamelNNTPStore CamelNNTPStore;
+typedef struct _CamelNNTPStoreClass CamelNNTPStoreClass;
+
+enum _xover_t {
+ XOVER_STRING = 0,
+ XOVER_MSGID,
+ XOVER_SIZE,
+};
+
+struct _xover_header {
+ struct _xover_header *next;
+
+ const char *name;
+ unsigned int skip:8;
+ enum _xover_t type:8;
+};
+
+struct _CamelNNTPStore {
+ CamelDiscoStore parent_object;
+
+ struct _CamelNNTPStorePrivate *priv;
+
+ guint32 extensions;
+
+ unsigned int posting_allowed:1;
+ unsigned int do_short_folder_notation:1;
+ unsigned int folder_hierarchy_relative:1;
+
+ struct _CamelNNTPStoreSummary *summary;
+
+ struct _CamelNNTPStream *stream;
+ struct _CamelStreamMem *mem;
+
+ struct _CamelDataCache *cache;
+
+ char *current_folder, *storage_path, *base_url;
+
+ struct _xover_header *xover;
+};
+
+struct _CamelNNTPStoreClass {
+ CamelDiscoStoreClass parent_class;
+
+};
+
+/* Standard Camel function */
+CamelType camel_nntp_store_get_type (void);
+
+int camel_nntp_raw_commandv (CamelNNTPStore *store, struct _CamelException *ex, char **line, const char *fmt, va_list ap);
+int camel_nntp_raw_command(CamelNNTPStore *store, struct _CamelException *ex, char **line, const char *fmt, ...);
+int camel_nntp_raw_command_auth(CamelNNTPStore *store, struct _CamelException *ex, char **line, const char *fmt, ...);
+int camel_nntp_command (CamelNNTPStore *store, struct _CamelException *ex, struct _CamelNNTPFolder *folder, char **line, const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_NNTP_STORE_H */
+
+
diff --git a/camel/providers/nntp/camel-nntp-stream.c b/camel/providers/nntp/camel-nntp-stream.c
new file mode 100644
index 0000000000..74bee9ced5
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-stream.c
@@ -0,0 +1,462 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Author:
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <string.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include "camel-nntp-stream.h"
+#include "camel-debug.h"
+
+#define dd(x) (camel_debug("nntp:stream")?(x):0)
+
+static CamelObjectClass *parent_class = NULL;
+
+/* Returns the class for a CamelStream */
+#define CS_CLASS(so) CAMEL_NNTP_STREAM_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+
+#define CAMEL_NNTP_STREAM_SIZE (4096)
+#define CAMEL_NNTP_STREAM_LINE_SIZE (1024) /* maximum line size */
+
+static int
+stream_fill(CamelNNTPStream *is)
+{
+ int left = 0;
+
+ if (is->source) {
+ left = is->end - is->ptr;
+ memcpy(is->buf, is->ptr, left);
+ is->end = is->buf + left;
+ is->ptr = is->buf;
+ left = camel_stream_read(is->source, is->end, CAMEL_NNTP_STREAM_SIZE - (is->end - is->buf));
+ if (left > 0) {
+ is->end += left;
+ is->end[0] = '\n';
+ return is->end - is->ptr;
+ } else {
+ dd(printf("NNTP_STREAM_FILL(ERROR): %d - '%s'\n", left, strerror(errno)));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static ssize_t
+stream_read(CamelStream *stream, char *buffer, size_t n)
+{
+ CamelNNTPStream *is = (CamelNNTPStream *)stream;
+ char *o, *oe;
+ unsigned char *p, *e, c;
+ int state;
+
+ if (is->mode != CAMEL_NNTP_STREAM_DATA || n == 0)
+ return 0;
+
+ o = buffer;
+ oe = buffer + n;
+ state = is->state;
+
+ /* Need to copy/strip '.'s and whatnot */
+ p = is->ptr;
+ e = is->end;
+
+ switch(state) {
+ state_0:
+ case 0: /* start of line, always read at least 3 chars */
+ while (e - p < 3) {
+ is->ptr = p;
+ if (stream_fill(is) == -1)
+ return -1;
+ p = is->ptr;
+ e = is->end;
+ }
+ if (p[0] == '.') {
+ if (p[1] == '\r' && p[2] == '\n') {
+ is->ptr = p+3;
+ is->mode = CAMEL_NNTP_STREAM_EOD;
+ is->state = 0;
+ dd(printf("NNTP_STREAM_READ(%d):\n%.*s\n", o-buffer, o-buffer, buffer));
+ return o-buffer;
+ }
+ p++;
+ }
+ state = 1;
+ /* FALLS THROUGH */
+ case 1: /* looking for next sol */
+ while (o < oe) {
+ c = *p++;
+ if (c == '\n') {
+ /* end of input sentinal check */
+ if (p > e) {
+ is->ptr = e;
+ if (stream_fill(is) == -1)
+ return -1;
+ p = is->ptr;
+ e = is->end;
+ } else {
+ *o++ = '\n';
+ state = 0;
+ goto state_0;
+ }
+ } else if (c != '\r') {
+ *o++ = c;
+ }
+ }
+ break;
+ }
+
+ is->ptr = p;
+ is->state = state;
+
+ dd(printf("NNTP_STREAM_READ(%d):\n%.*s\n", o-buffer, o-buffer, buffer));
+
+ return o-buffer;
+}
+
+static ssize_t
+stream_write(CamelStream *stream, const char *buffer, size_t n)
+{
+ CamelNNTPStream *is = (CamelNNTPStream *)stream;
+
+ return camel_stream_write(is->source, buffer, n);
+}
+
+static int
+stream_close(CamelStream *stream)
+{
+ /* nop? */
+ return 0;
+}
+
+static int
+stream_flush(CamelStream *stream)
+{
+ /* nop? */
+ return 0;
+}
+
+static gboolean
+stream_eos(CamelStream *stream)
+{
+ CamelNNTPStream *is = (CamelNNTPStream *)stream;
+
+ return is->mode != CAMEL_NNTP_STREAM_DATA;
+}
+
+static int
+stream_reset(CamelStream *stream)
+{
+ /* nop? reset literal mode? */
+ return 0;
+}
+
+static void
+camel_nntp_stream_class_init (CamelStreamClass *camel_nntp_stream_class)
+{
+ CamelStreamClass *camel_stream_class = (CamelStreamClass *)camel_nntp_stream_class;
+
+ parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE );
+
+ /* virtual method definition */
+ camel_stream_class->read = stream_read;
+ camel_stream_class->write = stream_write;
+ camel_stream_class->close = stream_close;
+ camel_stream_class->flush = stream_flush;
+ camel_stream_class->eos = stream_eos;
+ camel_stream_class->reset = stream_reset;
+}
+
+static void
+camel_nntp_stream_init(CamelNNTPStream *is, CamelNNTPStreamClass *isclass)
+{
+ /* +1 is room for appending a 0 if we need to for a line */
+ is->ptr = is->end = is->buf = g_malloc(CAMEL_NNTP_STREAM_SIZE+1);
+ is->lineptr = is->linebuf = g_malloc(CAMEL_NNTP_STREAM_LINE_SIZE+1);
+ is->lineend = is->linebuf + CAMEL_NNTP_STREAM_LINE_SIZE;
+
+ /* init sentinal */
+ is->ptr[0] = '\n';
+
+ is->state = 0;
+ is->mode = CAMEL_NNTP_STREAM_LINE;
+}
+
+static void
+camel_nntp_stream_finalise(CamelNNTPStream *is)
+{
+ g_free(is->buf);
+ g_free(is->linebuf);
+ if (is->source)
+ camel_object_unref((CamelObject *)is->source);
+}
+
+CamelType
+camel_nntp_stream_get_type (void)
+{
+ static CamelType camel_nntp_stream_type = CAMEL_INVALID_TYPE;
+
+ if (camel_nntp_stream_type == CAMEL_INVALID_TYPE) {
+ camel_nntp_stream_type = camel_type_register( camel_stream_get_type(),
+ "CamelNNTPStream",
+ sizeof( CamelNNTPStream ),
+ sizeof( CamelNNTPStreamClass ),
+ (CamelObjectClassInitFunc) camel_nntp_stream_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_nntp_stream_init,
+ (CamelObjectFinalizeFunc) camel_nntp_stream_finalise );
+ }
+
+ return camel_nntp_stream_type;
+}
+
+/**
+ * camel_nntp_stream_new:
+ *
+ * Returns a NULL stream. A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Return value: the stream
+ **/
+CamelStream *
+camel_nntp_stream_new(CamelStream *source)
+{
+ CamelNNTPStream *is;
+
+ is = (CamelNNTPStream *)camel_object_new(camel_nntp_stream_get_type ());
+ camel_object_ref((CamelObject *)source);
+ is->source = source;
+
+ return (CamelStream *)is;
+}
+
+/* Get one line from the nntp stream */
+int
+camel_nntp_stream_line(CamelNNTPStream *is, unsigned char **data, unsigned int *len)
+{
+ register unsigned char c, *p, *o, *oe;
+ int newlen, oldlen;
+ unsigned char *e;
+
+ if (is->mode == CAMEL_NNTP_STREAM_EOD) {
+ *data = is->linebuf;
+ *len = 0;
+ return 0;
+ }
+
+ o = is->linebuf;
+ oe = is->lineend - 1;
+ p = is->ptr;
+ e = is->end;
+
+ /* Data mode, convert leading '..' to '.', and stop when we reach a solitary '.' */
+ if (is->mode == CAMEL_NNTP_STREAM_DATA) {
+ /* need at least 3 chars in buffer */
+ while (e-p < 3) {
+ is->ptr = p;
+ if (stream_fill(is) == -1)
+ return -1;
+ p = is->ptr;
+ e = is->end;
+ }
+
+ /* check for isolated '.\r\n' or begging of line '.' */
+ if (p[0] == '.') {
+ if (p[1] == '\r' && p[2] == '\n') {
+ is->ptr = p+3;
+ is->mode = CAMEL_NNTP_STREAM_EOD;
+ *data = is->linebuf;
+ *len = 0;
+ is->linebuf[0] = 0;
+
+ dd(printf("NNTP_STREAM_LINE(END)\n"));
+
+ return 0;
+ }
+ p++;
+ }
+ }
+
+ while (1) {
+ while (o < oe) {
+ c = *p++;
+ if (c == '\n') {
+ /* sentinal? */
+ if (p> e) {
+ is->ptr = e;
+ if (stream_fill(is) == -1)
+ return -1;
+ p = is->ptr;
+ e = is->end;
+ } else {
+ is->ptr = p;
+ *data = is->linebuf;
+ *len = o - is->linebuf;
+ *o = 0;
+
+ dd(printf("NNTP_STREAM_LINE(%d): '%s'\n", *len, *data));
+
+ return 1;
+ }
+ } else if (c != '\r') {
+ *o++ = c;
+ }
+ }
+
+ /* limit this for bad server data? */
+ oldlen = o - is->linebuf;
+ newlen = (is->lineend - is->linebuf) * 3 / 2;
+ is->lineptr = is->linebuf = g_realloc(is->linebuf, newlen);
+ is->lineend = is->linebuf + newlen;
+ oe = is->lineend - 1;
+ o = is->linebuf + oldlen;
+ }
+
+ return -1;
+}
+
+/* returns -1 on error, 0 if last lot of data, >0 if more remaining */
+int camel_nntp_stream_gets(CamelNNTPStream *is, unsigned char **start, unsigned int *len)
+{
+ int max;
+ unsigned char *end;
+
+ *len = 0;
+
+ max = is->end - is->ptr;
+ if (max == 0) {
+ max = stream_fill(is);
+ if (max <= 0)
+ return max;
+ }
+
+ *start = is->ptr;
+ end = memchr(is->ptr, '\n', max);
+ if (end)
+ max = (end - is->ptr) + 1;
+ *start = is->ptr;
+ *len = max;
+ is->ptr += max;
+
+ dd(printf("NNTP_STREAM_GETS(%s,%d): '%.*s'\n", end==NULL?"more":"last", *len, (int)*len, *start));
+
+ return end == NULL?1:0;
+}
+
+void camel_nntp_stream_set_mode(CamelNNTPStream *is, camel_nntp_stream_mode_t mode)
+{
+ is->mode = mode;
+}
+
+/* returns -1 on erorr, 0 if last data, >0 if more data left */
+int camel_nntp_stream_getd(CamelNNTPStream *is, unsigned char **start, unsigned int *len)
+{
+ unsigned char *p, *e, *s;
+ int state;
+
+ *len = 0;
+
+ if (is->mode == CAMEL_NNTP_STREAM_EOD)
+ return 0;
+
+ if (is->mode == CAMEL_NNTP_STREAM_LINE) {
+ g_warning("nntp_stream reading data in line mode\n");
+ return 0;
+ }
+
+ state = is->state;
+ p = is->ptr;
+ e = is->end;
+
+ while (e - p < 3) {
+ is->ptr = p;
+ if (stream_fill(is) == -1)
+ return -1;
+ p = is->ptr;
+ e = is->end;
+ }
+
+ s = p;
+
+ do {
+ switch(state) {
+ case 0:
+ /* check leading '.', ... */
+ if (p[0] == '.') {
+ if (p[1] == '\r' && p[2] == '\n') {
+ is->ptr = p+3;
+ *len = p-s;
+ *start = s;
+ is->mode = CAMEL_NNTP_STREAM_EOD;
+ is->state = 0;
+
+ dd(printf("NNTP_STREAM_GETD(%s,%d): '%.*s'\n", "last", *len, (int)*len, *start));
+
+ return 0;
+ }
+
+ /* If at start, just skip '.', else return data upto '.' but skip it */
+ if (p == s) {
+ s++;
+ p++;
+ } else {
+ is->ptr = p+1;
+ *len = p-s;
+ *start = s;
+ is->state = 1;
+
+ dd(printf("NNTP_STREAM_GETD(%s,%d): '%.*s'\n", "more", *len, (int)*len, *start));
+
+ return 1;
+ }
+ }
+ state = 1;
+ case 1:
+ /* Scan for sentinal */
+ while ((*p++)!='\n')
+ ;
+
+ if (p > e) {
+ p = e;
+ } else {
+ state = 0;
+ }
+ break;
+ }
+ } while ((e-p) >= 3);
+
+ is->state = state;
+ is->ptr = p;
+ *len = p-s;
+ *start = s;
+
+ dd(printf("NNTP_STREAM_GETD(%s,%d): '%.*s'\n", "more", *len, (int)*len, *start));
+ return 1;
+}
+
diff --git a/camel/providers/nntp/camel-nntp-summary.c b/camel/providers/nntp/camel-nntp-summary.c
new file mode 100644
index 0000000000..02589e632e
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-summary.c
@@ -0,0 +1,504 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "camel/camel-file-utils.h"
+#include "camel/camel-mime-message.h"
+#include "camel/camel-stream-null.h"
+#include "camel/camel-operation.h"
+#include "camel/camel-data-cache.h"
+#include "camel/camel-debug.h"
+
+#include "camel-nntp-summary.h"
+#include "camel-nntp-folder.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-stream.h"
+
+#define w(x)
+#define io(x)
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+#define dd(x) (camel_debug("nntp")?(x):0)
+
+#define CAMEL_NNTP_SUMMARY_VERSION (1)
+
+struct _CamelNNTPSummaryPrivate {
+ char *uid;
+
+ struct _xover_header *xover; /* xoverview format */
+ int xover_setup;
+};
+
+#define _PRIVATE(o) (((CamelNNTPSummary *)(o))->priv)
+
+static CamelMessageInfo * message_info_new (CamelFolderSummary *, struct _camel_header_raw *);
+static int summary_header_load(CamelFolderSummary *, FILE *);
+static int summary_header_save(CamelFolderSummary *, FILE *);
+
+static void camel_nntp_summary_class_init (CamelNNTPSummaryClass *klass);
+static void camel_nntp_summary_init (CamelNNTPSummary *obj);
+static void camel_nntp_summary_finalise (CamelObject *obj);
+static CamelFolderSummaryClass *camel_nntp_summary_parent;
+
+CamelType
+camel_nntp_summary_get_type(void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register(camel_folder_summary_get_type(), "CamelNNTPSummary",
+ sizeof (CamelNNTPSummary),
+ sizeof (CamelNNTPSummaryClass),
+ (CamelObjectClassInitFunc) camel_nntp_summary_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_nntp_summary_init,
+ (CamelObjectFinalizeFunc) camel_nntp_summary_finalise);
+ }
+
+ return type;
+}
+
+static void
+camel_nntp_summary_class_init(CamelNNTPSummaryClass *klass)
+{
+ CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *) klass;
+
+ camel_nntp_summary_parent = CAMEL_FOLDER_SUMMARY_CLASS(camel_type_get_global_classfuncs(camel_folder_summary_get_type()));
+
+ sklass->message_info_new = message_info_new;
+ sklass->summary_header_load = summary_header_load;
+ sklass->summary_header_save = summary_header_save;
+}
+
+static void
+camel_nntp_summary_init(CamelNNTPSummary *obj)
+{
+ struct _CamelNNTPSummaryPrivate *p;
+ struct _CamelFolderSummary *s = (CamelFolderSummary *)obj;
+
+ p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
+
+ /* subclasses need to set the right instance data sizes */
+ s->message_info_size = sizeof(CamelMessageInfo);
+ s->content_info_size = sizeof(CamelMessageContentInfo);
+
+ /* and a unique file version */
+ s->version += CAMEL_NNTP_SUMMARY_VERSION;
+}
+
+static void
+camel_nntp_summary_finalise(CamelObject *obj)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(obj);
+
+ g_free(cns->priv);
+}
+
+CamelNNTPSummary *
+camel_nntp_summary_new(const char *path)
+{
+ CamelNNTPSummary *cns = (CamelNNTPSummary *)camel_object_new(camel_nntp_summary_get_type());
+
+ camel_folder_summary_set_filename((CamelFolderSummary *)cns, path);
+ camel_folder_summary_set_build_content((CamelFolderSummary *)cns, FALSE);
+
+ return cns;
+}
+
+static CamelMessageInfo *
+message_info_new(CamelFolderSummary *s, struct _camel_header_raw *h)
+{
+ CamelMessageInfo *mi;
+ CamelNNTPSummary *cns = (CamelNNTPSummary *)s;
+
+ /* error to call without this setup */
+ if (cns->priv->uid == NULL)
+ return NULL;
+
+ /* we shouldn't be here if we already have this uid */
+ g_assert(camel_folder_summary_uid(s, cns->priv->uid) == NULL);
+
+ mi = ((CamelFolderSummaryClass *)camel_nntp_summary_parent)->message_info_new(s, h);
+ if (mi) {
+ camel_message_info_set_uid(mi, cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+
+ return mi;
+}
+
+static int
+summary_header_load(CamelFolderSummary *s, FILE *in)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
+
+ if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_load(s, in) == -1)
+ return -1;
+
+ /* Legacy version */
+ if (s->version == 0x20c) {
+ camel_file_util_decode_fixed_int32(in, &cns->high);
+ return camel_file_util_decode_fixed_int32(in, &cns->low);
+ }
+
+ if (camel_file_util_decode_fixed_int32(in, &cns->version) == -1)
+ return -1;
+
+ if (cns->version > CAMEL_NNTP_SUMMARY_VERSION) {
+ g_warning("Unknown NNTP summary version");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (camel_file_util_decode_fixed_int32(in, &cns->high) == -1
+ || camel_file_util_decode_fixed_int32(in, &cns->low) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int
+summary_header_save(CamelFolderSummary *s, FILE *out)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
+
+ if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_save(s, out) == -1
+ || camel_file_util_encode_fixed_int32(out, CAMEL_NNTP_SUMMARY_VERSION) == -1
+ || camel_file_util_encode_fixed_int32(out, cns->high) == -1
+ || camel_file_util_encode_fixed_int32(out, cns->low) == -1)
+ return -1;
+
+ return 0;
+}
+
+/* ********************************************************************** */
+
+/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
+static int
+add_range_xover(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex)
+{
+ CamelFolderSummary *s;
+ CamelMessageInfo *mi;
+ struct _camel_header_raw *headers = NULL;
+ char *line, *tab;
+ int len, ret;
+ unsigned int n, count, total, size;
+ struct _xover_header *xover;
+
+ s = (CamelFolderSummary *)cns;
+
+ camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host);
+
+ ret = camel_nntp_raw_command_auth(store, ex, &line, "xover %r", low, high);
+ if (ret != 224) {
+ camel_operation_end(NULL);
+ if (ret != -1)
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected server response from xover: %s"), line);
+ return -1;
+ }
+
+ count = 0;
+ total = high-low+1;
+ while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
+ camel_operation_progress(NULL, (count * 100) / total);
+ count++;
+ n = strtoul(line, &tab, 10);
+ if (*tab != '\t')
+ continue;
+ tab++;
+ xover = store->xover;
+ size = 0;
+ for (;tab[0] && xover;xover = xover->next) {
+ line = tab;
+ tab = strchr(line, '\t');
+ if (tab)
+ *tab++ = 0;
+ else
+ tab = line+strlen(line);
+
+ /* do we care about this column? */
+ if (xover->name) {
+ line += xover->skip;
+ if (line < tab) {
+ camel_header_raw_append(&headers, xover->name, line, -1);
+ switch(xover->type) {
+ case XOVER_STRING:
+ break;
+ case XOVER_MSGID:
+ cns->priv->uid = g_strdup_printf("%u,%s", n, line);
+ break;
+ case XOVER_SIZE:
+ size = strtoul(line, NULL, 10);
+ break;
+ }
+ }
+ }
+ }
+
+ /* skip headers we don't care about, incase the server doesn't actually send some it said it would. */
+ while (xover && xover->name == NULL)
+ xover = xover->next;
+
+ /* truncated line? ignore? */
+ if (xover == NULL) {
+ mi = camel_folder_summary_uid(s, cns->priv->uid);
+ if (mi == NULL) {
+ mi = camel_folder_summary_add_from_header(s, headers);
+ if (mi) {
+ mi->size = size;
+ cns->high = n;
+ camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
+ }
+ } else {
+ camel_folder_summary_info_free(s, mi);
+ }
+ }
+
+ if (cns->priv->uid) {
+ g_free(cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+
+ camel_header_raw_clear(&headers);
+ }
+
+ camel_operation_end(NULL);
+
+ return ret;
+}
+
+/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
+static int
+add_range_head(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex)
+{
+ CamelFolderSummary *s;
+ int i, ret = -1;
+ char *line, *msgid;
+ unsigned int n, count, total;
+ CamelMessageInfo *mi;
+ CamelMimeParser *mp;
+
+ s = (CamelFolderSummary *)cns;
+
+ mp = camel_mime_parser_new();
+
+ camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host);
+
+ count = 0;
+ total = high-low+1;
+ for (i=low;i<high+1;i++) {
+ camel_operation_progress(NULL, (count * 100) / total);
+ count++;
+ ret = camel_nntp_raw_command_auth(store, ex, &line, "head %u", i);
+ /* unknown article, ignore */
+ if (ret == 423)
+ continue;
+ else if (ret == -1)
+ goto ioerror;
+ else if (ret != 221) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unexpected server response from head: %s"), line);
+ goto ioerror;
+ }
+ line += 3;
+ n = strtoul(line, &line, 10);
+ if (n != i)
+ g_warning("retrieved message '%d' when i expected '%d'?\n", n, i);
+
+ /* FIXME: use camel-mime-utils.c function for parsing msgid? */
+ if ((msgid = strchr(line, '<')) && (line = strchr(msgid+1, '>'))){
+ line[1] = 0;
+ cns->priv->uid = g_strdup_printf("%u,%s\n", n, msgid);
+ mi = camel_folder_summary_uid(s, cns->priv->uid);
+ if (mi == NULL) {
+ if (camel_mime_parser_init_with_stream(mp, (CamelStream *)store->stream) == -1)
+ goto error;
+ mi = camel_folder_summary_add_from_parser(s, mp);
+ while (camel_mime_parser_step(mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_EOF)
+ ;
+ if (mi == NULL) {
+ goto error;
+ }
+ cns->high = i;
+ camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
+ } else {
+ /* already have, ignore */
+ camel_folder_summary_info_free(s, mi);
+ }
+ if (cns->priv->uid) {
+ g_free(cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+ }
+ }
+
+ ret = 0;
+error:
+
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Use cancel"));
+ else
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Operation failed: %s"), strerror(errno));
+ }
+ioerror:
+
+ if (cns->priv->uid) {
+ g_free(cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+ camel_object_unref((CamelObject *)mp);
+
+ camel_operation_end(NULL);
+
+ return ret;
+}
+
+/* Assumes we have the stream */
+/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
+int
+camel_nntp_summary_check(CamelNNTPSummary *cns, CamelNNTPStore *store, char *line, CamelFolderChangeInfo *changes, CamelException *ex)
+{
+ CamelFolderSummary *s;
+ int ret = 0, i;
+ unsigned int n, f, l;
+ int count;
+ char *folder = NULL;
+ CamelNNTPStoreInfo *si;
+
+ s = (CamelFolderSummary *)cns;
+
+ line +=3;
+ n = strtoul(line, &line, 10);
+ f = strtoul(line, &line, 10);
+ l = strtoul(line, &line, 10);
+ if (line[0] == ' ') {
+ char *tmp;
+
+ folder = line+1;
+ tmp = strchr(folder, ' ');
+ if (tmp)
+ *tmp = 0;
+ tmp = g_alloca(strlen(folder)+1);
+ strcpy(tmp, folder);
+ folder = tmp;
+ }
+
+ if (cns->low == f && cns->high == l) {
+ dd(printf("nntp_summary: no work to do!\n"));
+ goto update;
+ }
+
+ /* Need to work out what to do with our messages */
+
+ /* Check for messages no longer on the server */
+ if (cns->low != f) {
+ count = camel_folder_summary_count(s);
+ for (i = 0; i < count; i++) {
+ CamelMessageInfo *mi = camel_folder_summary_index(s, i);
+
+ if (mi) {
+ const char *uid = camel_message_info_uid(mi);
+ const char *msgid;
+
+ n = strtoul(uid, NULL, 10);
+ if (n < f || n > l) {
+ dd(printf("nntp_summary: %u is lower/higher than lowest/highest article, removed\n", n));
+ /* Since we use a global cache this could prematurely remove
+ a cached message that might be in another folder - not that important as
+ it is a true cache */
+ msgid = strchr(uid, ',');
+ if (msgid)
+ camel_data_cache_remove(store->cache, "cache", msgid+1, NULL);
+ camel_folder_change_info_remove_uid(changes, uid);
+ camel_folder_summary_remove(s, mi);
+ count--;
+ i--;
+ }
+
+ camel_folder_summary_info_free(s, mi);
+ }
+ }
+ cns->low = f;
+ }
+
+ if (cns->high < l) {
+ if (cns->high < f)
+ cns->high = f-1;
+
+ if (store->xover) {
+ ret = add_range_xover(cns, store, l, cns->high+1, changes, ex);
+ } else {
+ ret = add_range_head(cns, store, l, cns->high+1, changes, ex);
+ }
+ }
+
+ /* TODO: not from here */
+ camel_folder_summary_touch(s);
+ camel_folder_summary_save(s);
+update:
+ /* update store summary if we have it */
+ if (folder
+ && (si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, folder))) {
+ int unread = 0;
+
+ count = camel_folder_summary_count(s);
+ for (i = 0; i < count; i++) {
+ CamelMessageInfo *mi = camel_folder_summary_index(s, i);
+
+ if (mi) {
+ if ((mi->flags & CAMEL_MESSAGE_SEEN) == 0)
+ unread++;
+ camel_folder_summary_info_free(s, mi);
+ }
+ }
+
+ if (si->info.unread != unread
+ || si->info.total != count
+ || si->first != f
+ || si->last != l) {
+ si->info.unread = unread;
+ si->info.total = count;
+ si->first = f;
+ si->last = l;
+ camel_store_summary_touch((CamelStoreSummary *)store->summary);
+ camel_store_summary_save((CamelStoreSummary *)store->summary);
+ }
+ camel_store_summary_info_free ((CamelStoreSummary *)store->summary, (CamelStoreInfo *)si);
+ } else {
+ if (folder)
+ g_warning("Group '%s' not present in summary", folder);
+ else
+ g_warning("Missing group from group response");
+ }
+
+ return ret;
+}
diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c
new file mode 100644
index 0000000000..96db912efc
--- /dev/null
+++ b/camel/providers/pop3/camel-pop3-store.c
@@ -0,0 +1,682 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-pop3-store.c : class for a pop3 store */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "camel-operation.h"
+
+#include "camel-pop3-store.h"
+#include "camel-pop3-folder.h"
+#include "camel-stream-buffer.h"
+#include "camel-session.h"
+#include "camel-exception.h"
+#include "camel-url.h"
+#include "e-util/md5-utils.h"
+#include "camel-pop3-engine.h"
+#include "camel-sasl.h"
+#include "camel-data-cache.h"
+#include "camel-tcp-stream.h"
+#include "camel-tcp-stream-raw.h"
+#ifdef HAVE_SSL
+#include "camel-tcp-stream-ssl.h"
+#endif
+
+/* Specified in RFC 1939 */
+#define POP3_PORT 110
+
+static CamelStoreClass *parent_class = NULL;
+
+static void finalize (CamelObject *object);
+
+static gboolean pop3_connect (CamelService *service, CamelException *ex);
+static gboolean pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex);
+static GList *query_auth_types (CamelService *service, CamelException *ex);
+
+static CamelFolder *get_folder (CamelStore *store, const char *folder_name,
+ guint32 flags, CamelException *ex);
+
+static CamelFolder *get_trash (CamelStore *store, CamelException *ex);
+
+static void
+camel_pop3_store_class_init (CamelPOP3StoreClass *camel_pop3_store_class)
+{
+ CamelServiceClass *camel_service_class =
+ CAMEL_SERVICE_CLASS (camel_pop3_store_class);
+ CamelStoreClass *camel_store_class =
+ CAMEL_STORE_CLASS (camel_pop3_store_class);
+
+ parent_class = CAMEL_STORE_CLASS (camel_type_get_global_classfuncs (camel_store_get_type ()));
+
+ /* virtual method overload */
+ camel_service_class->query_auth_types = query_auth_types;
+ camel_service_class->connect = pop3_connect;
+ camel_service_class->disconnect = pop3_disconnect;
+
+ camel_store_class->get_folder = get_folder;
+ camel_store_class->get_trash = get_trash;
+}
+
+
+
+static void
+camel_pop3_store_init (gpointer object, gpointer klass)
+{
+ ;
+}
+
+CamelType
+camel_pop3_store_get_type (void)
+{
+ static CamelType camel_pop3_store_type = CAMEL_INVALID_TYPE;
+
+ if (!camel_pop3_store_type) {
+ camel_pop3_store_type = camel_type_register (CAMEL_STORE_TYPE,
+ "CamelPOP3Store",
+ sizeof (CamelPOP3Store),
+ sizeof (CamelPOP3StoreClass),
+ (CamelObjectClassInitFunc) camel_pop3_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_pop3_store_init,
+ finalize);
+ }
+
+ return camel_pop3_store_type;
+}
+
+static void
+finalize (CamelObject *object)
+{
+ CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (object);
+
+ /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
+ /* SIGH */
+
+ camel_service_disconnect((CamelService *)pop3_store, TRUE, NULL);
+
+ if (pop3_store->engine)
+ camel_object_unref((CamelObject *)pop3_store->engine);
+ if (pop3_store->cache)
+ camel_object_unref((CamelObject *)pop3_store->cache);
+}
+
+enum {
+ USE_SSL_NEVER,
+ USE_SSL_ALWAYS,
+ USE_SSL_WHEN_POSSIBLE
+};
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+static gboolean
+connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex)
+{
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+ CamelStream *tcp_stream;
+ CamelPOP3Command *pc;
+ guint32 flags = 0;
+ int clean_quit;
+ int ret;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
+ const char *port = NULL;
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else {
+ serv = "pop3";
+ port = "110";
+ }
+
+ if (ssl_mode != USE_SSL_NEVER) {
+#ifdef HAVE_SSL
+ if (try_starttls) {
+ tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
+ } else {
+ if (service->url->port == 0) {
+ serv = "pop3s";
+ port = "995";
+ }
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+ }
+#else
+ if (!try_starttls && service->url->port == 0) {
+ serv = "pop3s";
+ port = "995";
+ }
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
+ _("SSL unavailable"));
+
+ return FALSE;
+#endif /* HAVE_SSL */
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
+ camel_exception_clear(ex);
+ ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
+ }
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ return FALSE;
+ }
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to POP server %s (port %s): %s"),
+ service->url->host, serv, g_strerror (errno));
+
+ camel_object_unref (tcp_stream);
+
+ return FALSE;
+ }
+
+ /* parent class connect initialization */
+ if (CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex) == FALSE) {
+ camel_object_unref (tcp_stream);
+ return FALSE;
+ }
+
+ if (camel_url_get_param (service->url, "disable_extensions"))
+ flags |= CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS;
+
+ if (!(store->engine = camel_pop3_engine_new (tcp_stream, flags))) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to read a valid greeting from POP server %s (port %s)"),
+ service->url->host, serv);
+ return FALSE;
+ }
+
+#ifdef HAVE_SSL
+ if (store->engine) {
+ if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ if (store->engine->capa & CAMEL_POP3_CAP_STLS)
+ goto starttls;
+ } else if (ssl_mode == USE_SSL_ALWAYS) {
+ if (try_starttls) {
+ if (store->engine->capa & CAMEL_POP3_CAP_STLS) {
+ /* attempt to toggle STARTTLS mode */
+ goto starttls;
+ } else {
+ /* server doesn't support STARTTLS, abort */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to POP server %s in secure mode: %s"),
+ service->url->host, _("SSL/TLS extension not supported."));
+ /* we have the possibility of quitting cleanly here */
+ clean_quit = TRUE;
+ goto stls_exception;
+ }
+ }
+ }
+ }
+#endif /* HAVE_SSL */
+
+ camel_object_unref (tcp_stream);
+
+ return store->engine != NULL;
+
+#ifdef HAVE_SSL
+ starttls:
+ /* as soon as we send a STLS command, all hope is lost of a clean QUIT if problems arise */
+ clean_quit = FALSE;
+
+ pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "STLS\r\n");
+ while (camel_pop3_engine_iterate (store->engine, NULL) > 0)
+ ;
+
+ ret = pc->state == CAMEL_POP3_COMMAND_OK;
+ camel_pop3_engine_command_free (store->engine, pc);
+
+ if (ret == FALSE) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to POP server %s in secure mode: %s"),
+ service->url->host, store->engine->line);
+ goto stls_exception;
+ }
+
+ /* Okay, now toggle SSL/TLS mode */
+ ret = camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream));
+
+ camel_object_unref (CAMEL_OBJECT (tcp_stream));
+
+ if (ret == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to POP server %s in secure mode: %s"),
+ service->url->host, _("SSL negotiations failed"));
+ goto stls_exception;
+ }
+
+ /* rfc2595, section 4 states that after a successful STLS
+ command, the client MUST discard prior CAPA responses */
+ camel_pop3_engine_reget_capabilities (store->engine);
+
+ return TRUE;
+
+ stls_exception:
+ if (clean_quit) {
+ /* try to disconnect cleanly */
+ pc = camel_pop3_engine_command_new (store->engine, 0, NULL, NULL, "QUIT\r\n");
+ while (camel_pop3_engine_iterate (store->engine, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free (store->engine, pc);
+ }
+
+ camel_object_unref (CAMEL_OBJECT (store->engine));
+ camel_object_unref (CAMEL_OBJECT (tcp_stream));
+ store->engine = NULL;
+
+ return FALSE;
+#endif /* HAVE_SSL */
+}
+
+static struct {
+ char *value;
+ int mode;
+} ssl_options[] = {
+ { "", USE_SSL_ALWAYS },
+ { "always", USE_SSL_ALWAYS },
+ { "when-possible", USE_SSL_WHEN_POSSIBLE },
+ { "never", USE_SSL_NEVER },
+ { NULL, USE_SSL_NEVER },
+};
+
+static gboolean
+connect_to_server_wrapper (CamelService *service, CamelException *ex)
+{
+#ifdef HAVE_SSL
+ const char *use_ssl;
+ int i, ssl_mode;
+
+ use_ssl = camel_url_get_param (service->url, "use_ssl");
+ if (use_ssl) {
+ for (i = 0; ssl_options[i].value; i++)
+ if (!strcmp (ssl_options[i].value, use_ssl))
+ break;
+ ssl_mode = ssl_options[i].mode;
+ } else
+ ssl_mode = USE_SSL_NEVER;
+
+ if (ssl_mode == USE_SSL_ALWAYS) {
+ /* First try the ssl port */
+ if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
+ if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
+ /* The ssl port seems to be unavailable, lets try STARTTLS */
+ camel_exception_clear (ex);
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ /* If the server supports STARTTLS, use it */
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ /* User doesn't care about SSL */
+ return connect_to_server (service, ssl_mode, FALSE, ex);
+ }
+#else
+ return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
+#endif
+}
+
+extern CamelServiceAuthType camel_pop3_password_authtype;
+extern CamelServiceAuthType camel_pop3_apop_authtype;
+
+static GList *
+query_auth_types (CamelService *service, CamelException *ex)
+{
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+ GList *types = NULL;
+
+ types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
+ if (camel_exception_is_set (ex))
+ return NULL;
+
+ if (connect_to_server_wrapper (service, NULL)) {
+ types = g_list_concat(types, g_list_copy(store->engine->auth));
+ pop3_disconnect (service, TRUE, NULL);
+ } else {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to POP server %s"),
+ service->url->host);
+ }
+
+ return types;
+}
+
+/**
+ * camel_pop3_store_expunge:
+ * @store: the store
+ * @ex: a CamelException
+ *
+ * Expunge messages from the store. This will result in the connection
+ * being closed, which may cause later commands to fail if they can't
+ * reconnect.
+ **/
+void
+camel_pop3_store_expunge (CamelPOP3Store *store, CamelException *ex)
+{
+ CamelPOP3Command *pc;
+
+ pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
+ while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free(store->engine, pc);
+
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, ex);
+}
+
+static int
+try_sasl(CamelPOP3Store *store, const char *mech, CamelException *ex)
+{
+ CamelPOP3Stream *stream = store->engine->stream;
+ unsigned char *line, *resp;
+ CamelSasl *sasl;
+ unsigned int len;
+ int ret;
+
+ sasl = camel_sasl_new("pop3", mech, (CamelService *)store);
+ if (sasl == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
+ _("Unable to connect to POP server %s: "
+ "No support for requested authentication mechanism."),
+ CAMEL_SERVICE (store)->url->host);
+ return -1;
+ }
+
+ if (camel_stream_printf((CamelStream *)stream, "AUTH %s\r\n", mech) == -1)
+ goto ioerror;
+
+ while (1) {
+ if (camel_pop3_stream_line(stream, &line, &len) == -1)
+ goto ioerror;
+ if (strncmp(line, "+OK", 3) == 0)
+ break;
+ if (strncmp(line, "-ERR", 4) == 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("SASL `%s' Login failed for POP server %s: %s"),
+ mech, CAMEL_SERVICE (store)->url->host, line);
+ goto done;
+ }
+ /* If we dont get continuation, or the sasl object's run out of work, or we dont get a challenge,
+ its a protocol error, so fail, and try reset the server */
+ if (strncmp(line, "+ ", 2) != 0
+ || camel_sasl_authenticated(sasl)
+ || (resp = camel_sasl_challenge_base64(sasl, line+2, ex)) == NULL) {
+ camel_stream_printf((CamelStream *)stream, "*\r\n");
+ camel_pop3_stream_line(stream, &line, &len);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Cannot login to POP server %s: SASL Protocol error"),
+ CAMEL_SERVICE (store)->url->host);
+ goto done;
+ }
+
+ ret = camel_stream_printf((CamelStream *)stream, "%s\r\n", resp);
+ g_free(resp);
+ if (ret == -1)
+ goto ioerror;
+
+ }
+ camel_object_unref((CamelObject *)sasl);
+ return 0;
+
+ ioerror:
+ if (errno == EINTR) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ } else {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to authenticate on POP server %s: %s"),
+ CAMEL_SERVICE (store)->url->host, g_strerror (errno));
+ }
+ done:
+ camel_object_unref((CamelObject *)sasl);
+ return -1;
+}
+
+static int
+pop3_try_authenticate (CamelService *service, gboolean reprompt, const char *errmsg, CamelException *ex)
+{
+ CamelPOP3Store *store = (CamelPOP3Store *)service;
+ CamelPOP3Command *pcu = NULL, *pcp = NULL;
+ int status;
+
+ /* override, testing only */
+ /*printf("Forcing authmech to 'login'\n");
+ service->url->authmech = g_strdup("LOGIN");*/
+
+ if (!service->url->passwd) {
+ char *prompt;
+ guint32 flags = CAMEL_SESSION_PASSWORD_SECRET;
+
+ if (reprompt)
+ flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
+
+ prompt = g_strdup_printf (_("%sPlease enter the POP password for %s on host %s"),
+ errmsg ? errmsg : "",
+ service->url->user,
+ service->url->host);
+ service->url->passwd = camel_session_get_password (camel_service_get_session (service), service, NULL,
+ prompt, "password", flags, ex);
+ g_free (prompt);
+ if (!service->url->passwd)
+ return FALSE;
+ }
+
+ if (!service->url->authmech) {
+ /* pop engine will take care of pipelining ability */
+ pcu = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "USER %s\r\n", service->url->user);
+ pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "PASS %s\r\n", service->url->passwd);
+ } else if (strcmp(service->url->authmech, "+APOP") == 0 && store->engine->apop) {
+ char *secret, md5asc[33], *d;
+ unsigned char md5sum[16], *s;
+
+ secret = g_alloca(strlen(store->engine->apop)+strlen(service->url->passwd)+1);
+ sprintf(secret, "%s%s", store->engine->apop, service->url->passwd);
+ md5_get_digest(secret, strlen (secret), md5sum);
+
+ for (s = md5sum, d = md5asc; d < md5asc + 32; s++, d += 2)
+ sprintf (d, "%.2x", *s);
+
+ pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "APOP %s %s\r\n",
+ service->url->user, md5asc);
+ } else {
+ CamelServiceAuthType *auth;
+ GList *l;
+
+ l = store->engine->auth;
+ while (l) {
+ auth = l->data;
+ if (strcmp(auth->authproto, service->url->authmech) == 0)
+ return try_sasl(store, service->url->authmech, ex) == -1;
+ l = l->next;
+ }
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID,
+ _("Unable to connect to POP server %s: "
+ "No support for requested authentication mechanism."),
+ CAMEL_SERVICE (store)->url->host);
+ return FALSE;
+ }
+
+ while ((status = camel_pop3_engine_iterate(store->engine, pcp)) > 0)
+ ;
+
+ if (status == -1) {
+ if (errno == EINTR) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ } else {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unable to connect to POP server %s.\n"
+ "Error sending password: %s"),
+ CAMEL_SERVICE (store)->url->host,
+ errno ? g_strerror (errno) : _("Unknown error"));
+ }
+ } else if (pcu && pcu->state != CAMEL_POP3_COMMAND_OK) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Unable to connect to POP server %s.\n"
+ "Error sending username: %s"),
+ CAMEL_SERVICE (store)->url->host,
+ store->engine->line ? (char *)store->engine->line : _("Unknown error"));
+ } else if (pcp->state != CAMEL_POP3_COMMAND_OK)
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Unable to connect to POP server %s.\n"
+ "Error sending password: %s"),
+ CAMEL_SERVICE (store)->url->host,
+ store->engine->line ? (char *)store->engine->line : _("Unknown error"));
+
+ camel_pop3_engine_command_free(store->engine, pcp);
+
+ if (pcu)
+ camel_pop3_engine_command_free(store->engine, pcu);
+
+ return status;
+}
+
+static gboolean
+pop3_connect (CamelService *service, CamelException *ex)
+{
+ CamelPOP3Store *store = (CamelPOP3Store *)service;
+ gboolean reprompt = FALSE;
+ CamelSession *session;
+ char *errbuf = NULL;
+ int status;
+
+ session = camel_service_get_session (service);
+
+ if (store->cache == NULL) {
+ char *root;
+
+ root = camel_session_get_storage_path (session, service, ex);
+ if (root) {
+ store->cache = camel_data_cache_new(root, 0, ex);
+ g_free(root);
+ if (store->cache) {
+ /* Default cache expiry - 1 week or not visited in a day */
+ camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
+ camel_data_cache_set_expire_access(store->cache, 60*60*24);
+ }
+ }
+ }
+
+ if (!connect_to_server_wrapper (service, ex))
+ return FALSE;
+
+ while (1) {
+ status = pop3_try_authenticate (service, reprompt, errbuf, ex);
+ g_free (errbuf);
+ errbuf = NULL;
+
+ /* we only re-prompt if we failed to authenticate, any other error and we just abort */
+ if (status == 0 && camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE) {
+ errbuf = g_strdup_printf ("%s\n\n", camel_exception_get_description (ex));
+ g_free (service->url->passwd);
+ service->url->passwd = NULL;
+ reprompt = TRUE;
+ camel_exception_clear (ex);
+ } else
+ break;
+ }
+
+ g_free (errbuf);
+
+ if (status == -1 || camel_exception_is_set(ex)) {
+ camel_service_disconnect(service, TRUE, ex);
+ return FALSE;
+ }
+
+ /* Now that we are in the TRANSACTION state, try regetting the capabilities */
+ store->engine->state = CAMEL_POP3_ENGINE_TRANSACTION;
+ camel_pop3_engine_reget_capabilities (store->engine);
+
+ return TRUE;
+}
+
+static gboolean
+pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelPOP3Store *store = CAMEL_POP3_STORE (service);
+
+ if (clean) {
+ CamelPOP3Command *pc;
+
+ pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
+ while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
+ ;
+ camel_pop3_engine_command_free(store->engine, pc);
+ }
+
+ if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
+ return FALSE;
+
+ camel_object_unref((CamelObject *)store->engine);
+ store->engine = NULL;
+
+ return TRUE;
+}
+
+static CamelFolder *
+get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ if (g_ascii_strcasecmp (folder_name, "inbox") != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
+ _("No such folder `%s'."), folder_name);
+ return NULL;
+ }
+ return camel_pop3_folder_new (store, ex);
+}
+
+static CamelFolder *
+get_trash (CamelStore *store, CamelException *ex)
+{
+ /* no-op */
+ return NULL;
+}
diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c
new file mode 100644
index 0000000000..ea8ca26e49
--- /dev/null
+++ b/camel/providers/smtp/camel-smtp-transport.c
@@ -0,0 +1,1413 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-smtp-transport.c : class for a smtp transport */
+
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#undef MIN
+#undef MAX
+#include "camel-mime-filter-crlf.h"
+#include "camel-stream-filter.h"
+#include "camel-smtp-transport.h"
+#include "camel-mime-message.h"
+#include "camel-multipart.h"
+#include "camel-mime-part.h"
+#include "camel-operation.h"
+#include "camel-stream-buffer.h"
+#include "camel-tcp-stream.h"
+#include "camel-tcp-stream-raw.h"
+#ifdef HAVE_SSL
+#include "camel-tcp-stream-ssl.h"
+#endif
+#include "camel-session.h"
+#include "camel-exception.h"
+#include "camel-sasl.h"
+
+
+extern int camel_verbose_debug;
+#define d(x) (camel_verbose_debug ? (x) : 0)
+
+/* Specified in RFC 821 */
+#define SMTP_PORT 25
+
+/* camel smtp transport class prototypes */
+static gboolean smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
+ CamelAddress *from, CamelAddress *recipients, CamelException *ex);
+
+/* support prototypes */
+static void smtp_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+static gboolean smtp_connect (CamelService *service, CamelException *ex);
+static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
+static GHashTable *esmtp_get_authtypes (const unsigned char *buffer);
+static GList *query_auth_types (CamelService *service, CamelException *ex);
+static char *get_name (CamelService *service, gboolean brief);
+
+static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
+static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
+static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
+ gboolean has_8bit_parts, CamelException *ex);
+static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
+static gboolean smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex);
+static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
+static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
+
+static void smtp_set_exception (CamelSmtpTransport *transport, gboolean disconnect, const char *respbuf,
+ const char *message, CamelException *ex);
+
+/* private data members */
+static CamelTransportClass *parent_class = NULL;
+
+static void
+camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
+{
+ CamelTransportClass *camel_transport_class =
+ CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
+ CamelServiceClass *camel_service_class =
+ CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
+
+ parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
+
+ /* virtual method overload */
+ camel_service_class->construct = smtp_construct;
+ camel_service_class->connect = smtp_connect;
+ camel_service_class->disconnect = smtp_disconnect;
+ camel_service_class->query_auth_types = query_auth_types;
+ camel_service_class->get_name = get_name;
+
+ camel_transport_class->send_to = smtp_send_to;
+}
+
+static void
+camel_smtp_transport_init (gpointer object)
+{
+ CamelSmtpTransport *smtp = CAMEL_SMTP_TRANSPORT (object);
+
+ smtp->flags = 0;
+ smtp->connected = FALSE;
+}
+
+CamelType
+camel_smtp_transport_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (CAMEL_TRANSPORT_TYPE,
+ "CamelSmtpTransport",
+ sizeof (CamelSmtpTransport),
+ sizeof (CamelSmtpTransportClass),
+ (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_smtp_transport_init,
+ NULL);
+ }
+
+ return type;
+}
+
+static void
+smtp_construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex)
+{
+ CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (service);
+ const char *use_ssl;
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+
+ if ((use_ssl = camel_url_get_param (url, "use_ssl"))) {
+ /* Note: previous versions would use "" to toggle use_ssl to 'on' */
+ if (!*use_ssl || !strcmp (use_ssl, "always"))
+ smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS;
+ else if (!strcmp (use_ssl, "when-possible"))
+ smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE;
+ }
+}
+
+static const char *
+smtp_error_string (int error)
+{
+ /* SMTP error codes grabbed from rfc821 */
+ switch (error) {
+ case 0:
+ /* looks like a read problem, check errno */
+ if (errno)
+ return g_strerror (errno);
+ else
+ return _("Unknown");
+ case 500:
+ return _("Syntax error, command unrecognized");
+ case 501:
+ return _("Syntax error in parameters or arguments");
+ case 502:
+ return _("Command not implemented");
+ case 504:
+ return _("Command parameter not implemented");
+ case 211:
+ return _("System status, or system help reply");
+ case 214:
+ return _("Help message");
+ case 220:
+ return _("Service ready");
+ case 221:
+ return _("Service closing transmission channel");
+ case 421:
+ return _("Service not available, closing transmission channel");
+ case 250:
+ return _("Requested mail action okay, completed");
+ case 251:
+ return _("User not local; will forward to <forward-path>");
+ case 450:
+ return _("Requested mail action not taken: mailbox unavailable");
+ case 550:
+ return _("Requested action not taken: mailbox unavailable");
+ case 451:
+ return _("Requested action aborted: error in processing");
+ case 551:
+ return _("User not local; please try <forward-path>");
+ case 452:
+ return _("Requested action not taken: insufficient system storage");
+ case 552:
+ return _("Requested mail action aborted: exceeded storage allocation");
+ case 553:
+ return _("Requested action not taken: mailbox name not allowed");
+ case 354:
+ return _("Start mail input; end with <CRLF>.<CRLF>");
+ case 554:
+ return _("Transaction failed");
+
+ /* AUTH error codes: */
+ case 432:
+ return _("A password transition is needed");
+ case 534:
+ return _("Authentication mechanism is too weak");
+ case 538:
+ return _("Encryption required for requested authentication mechanism");
+ case 454:
+ return _("Temporary authentication failure");
+ case 530:
+ return _("Authentication required");
+
+ default:
+ return _("Unknown");
+ }
+}
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+static gboolean
+connect_to_server (CamelService *service, int try_starttls, CamelException *ex)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelStream *tcp_stream;
+ char *respbuf = NULL;
+ int ret;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
+ const char *port = NULL;
+
+ if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
+ return FALSE;
+
+ /* set some smtp transport defaults */
+ transport->flags &= CAMEL_SMTP_TRANSPORT_USE_SSL; /* reset all but ssl flags */
+ transport->authtypes = NULL;
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else {
+ serv = "smtp";
+ port = "25";
+ }
+
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) {
+#ifdef HAVE_SSL
+ if (try_starttls) {
+ tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
+ } else {
+ if (service->url->port == 0) {
+ serv = "smtps";
+ port = "465";
+ }
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+ }
+#else
+ if (!try_starttls && service->url->port == 0) {
+ serv = "smtps";
+ port = "465";
+ }
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
+ _("SSL unavailable"));
+
+ return FALSE;
+#endif /* HAVE_SSL */
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ /* fallback to numerical port if the system is misconfigured */
+ if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
+ camel_exception_clear(ex);
+ ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
+ }
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ return FALSE;
+ }
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
+ g_strerror (errno));
+
+ camel_object_unref (tcp_stream);
+
+ return FALSE;
+ }
+
+ transport->connected = TRUE;
+
+ /* get the localaddr - needed later by smtp_helo */
+ transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream), &transport->localaddrlen);
+
+ transport->ostream = tcp_stream;
+ transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
+
+ /* Read the greeting, note whether the server is ESMTP or not. */
+ do {
+ /* Check for "220" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+ if (!respbuf || strncmp (respbuf, "220", 3)) {
+ smtp_set_exception (transport, FALSE, respbuf, _("Welcome response error"), ex);
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
+ g_free (respbuf);
+
+ /* Try sending EHLO */
+ transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
+ if (!smtp_helo (transport, ex)) {
+ if (!transport->connected)
+ return FALSE;
+
+ /* Fall back to HELO */
+ camel_exception_clear (ex);
+ transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
+ if (!smtp_helo (transport, ex) && !transport->connected)
+ return FALSE;
+ }
+
+ /* clear any EHLO/HELO exception and assume that any SMTP errors encountered were non-fatal */
+ camel_exception_clear (ex);
+
+#ifdef HAVE_SSL
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) {
+ /* try_starttls is always TRUE here */
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)
+ goto starttls;
+ } else if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS) {
+ if (try_starttls) {
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS) {
+ goto starttls;
+ } else {
+ /* server doesn't support STARTTLS, abort */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to SMTP server %s in secure mode: %s"),
+ service->url->host, _("server does not appear to support SSL"));
+ goto exception_cleanup;
+ }
+ }
+ }
+#endif /* HAVE_SSL */
+
+ return TRUE;
+
+#ifdef HAVE_SSL
+ starttls:
+ d(fprintf (stderr, "sending : STARTTLS\r\n"));
+ if (camel_stream_write (tcp_stream, "STARTTLS\r\n", 10) == -1) {
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("STARTTLS command failed: %s"),
+ g_strerror (errno));
+ goto exception_cleanup;
+ }
+
+ respbuf = NULL;
+
+ do {
+ /* Check for "220 Ready for TLS" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "220", 3)) {
+ smtp_set_exception (transport, FALSE, respbuf, _("STARTTLS command failed"), ex);
+ g_free (respbuf);
+ goto exception_cleanup;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
+
+ /* Okay, now toggle SSL/TLS mode */
+ if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to SMTP server %s in secure mode: %s"),
+ service->url->host, g_strerror (errno));
+ goto exception_cleanup;
+ }
+
+ /* We are supposed to re-EHLO after a successful STARTTLS to
+ re-fetch any supported extensions. */
+ if (!smtp_helo (transport, ex) && !transport->connected)
+ return FALSE;
+
+ return TRUE;
+
+ exception_cleanup:
+
+ camel_object_unref (transport->istream);
+ transport->istream = NULL;
+ camel_object_unref (transport->ostream);
+ transport->ostream = NULL;
+
+ transport->connected = FALSE;
+
+ return FALSE;
+#endif /* HAVE_SSL */
+}
+
+static gboolean
+connect_to_server_wrapper (CamelService *service, CamelException *ex)
+{
+#ifdef HAVE_SSL
+ CamelSmtpTransport *transport = (CamelSmtpTransport *) service;
+
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS) {
+ /* First try connecting to the SSL port */
+ if (!connect_to_server (service, FALSE, ex)) {
+ if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
+ /* Seems the SSL port is unavailable, lets try STARTTLS */
+ camel_exception_clear (ex);
+ return connect_to_server (service, TRUE, ex);
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) {
+ /* If the server supports STARTTLS, use it */
+ return connect_to_server (service, TRUE, ex);
+ } else {
+ /* User doesn't care about SSL */
+ return connect_to_server (service, FALSE, ex);
+ }
+#else
+ return connect_to_server (service, FALSE, ex);
+#endif
+}
+
+static gboolean
+smtp_connect (CamelService *service, CamelException *ex)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ gboolean has_authtypes;
+
+ /* We (probably) need to check popb4smtp before we connect ... */
+ if (service->url->authmech && !strcmp (service->url->authmech, "POPB4SMTP")) {
+ int truth;
+ GByteArray *chal;
+ CamelSasl *sasl;
+
+ sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
+ chal = camel_sasl_challenge (sasl, NULL, ex);
+ truth = camel_sasl_authenticated (sasl);
+ if (chal)
+ g_byte_array_free (chal, TRUE);
+ camel_object_unref (sasl);
+
+ if (!truth)
+ return FALSE;
+
+ return connect_to_server_wrapper (service, ex);
+ }
+
+ if (!connect_to_server_wrapper (service, ex))
+ return FALSE;
+
+ /* check to see if AUTH is required, if so...then AUTH ourselves */
+ has_authtypes = transport->authtypes ? g_hash_table_size (transport->authtypes) > 0 : FALSE;
+ if (service->url->authmech && (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) && has_authtypes) {
+ CamelSession *session = camel_service_get_session (service);
+ CamelServiceAuthType *authtype;
+ gboolean authenticated = FALSE;
+ char *errbuf = NULL;
+
+ if (!g_hash_table_lookup (transport->authtypes, service->url->authmech)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("SMTP server %s does not support requested "
+ "authentication type %s."),
+ service->url->host, service->url->authmech);
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+
+ authtype = camel_sasl_authtype (service->url->authmech);
+ if (!authtype) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("No support for authentication type %s"),
+ service->url->authmech);
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+
+ if (!authtype->need_password) {
+ /* authentication mechanism doesn't need a password,
+ so if it fails there's nothing we can do */
+ authenticated = smtp_auth (transport, authtype->authproto, ex);
+ if (!authenticated) {
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+ }
+
+ /* keep trying to login until either we succeed or the user cancels */
+ while (!authenticated) {
+ if (errbuf) {
+ /* We need to un-cache the password before prompting again */
+ camel_session_forget_password (session, service, NULL, "password", NULL);
+ g_free (service->url->passwd);
+ service->url->passwd = NULL;
+ }
+
+ if (!service->url->passwd) {
+ char *prompt;
+
+ prompt = g_strdup_printf (_("%sPlease enter the SMTP password for %s on host %s"),
+ errbuf ? errbuf : "", service->url->user,
+ service->url->host);
+
+ service->url->passwd = camel_session_get_password (session, service, NULL,
+ prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
+
+ g_free (prompt);
+ g_free (errbuf);
+ errbuf = NULL;
+
+ if (!service->url->passwd) {
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+ }
+
+ authenticated = smtp_auth (transport, authtype->authproto, ex);
+ if (!authenticated) {
+ errbuf = g_strdup_printf (_("Unable to authenticate "
+ "to SMTP server.\n%s\n\n"),
+ camel_exception_get_description (ex));
+ camel_exception_clear (ex);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+authtypes_free (gpointer key, gpointer value, gpointer data)
+{
+ g_free (value);
+}
+
+static gboolean
+smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+
+ /*if (!service->connected)
+ * return TRUE;
+ */
+
+ if (transport->connected && clean) {
+ /* send the QUIT command to the SMTP server */
+ smtp_quit (transport, ex);
+ }
+
+ if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
+ return FALSE;
+
+ if (transport->authtypes) {
+ g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
+ g_hash_table_destroy (transport->authtypes);
+ transport->authtypes = NULL;
+ }
+
+ if (transport->istream) {
+ camel_object_unref (transport->istream);
+ transport->istream = NULL;
+ }
+
+ if (transport->ostream) {
+ camel_object_unref (transport->ostream);
+ transport->ostream = NULL;
+ }
+
+ g_free(transport->localaddr);
+ transport->localaddr = NULL;
+
+ transport->connected = FALSE;
+
+ return TRUE;
+}
+
+static GHashTable *
+esmtp_get_authtypes (const unsigned char *buffer)
+{
+ const unsigned char *start, *end;
+ GHashTable *table = NULL;
+
+ /* advance to the first token */
+ start = buffer;
+ while (isspace ((int) *start) || *start == '=')
+ start++;
+
+ if (!*start)
+ return NULL;
+
+ table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for ( ; *start; ) {
+ char *type;
+
+ /* advance to the end of the token */
+ end = start;
+ while (*end && !isspace ((int) *end))
+ end++;
+
+ type = g_strndup (start, end - start);
+ g_hash_table_insert (table, type, type);
+
+ /* advance to the next token */
+ start = end;
+ while (isspace ((int) *start))
+ start++;
+ }
+
+ return table;
+}
+
+static GList *
+query_auth_types (CamelService *service, CamelException *ex)
+{
+ CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelServiceAuthType *authtype;
+ GList *types, *t, *next;
+
+ if (!connect_to_server_wrapper (service, ex))
+ return NULL;
+
+ types = g_list_copy (service->provider->authtypes);
+ for (t = types; t; t = next) {
+ authtype = t->data;
+ next = t->next;
+
+ if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
+ types = g_list_remove_link (types, t);
+ g_list_free_1 (t);
+ }
+ }
+
+ smtp_disconnect (service, TRUE, NULL);
+
+ return types;
+}
+
+static char *
+get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup_printf (_("SMTP server %s"), service->url->host);
+ else {
+ return g_strdup_printf (_("SMTP mail delivery via %s"),
+ service->url->host);
+ }
+}
+
+static gboolean
+smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
+ CamelAddress *from, CamelAddress *recipients,
+ CamelException *ex)
+{
+ CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
+ const CamelInternetAddress *cia;
+ gboolean has_8bit_parts;
+ const char *addr;
+ int i, len;
+
+ if (!smtp_transport->connected) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
+ _("Cannot send message: service not connected."));
+ return FALSE;
+ }
+
+ if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot send message: sender address not valid."));
+ return FALSE;
+ }
+
+ camel_operation_start (NULL, _("Sending message"));
+
+ /* find out if the message has 8bit mime parts */
+ has_8bit_parts = camel_mime_message_has_8bit_parts (message);
+
+ /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
+ you'll be sending an 8bit mime message at "MAIL FROM:" time. */
+ if (!smtp_mail (smtp_transport, addr, has_8bit_parts, ex)) {
+ camel_operation_end (NULL);
+ return FALSE;
+ }
+
+ len = camel_address_length (recipients);
+ if (len == 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot send message: no recipients defined."));
+ camel_operation_end (NULL);
+ return FALSE;
+ }
+
+ cia = CAMEL_INTERNET_ADDRESS (recipients);
+ for (i = 0; i < len; i++) {
+ char *enc;
+
+ if (!camel_internet_address_get (cia, i, NULL, &addr)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot send message: one or more invalid recipients"));
+ camel_operation_end (NULL);
+ return FALSE;
+ }
+
+ enc = camel_internet_address_encode_address(NULL, NULL, addr);
+ if (!smtp_rcpt (smtp_transport, enc, ex)) {
+ g_free(enc);
+ camel_operation_end (NULL);
+ return FALSE;
+ }
+ g_free(enc);
+ }
+
+ if (!smtp_data (smtp_transport, message, ex)) {
+ camel_operation_end (NULL);
+ return FALSE;
+ }
+
+ /* reset the service for our next transfer session */
+ if (!smtp_rset (smtp_transport, ex))
+ camel_exception_clear (ex);
+
+ camel_operation_end (NULL);
+
+ return TRUE;
+}
+
+static const char *
+smtp_next_token (const char *buf)
+{
+ const unsigned char *token;
+
+ token = (const unsigned char *) buf;
+ while (*token && !isspace ((int) *token))
+ token++;
+
+ while (*token && isspace ((int) *token))
+ token++;
+
+ return (const char *) token;
+}
+
+#define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
+
+/**
+ * example (rfc2034):
+ * 5.1.1 Mailbox "nosuchuser" does not exist
+ *
+ * The human-readable status code is what we want. Since this text
+ * could possibly be encoded, we must decode it.
+ *
+ * "xtext" is formally defined as follows:
+ *
+ * xtext = *( xchar / hexchar / linear-white-space / comment )
+ *
+ * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
+ * except for "+", "\" and "(".
+ *
+ * "hexchar"s are intended to encode octets that cannot be represented
+ * as plain text, either because they are reserved, or because they are
+ * non-printable. However, any octet value may be represented by a
+ * "hexchar".
+ *
+ * hexchar = ASCII "+" immediately followed by two upper case
+ * hexadecimal digits
+ **/
+static char *
+smtp_decode_status_code (const char *in, size_t len)
+{
+ unsigned char *inptr, *outptr;
+ const unsigned char *inend;
+ char *outbuf;
+
+ outptr = outbuf = g_malloc (len + 1);
+
+ inptr = (unsigned char *) in;
+ inend = inptr + len;
+ while (inptr < inend) {
+ if (*inptr == '+') {
+ if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
+ *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
+ inptr += 3;
+ } else
+ *outptr++ = *inptr++;
+ } else
+ *outptr++ = *inptr++;
+ }
+
+ *outptr = '\0';
+
+ return outbuf;
+}
+
+static void
+smtp_set_exception (CamelSmtpTransport *transport, gboolean disconnect, const char *respbuf, const char *message, CamelException *ex)
+{
+ const char *token, *rbuf = respbuf;
+ char *buffer = NULL;
+ GString *string;
+ int error;
+
+ if (!respbuf || !(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)) {
+ fake_status_code:
+ error = respbuf ? atoi (respbuf) : 0;
+ camel_exception_setv (ex, error == 0 && errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ "%s: %s", message, smtp_error_string (error));
+ } else {
+ string = g_string_new ("");
+ do {
+ token = smtp_next_token (rbuf + 4);
+ if (*token == '\0') {
+ g_free (buffer);
+ g_string_free (string, TRUE);
+ goto fake_status_code;
+ }
+
+ g_string_append (string, token);
+ if (*(rbuf + 3) == '-') {
+ g_free (buffer);
+ buffer = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+ g_string_append_c (string, '\n');
+ } else {
+ g_free (buffer);
+ buffer = NULL;
+ }
+
+ rbuf = buffer;
+ } while (rbuf);
+
+ buffer = smtp_decode_status_code (string->str, string->len);
+ g_string_free (string, TRUE);
+ if (!buffer)
+ goto fake_status_code;
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ "%s: %s", message, buffer);
+
+ g_free (buffer);
+ }
+
+ if (!respbuf) {
+ /* we got disconnected */
+ if (disconnect)
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+ else
+ transport->connected = FALSE;
+ }
+}
+
+static gboolean
+smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
+{
+ /* say hello to the server */
+ char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
+ const char *token, *numeric = NULL;
+
+ /* these are flags that we set, so unset them in case we
+ are being called a second time (ie, after a STARTTLS) */
+ transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
+ CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
+ CAMEL_SMTP_TRANSPORT_STARTTLS);
+
+ if (transport->authtypes) {
+ g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
+ g_hash_table_destroy (transport->authtypes);
+ transport->authtypes = NULL;
+ }
+
+ camel_operation_start_transient (NULL, _("SMTP Greeting"));
+
+ /* force name resolution first, fallback to numerical, we need to know when it falls back */
+ if (camel_getnameinfo(transport->localaddr, transport->localaddrlen, &name, NULL, NI_NAMEREQD, NULL) != 0) {
+ if (camel_getnameinfo(transport->localaddr, transport->localaddrlen, &name, NULL, NI_NUMERICHOST, NULL) != 0)
+ name = g_strdup("localhost.localdomain");
+ else {
+ if (transport->localaddr->sa_family == AF_INET6)
+ numeric = "IPv6:";
+ else
+ numeric = "";
+ }
+ }
+
+ /* hiya server! how are you today? */
+ token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
+ if (numeric)
+ cmdbuf = g_strdup_printf("%s [%s%s]\r\n", token, numeric, name);
+ else
+ cmdbuf = g_strdup_printf("%s %s\r\n", token, name);
+ g_free (name);
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("HELO command failed: %s"), g_strerror (errno));
+ camel_operation_end (NULL);
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "250", 3)) {
+ smtp_set_exception (transport, FALSE, respbuf, _("HELO command failed"), ex);
+ camel_operation_end (NULL);
+ g_free (respbuf);
+
+ return FALSE;
+ }
+
+ token = respbuf + 4;
+
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
+ if (!strncmp (token, "8BITMIME", 8)) {
+ d(fprintf (stderr, "This server supports 8bit MIME\n"));
+ transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
+ } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) {
+ d(fprintf (stderr, "This server supports enhanced status codes\n"));
+ transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
+ } else if (!strncmp (token, "STARTTLS", 8)) {
+ d(fprintf (stderr, "This server supports STARTTLS\n"));
+ transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
+ } else if (!strncmp (token, "AUTH", 4)) {
+ if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
+ /* Don't bother parsing any authtypes if we already have a list.
+ * Some servers will list AUTH twice, once the standard way and
+ * once the way Microsoft Outlook requires them to be:
+ *
+ * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
+ * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
+ *
+ * Since they can come in any order, parse each list that we get
+ * until we parse an authtype list that does not use the AUTH=
+ * format. We want to let the standard way have priority over the
+ * broken way.
+ **/
+
+ if (token[4] == '=')
+ transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
+ else
+ transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
+
+ /* parse for supported AUTH types */
+ token += 5;
+
+ if (transport->authtypes) {
+ g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
+ g_hash_table_destroy (transport->authtypes);
+ }
+
+ transport->authtypes = esmtp_get_authtypes (token);
+ }
+ }
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ camel_operation_end (NULL);
+
+ return TRUE;
+}
+
+static gboolean
+smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
+{
+ char *cmdbuf, *respbuf = NULL, *challenge;
+ gboolean auth_challenge = FALSE;
+ CamelSasl *sasl = NULL;
+
+ camel_operation_start_transient (NULL, _("SMTP Authentication"));
+
+ sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
+ if (!sasl) {
+ camel_operation_end (NULL);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Error creating SASL authentication object."));
+ return FALSE;
+ }
+
+ challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
+ if (challenge) {
+ auth_challenge = TRUE;
+ cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
+ g_free (challenge);
+ } else {
+ cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
+ }
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("AUTH command failed: %s"), g_strerror (errno));
+ goto lose;
+ }
+ g_free (cmdbuf);
+
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ while (!camel_sasl_authenticated (sasl)) {
+ if (!respbuf) {
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("AUTH command failed: %s"), g_strerror (errno));
+ goto lose;
+ }
+
+ /* the server challenge/response should follow a 334 code */
+ if (strncmp (respbuf, "334", 3) != 0) {
+ smtp_set_exception (transport, FALSE, respbuf, _("AUTH command failed"), ex);
+ g_free (respbuf);
+ goto lose;
+ }
+
+ if (FALSE) {
+ broken_smtp_server:
+ d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
+ "authentication mechanism is broken. Please report this to the\n"
+ "appropriate vendor and suggest that they re-read rfc2554 again\n"
+ "for the first time (specifically Section 4).\n",
+ mech));
+ }
+
+ /* eat whtspc */
+ for (challenge = respbuf + 4; isspace (*challenge); challenge++);
+
+ challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
+ g_free (respbuf);
+ if (challenge == NULL)
+ goto break_and_lose;
+
+ /* send our challenge */
+ cmdbuf = g_strdup_printf ("%s\r\n", challenge);
+ g_free (challenge);
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ goto lose;
+ }
+ g_free (cmdbuf);
+
+ /* get the server's response */
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+ }
+
+ /* check that the server says we are authenticated */
+ if (!respbuf || strncmp (respbuf, "235", 3)) {
+ if (respbuf && auth_challenge && !strncmp (respbuf, "334", 3)) {
+ /* broken server, but lets try and work around it anyway... */
+ goto broken_smtp_server;
+ }
+ g_free (respbuf);
+ goto lose;
+ }
+
+ camel_object_unref (sasl);
+ camel_operation_end (NULL);
+
+ return TRUE;
+
+ break_and_lose:
+ /* Get the server out of "waiting for continuation data" mode. */
+ d(fprintf (stderr, "sending : *\n"));
+ camel_stream_write (transport->ostream, "*\r\n", 3);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ lose:
+ if (!camel_exception_is_set (ex)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server.\n"));
+ }
+
+ camel_object_unref (sasl);
+ camel_operation_end (NULL);
+
+ return FALSE;
+}
+
+static gboolean
+smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
+{
+ /* we gotta tell the smtp server who we are. (our email addy) */
+ char *cmdbuf, *respbuf = NULL;
+
+ if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
+ cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
+ else
+ cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("MAIL FROM command failed: %s: mail not sent"),
+ g_strerror (errno));
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250 Sender OK..." */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "250", 3)) {
+ smtp_set_exception (transport, TRUE, respbuf, _("MAIL FROM command failed"), ex);
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ return TRUE;
+}
+
+static gboolean
+smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
+{
+ /* we gotta tell the smtp server who we are going to be sending
+ * our email to */
+ char *cmdbuf, *respbuf = NULL;
+
+ cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("RCPT TO command failed: %s: mail not sent"),
+ g_strerror (errno));
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250 Recipient OK..." */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "250", 3)) {
+ char *message;
+
+ message = g_strdup_printf (_("RCPT TO <%s> failed"), recipient);
+ smtp_set_exception (transport, TRUE, respbuf, message, ex);
+ g_free (message);
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ return TRUE;
+}
+
+static gboolean
+smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex)
+{
+ CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
+ struct _camel_header_raw *header, *savedbcc, *n, *tail;
+ char *cmdbuf, *respbuf = NULL;
+ CamelStreamFilter *filtered_stream;
+ CamelMimeFilter *crlffilter;
+ int ret;
+
+ /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
+ if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
+ enctype = CAMEL_BESTENC_7BIT;
+
+ /* FIXME: should we get the best charset too?? */
+ /* Changes the encoding of all mime parts to fit within our required
+ encoding type and also force any text parts with long lines (longer
+ than 998 octets) to wrap by QP or base64 encoding them. */
+ camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
+
+ cmdbuf = g_strdup ("DATA\r\n");
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("DATA command failed: %s: mail not sent"),
+ g_strerror (errno));
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "354", 3)) {
+ /* we should have gotten instructions on how to use the DATA command:
+ * 354 Enter mail, end with "." on a line by itself
+ */
+ smtp_set_exception (transport, TRUE, respbuf, _("DATA command failed"), ex);
+ g_free (respbuf);
+ return FALSE;
+ }
+
+ g_free (respbuf);
+ respbuf = NULL;
+
+ /* setup stream filtering */
+ crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
+ filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
+ camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
+ camel_object_unref (crlffilter);
+
+ /* unlink the bcc headers */
+ savedbcc = NULL;
+ tail = (struct _camel_header_raw *) &savedbcc;
+
+ header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
+ n = header->next;
+ while (n != NULL) {
+ if (!g_ascii_strcasecmp (n->name, "Bcc")) {
+ header->next = n->next;
+ tail->next = n;
+ n->next = NULL;
+ tail = n;
+ } else {
+ header = n;
+ }
+
+ n = header->next;
+ }
+
+ /* write the message */
+ ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream));
+
+ /* restore the bcc headers */
+ header->next = savedbcc;
+
+ if (ret == -1) {
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("DATA command failed: %s: mail not sent"),
+ g_strerror (errno));
+
+ camel_object_unref (filtered_stream);
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+
+ camel_stream_flush (CAMEL_STREAM (filtered_stream));
+ camel_object_unref (filtered_stream);
+
+ /* terminate the message body */
+
+ d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
+
+ if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("DATA command failed: %s: mail not sent"),
+ g_strerror (errno));
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+
+ do {
+ /* Check for "250 Sender OK..." */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "250", 3)) {
+ smtp_set_exception (transport, TRUE, respbuf, _("DATA command failed"), ex);
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ return TRUE;
+}
+
+static gboolean
+smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
+{
+ /* we are going to reset the smtp server (just to be nice) */
+ char *cmdbuf, *respbuf = NULL;
+
+ cmdbuf = g_strdup ("RSET\r\n");
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("RSET command failed: %s"), g_strerror (errno));
+
+ camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "250" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "250", 3)) {
+ smtp_set_exception (transport, TRUE, respbuf, _("RSET command failed"), ex);
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
+ g_free (respbuf);
+
+ return TRUE;
+}
+
+static gboolean
+smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
+{
+ /* we are going to reset the smtp server (just to be nice) */
+ char *cmdbuf, *respbuf = NULL;
+
+ cmdbuf = g_strdup ("QUIT\r\n");
+
+ d(fprintf (stderr, "sending : %s", cmdbuf));
+
+ if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
+ g_free (cmdbuf);
+ camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
+ _("QUIT command failed: %s"), g_strerror (errno));
+
+ return FALSE;
+ }
+ g_free (cmdbuf);
+
+ do {
+ /* Check for "221" */
+ g_free (respbuf);
+ respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
+
+ d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
+
+ if (!respbuf || strncmp (respbuf, "221", 3)) {
+ smtp_set_exception (transport, FALSE, respbuf, _("QUIT command failed"), ex);
+ g_free (respbuf);
+ return FALSE;
+ }
+ } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
+ g_free (respbuf);
+
+ return TRUE;
+}
diff --git a/camel/providers/smtp/camel-smtp-transport.h b/camel/providers/smtp/camel-smtp-transport.h
new file mode 100644
index 0000000000..87fcafb58b
--- /dev/null
+++ b/camel/providers/smtp/camel-smtp-transport.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-smtp-transport.h : class for an smtp transfer */
+
+/*
+ * Authors:
+ * Jeffrey Stedfast <fejj@stampede.org>
+ *
+ * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef CAMEL_SMTP_TRANSPORT_H
+#define CAMEL_SMTP_TRANSPORT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#include "camel-transport.h"
+#include "camel-tcp-stream.h"
+
+#define CAMEL_SMTP_TRANSPORT_TYPE (camel_smtp_transport_get_type ())
+#define CAMEL_SMTP_TRANSPORT(obj) (CAMEL_CHECK_CAST((obj), CAMEL_SMTP_TRANSPORT_TYPE, CamelSmtpTransport))
+#define CAMEL_SMTP_TRANSPORT_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_SMTP_TRANSPORT_TYPE, CamelSmtpTransportClass))
+#define CAMEL_IS_SMTP_TRANSPORT(o) (CAMEL_CHECK_TYPE((o), CAMEL_SMTP_TRANSPORT_TYPE))
+
+#define CAMEL_SMTP_TRANSPORT_IS_ESMTP (1 << 0)
+#define CAMEL_SMTP_TRANSPORT_8BITMIME (1 << 1)
+#define CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES (1 << 2)
+#define CAMEL_SMTP_TRANSPORT_STARTTLS (1 << 3)
+
+#define CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS (1 << 4)
+#define CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE (1 << 5)
+
+#define CAMEL_SMTP_TRANSPORT_USE_SSL (CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS | \
+ CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE)
+
+#define CAMEL_SMTP_TRANSPORT_AUTH_EQUAL (1 << 6) /* set if we are using authtypes from a broken AUTH= */
+
+typedef struct {
+ CamelTransport parent_object;
+
+ CamelStream *istream, *ostream;
+
+ guint32 flags;
+
+ gboolean connected;
+ struct sockaddr *localaddr;
+ socklen_t localaddrlen;
+
+ GHashTable *authtypes;
+} CamelSmtpTransport;
+
+typedef struct {
+ CamelTransportClass parent_class;
+
+} CamelSmtpTransportClass;
+
+/* Standard Camel function */
+CamelType camel_smtp_transport_get_type (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CAMEL_SMTP_TRANSPORT_H */
diff --git a/camel/tests/folder/Makefile.am b/camel/tests/folder/Makefile.am
new file mode 100644
index 0000000000..28b16007c1
--- /dev/null
+++ b/camel/tests/folder/Makefile.am
@@ -0,0 +1,28 @@
+
+INCLUDES = \
+ -I$(includedir) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/intl \
+ -I$(top_srcdir)/e-util \
+ -I$(top_srcdir)/camel \
+ -I$(top_srcdir)/camel/tests/lib \
+ -DG_LOG_DOMAIN=\"evolution-tests\" \
+ $(CAMEL_CFLAGS)
+
+LDADD = \
+ $(top_builddir)/camel/libcamel.la \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/camel/tests/lib/libcameltest.a \
+ $(INTLLIBS) \
+ $(EVOLUTION_MAIL_LIBS)
+
+check_PROGRAMS = \
+ test1 test2 test3 \
+ test4 test5 test6 \
+ test7 test8 test9 \
+ test10 test11
+
+TESTS = test1 test2 test3 \
+ test4 test5 test6 \
+ test7 test8 test9 \
+ test10 test11
diff --git a/camel/tests/folder/README b/camel/tests/folder/README
new file mode 100644
index 0000000000..1d6b0cf240
--- /dev/null
+++ b/camel/tests/folder/README
@@ -0,0 +1,14 @@
+
+test1 camel store folder operations (local only)
+test2 basic folder operations, local
+test3 folder searching and indexing, local
+test4 camel store folder operations, IMAP
+test5 camel store folder operations, NNTP
+test6 basic folder operations, IMAP
+test7 basic folder operations, NNTP
+
+test8 multithreaded folder torture test, local
+test9 filtering
+test10 multithreaded folder/store object bag torture test
+
+test11 old format maildir name compatability
diff --git a/camel/tests/folder/test11.c b/camel/tests/folder/test11.c
new file mode 100644
index 0000000000..4c60ee632e
--- /dev/null
+++ b/camel/tests/folder/test11.c
@@ -0,0 +1,188 @@
+
+/* threaded folder testing */
+
+#include <string.h>
+#include <pthread.h>
+
+#include "camel-test.h"
+#include "session.h"
+
+#include <camel/camel-exception.h>
+#include <camel/camel-service.h>
+#include <camel/camel-store.h>
+
+#define MAX_LOOP (10000)
+#define MAX_THREADS (5)
+
+#define d(x)
+
+#define ARRAY_LEN(x) (sizeof(x)/sizeof(x[0]))
+
+static CamelSession *session;
+
+/* FIXME: flags aren't really right yet */
+/* ASCII sorted on full_name */
+static CamelFolderInfo fi_list_1[] = {
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#.", "Inbox", ".", CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#Junk", "Junk", "Junk", CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#Trash", "Trash", "Trash", CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox", "testbox", "testbox", CAMEL_FOLDER_CHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox/foo", "foo", "testbox/foo", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox2", "testbox2", "testbox2", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+};
+
+static CamelFolderInfo fi_list_2[] = {
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#.", "Inbox", ".", CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#Junk", "Junk", "Junk", CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#Trash", "Trash", "Trash", CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox", "testbox", "testbox", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox2", "testbox2", "testbox2", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+};
+
+static CamelFolderInfo fi_list_3[] = {
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox", "testbox", "testbox", CAMEL_FOLDER_CHILDREN, -1, -1 },
+ { NULL, NULL, NULL, "maildir:/tmp/camel-test/maildir#testbox/foo", "foo", "testbox/foo", CAMEL_FOLDER_NOCHILDREN, -1, -1 },
+};
+
+static int
+cmp_fi(const void *a, const void *b)
+{
+ const CamelFolderInfo *fa = ((const CamelFolderInfo **)a)[0];
+ const CamelFolderInfo *fb = ((const CamelFolderInfo **)b)[0];
+
+ return strcmp(fa->full_name, fb->full_name);
+}
+
+static void
+add_fi(GPtrArray *folders, CamelFolderInfo *fi)
+{
+ while (fi) {
+ g_ptr_array_add(folders, fi);
+ if (fi->child)
+ add_fi(folders, fi->child);
+ fi = fi->next;
+ }
+}
+
+static void
+check_fi(CamelFolderInfo *fi, CamelFolderInfo *list, int len)
+{
+ GPtrArray *folders = g_ptr_array_new();
+ int i;
+
+ add_fi(folders, fi);
+ check_msg(folders->len == len, "unexpected number of folders returned from folderinfo");
+ qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), cmp_fi);
+ for (i=0;i<len;i++) {
+ CamelFolderInfo *f = folders->pdata[i];
+
+ camel_test_push("checking folder '%s'", list[i].uri);
+
+ check_msg(!strcmp(f->uri, list[i].uri), "got '%s' expecting '%s'", f->uri, list[i].uri);
+ check(!strcmp(f->full_name, list[i].full_name));
+
+ /* this might be translated, but we can't know */
+ camel_test_nonfatal("Inbox not english");
+ check(!strcmp(f->name, list[i].name));
+ camel_test_fatal();
+
+ camel_test_nonfatal("Flags mismatch");
+ check(f->flags == list[i].flags);
+ camel_test_fatal();
+
+ camel_test_pull();
+ }
+
+ g_ptr_array_free(folders, TRUE);
+}
+
+int main(int argc, char **argv)
+{
+ CamelException *ex;
+ CamelFolder *f1, *f2;
+ CamelStore *store;
+ CamelFolderInfo *fi;
+
+ camel_test_init(argc, argv);
+
+ ex = camel_exception_new();
+
+ /* clear out any camel-test data */
+ system("/bin/rm -rf /tmp/camel-test");
+
+ session = camel_test_session_new("/tmp/camel-test");
+ store = camel_session_get_store(session, "maildir:///tmp/camel-test/maildir", ex);
+ camel_exception_clear(ex);
+
+ camel_test_start("Maildir backward compatability tests");
+
+ camel_test_push("./ prefix path, one level");
+ f1 = camel_store_get_folder(store, "testbox", CAMEL_STORE_FOLDER_CREATE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ f2 = camel_store_get_folder(store, "./testbox", CAMEL_STORE_FOLDER_CREATE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(f1 == f2);
+ check_unref(f2, 2);
+ check_unref(f1, 1);
+ camel_test_pull();
+
+ camel_test_push("./ prefix path, one level, no create");
+ f1 = camel_store_get_folder(store, "testbox2", CAMEL_STORE_FOLDER_CREATE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ f2 = camel_store_get_folder(store, "./testbox2", 0, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(f1 == f2);
+ check_unref(f2, 2);
+ check_unref(f1, 1);
+ camel_test_pull();
+
+ camel_test_push("./ prefix path, two levels");
+ f1 = camel_store_get_folder(store, "testbox/foo", CAMEL_STORE_FOLDER_CREATE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ f2 = camel_store_get_folder(store, "./testbox/foo", CAMEL_STORE_FOLDER_CREATE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(f1 == f2);
+ check_unref(f2, 2);
+ check_unref(f1, 1);
+ camel_test_pull();
+
+ camel_test_push("'.' == Inbox");
+ f2 = camel_store_get_inbox(store, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ f1 = camel_store_get_folder(store, ".", 0, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(f1 == f2);
+ check_unref(f2, 2);
+ check_unref(f1, 1);
+ camel_test_pull();
+
+ camel_test_push("folder info, recursive");
+ fi = camel_store_get_folder_info(store, "", CAMEL_STORE_FOLDER_INFO_RECURSIVE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(fi != NULL);
+ check_fi(fi, fi_list_1, ARRAY_LEN(fi_list_1));
+ camel_test_pull();
+
+ camel_test_push("folder info, flat");
+ fi = camel_store_get_folder_info(store, "", 0, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(fi != NULL);
+ check_fi(fi, fi_list_2, ARRAY_LEN(fi_list_2));
+ camel_test_pull();
+
+ camel_test_push("folder info, recursive, non root");
+ fi = camel_store_get_folder_info(store, "testbox", CAMEL_STORE_FOLDER_INFO_RECURSIVE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(fi != NULL);
+ check_fi(fi, fi_list_3, ARRAY_LEN(fi_list_3));
+ camel_test_pull();
+
+ check_unref(store, 1);
+ check_unref(session, 1);
+
+ camel_exception_free(ex);
+
+ camel_test_end();
+
+ return 0;
+}
diff --git a/camel/tests/lib/folders.c b/camel/tests/lib/folders.c
new file mode 100644
index 0000000000..527f1622d8
--- /dev/null
+++ b/camel/tests/lib/folders.c
@@ -0,0 +1,580 @@
+#include <string.h>
+
+#include "camel-test.h"
+#include "folders.h"
+#include "messages.h"
+
+#include "camel/camel-exception.h"
+
+/* check the total/unread is what we think it should be */
+void
+test_folder_counts(CamelFolder *folder, int total, int unread)
+{
+ GPtrArray *s;
+ int i, myunread;
+ guint32 gottotal, gotunread;
+ CamelMessageInfo *info;
+
+ push("test folder counts %d total %d unread", total, unread);
+
+ /* first, use the standard functions */
+ check(camel_folder_get_message_count(folder) == total);
+ check(camel_folder_get_unread_message_count(folder) == unread);
+
+ /* accessors */
+ camel_object_get(folder, NULL, CAMEL_FOLDER_TOTAL, &gottotal, CAMEL_FOLDER_UNREAD, &gotunread, 0);
+ check(gottotal == total);
+ check(gotunread == unread);
+
+ /* next, use the summary */
+ s = camel_folder_get_summary(folder);
+ check(s != NULL);
+ check(s->len == total);
+ myunread = s->len;
+ for (i=0;i<s->len;i++) {
+ info = s->pdata[i];
+ if (info->flags & CAMEL_MESSAGE_SEEN)
+ myunread--;
+ }
+ check(unread == myunread);
+ camel_folder_free_summary(folder, s);
+
+ /* last, use the uid list */
+ s = camel_folder_get_uids(folder);
+ check(s != NULL);
+ check(s->len == total);
+ myunread = s->len;
+ for (i=0;i<s->len;i++) {
+ info = camel_folder_get_message_info(folder, s->pdata[i]);
+ if (info->flags & CAMEL_MESSAGE_SEEN)
+ myunread--;
+ camel_folder_free_message_info(folder, info);
+ }
+ check(unread == myunread);
+ camel_folder_free_uids(folder, s);
+
+ pull();
+}
+
+static int
+safe_strcmp(const char *a, const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ if (a == NULL)
+ return 1;
+ if (b == NULL)
+ return -1;
+ return strcmp(a, b);
+}
+
+void
+test_message_info(CamelMimeMessage *msg, const CamelMessageInfo *info)
+{
+ check_msg(safe_strcmp(camel_message_info_subject(info), camel_mime_message_get_subject(msg)) == 0,
+ "info->subject = '%s', get_subject() = '%s'", camel_message_info_subject(info), camel_mime_message_get_subject(msg));
+
+ /* FIXME: testing from/cc/to, etc is more tricky */
+
+ check(info->date_sent == camel_mime_message_get_date(msg, NULL));
+
+ /* date received isn't set for messages that haven't been sent anywhere ... */
+ /*check(info->date_received == camel_mime_message_get_date_received(msg, NULL));*/
+
+ /* so is messageid/references, etc */
+}
+
+/* check a message is present */
+void
+test_folder_message(CamelFolder *folder, const char *uid)
+{
+ CamelMimeMessage *msg;
+ CamelMessageInfo *info;
+ GPtrArray *s;
+ int i;
+ CamelException *ex = camel_exception_new();
+ int found;
+
+ push("uid %s is in folder", uid);
+
+ /* first try getting info */
+ info = camel_folder_get_message_info(folder, uid);
+ check(info != NULL);
+ check(strcmp(camel_message_info_uid(info), uid) == 0);
+ camel_folder_free_message_info(folder, info);
+
+ /* then, getting message */
+ msg = camel_folder_get_message(folder, uid, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(msg != NULL);
+
+ /* cross check with info */
+ test_message_info(msg, info);
+
+ camel_object_unref((CamelObject *)msg);
+
+ /* see if it is in the summary (only once) */
+ s = camel_folder_get_summary(folder);
+ check(s != NULL);
+ found = 0;
+ for (i=0;i<s->len;i++) {
+ info = s->pdata[i];
+ if (strcmp(camel_message_info_uid(info), uid) == 0)
+ found++;
+ }
+ check(found == 1);
+ camel_folder_free_summary(folder, s);
+
+ /* check it is in the uid list */
+ s = camel_folder_get_uids(folder);
+ check(s != NULL);
+ found = 0;
+ for (i=0;i<s->len;i++) {
+ if (strcmp(s->pdata[i], uid) == 0)
+ found++;
+ }
+ check(found == 1);
+ camel_folder_free_uids(folder, s);
+
+ camel_exception_free(ex);
+
+ pull();
+}
+
+/* check message not present */
+void
+test_folder_not_message(CamelFolder *folder, const char *uid)
+{
+ CamelMimeMessage *msg;
+ CamelMessageInfo *info;
+ GPtrArray *s;
+ int i;
+ CamelException *ex = camel_exception_new();
+ int found;
+
+ push("uid '%s' is not in folder", uid);
+
+ /* first try getting info */
+ push("no message info");
+ info = camel_folder_get_message_info(folder, uid);
+ check(info == NULL);
+ pull();
+
+ /* then, getting message */
+ push("no message");
+ msg = camel_folder_get_message(folder, uid, ex);
+ check(camel_exception_is_set(ex));
+ check(msg == NULL);
+ camel_exception_clear(ex);
+ pull();
+
+ /* see if it is not in the summary (only once) */
+ push("not in summary list");
+ s = camel_folder_get_summary(folder);
+ check(s != NULL);
+ found = 0;
+ for (i=0;i<s->len;i++) {
+ info = s->pdata[i];
+ if (strcmp(camel_message_info_uid(info), uid) == 0)
+ found++;
+ }
+ check(found == 0);
+ camel_folder_free_summary(folder, s);
+ pull();
+
+ /* check it is not in the uid list */
+ push("not in uid list");
+ s = camel_folder_get_uids(folder);
+ check(s != NULL);
+ found = 0;
+ for (i=0;i<s->len;i++) {
+ if (strcmp(s->pdata[i], uid) == 0)
+ found++;
+ }
+ check(found == 0);
+ camel_folder_free_uids(folder, s);
+ pull();
+
+ camel_exception_free(ex);
+
+ pull();
+}
+
+/* test basic store operations on folders */
+/* TODO: Add subscription stuff */
+void
+test_folder_basic(CamelSession *session, const char *storename, int local, int spool)
+{
+ CamelStore *store;
+ CamelException *ex = camel_exception_new();
+ CamelFolder *folder;
+ char *what = g_strdup_printf("testing store: %s", storename);
+
+ camel_test_start(what);
+ test_free(what);
+
+ push("getting store");
+ store = camel_session_get_store(session, storename, ex);
+ check_msg(!camel_exception_is_set(ex), "getting store: %s", camel_exception_get_description(ex));
+ check(store != NULL);
+ pull();
+
+ /* local providers == no inbox */
+ push("getting inbox folder");
+ folder = camel_store_get_inbox(store, ex);
+ if (local) {
+ /* Well, maildir can have an inbox */
+ if (folder) {
+ check(!camel_exception_is_set(ex));
+ check_unref(folder, 1);
+ } else {
+ check(camel_exception_is_set(ex));
+ camel_exception_clear(ex);
+ }
+ } else {
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(folder != NULL);
+ check_unref(folder, 2);
+ }
+ pull();
+
+ push("getting a non-existant folder, no create");
+ folder = camel_store_get_folder(store, "unknown", 0, ex);
+ check(camel_exception_is_set(ex));
+ check(folder == NULL);
+ camel_exception_clear(ex);
+ pull();
+
+ if (!spool) {
+ push("getting a non-existant folder, with create");
+ folder = camel_store_get_folder(store, "testbox", CAMEL_STORE_FOLDER_CREATE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(folder != NULL);
+ if (local)
+ check_unref(folder, 1);
+ else
+ check_unref(folder, 2);
+ pull();
+
+ push("getting an existing folder");
+ folder = camel_store_get_folder(store, "testbox", 0, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(folder != NULL);
+ if (local)
+ check_unref(folder, 1);
+ else
+ check_unref(folder, 2);
+ pull();
+
+ push("renaming a non-existant folder");
+ camel_store_rename_folder(store, "unknown1", "unknown2", ex);
+ check(camel_exception_is_set(ex));
+ camel_exception_clear(ex);
+ pull();
+
+ push("renaming an existing folder");
+ camel_store_rename_folder(store, "testbox", "testbox2", ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ pull();
+
+ push("opening the old name of a renamed folder");
+ folder = camel_store_get_folder(store, "testbox", 0, ex);
+ check(camel_exception_is_set(ex));
+ check(folder == NULL);
+ camel_exception_clear(ex);
+ pull();
+
+ push("opening the new name of a renamed folder");
+ folder = camel_store_get_folder(store, "testbox2", 0, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(folder != NULL);
+ if (local)
+ check_unref(folder, 1);
+ else
+ check_unref(folder, 2);
+ pull();
+ }
+
+ push("deleting a non-existant folder");
+ camel_store_delete_folder(store, "unknown", ex);
+ check(camel_exception_is_set(ex));
+ camel_exception_clear(ex);
+ pull();
+
+ if (!spool) {
+ push("deleting an existing folder");
+ camel_store_delete_folder(store, "testbox2", ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ pull();
+ }
+
+ push("opening a folder that has been deleted");
+ folder = camel_store_get_folder(store, "testbox2", 0, ex);
+ check(camel_exception_is_set(ex));
+ check(folder == NULL);
+ camel_exception_clear(ex);
+ pull();
+
+ check_unref(store, 1);
+
+ camel_test_end();
+
+ camel_exception_free(ex);
+}
+
+
+/* todo: cross-check everything with folder_info checks as well */
+/* this should probably take a folder instead of a session ... */
+void
+test_folder_message_ops(CamelSession *session, const char *name, int local, const char *mailbox)
+{
+ CamelStore *store;
+ CamelException *ex = camel_exception_new();
+ CamelFolder *folder;
+ CamelMimeMessage *msg;
+ int j;
+ int indexed, max;
+ GPtrArray *uids;
+ CamelMessageInfo *info;
+
+ max=local?2:1;
+
+ for (indexed = 0;indexed<max;indexed++) {
+ char *what = g_strdup_printf("folder ops: %s %s", name, local?(indexed?"indexed":"non-indexed"):"");
+ int flags;
+
+ camel_test_start(what);
+ test_free(what);
+
+ push("getting store");
+ store = camel_session_get_store(session, name, ex);
+ check_msg(!camel_exception_is_set(ex), "getting store: %s", camel_exception_get_description(ex));
+ check(store != NULL);
+ pull();
+
+ push("creating %sindexed folder", indexed?"":"non-");
+ if (indexed)
+ flags = CAMEL_STORE_FOLDER_CREATE|CAMEL_STORE_FOLDER_BODY_INDEX;
+ else
+ flags = CAMEL_STORE_FOLDER_CREATE;
+ folder = camel_store_get_folder(store, mailbox, flags, ex);
+
+ /* we can't create mailbox outside of namespace, since we have no api for it, try
+ using inbox namespace, works for courier */
+ if (folder == NULL) {
+ char *mbox = g_strdup_printf("INBOX/%s", mailbox);
+ mailbox = mbox;
+ camel_exception_clear(ex);
+ folder = camel_store_get_folder(store, mailbox, flags, ex);
+ }
+
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(folder != NULL);
+
+ /* verify empty/can't get nonexistant stuff */
+ test_folder_counts(folder, 0, 0);
+ test_folder_not_message(folder, "0");
+ test_folder_not_message(folder, "");
+
+ for (j=0;j<10;j++) {
+ char *content, *subject;
+
+ push("creating test message");
+ msg = test_message_create_simple();
+ content = g_strdup_printf("Test message %d contents\n\n", j);
+ test_message_set_content_simple((CamelMimePart *)msg, 0, "text/plain",
+ content, strlen(content));
+ test_free(content);
+ subject = g_strdup_printf("Test message %d", j);
+ camel_mime_message_set_subject(msg, subject);
+ pull();
+
+ push("appending simple message %d", j);
+ camel_folder_append_message(folder, msg, NULL, NULL, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+
+#if 0
+ /* sigh, this shouldn't be required, but the imap code is too dumb to do it itself */
+ if (!local) {
+ push("forcing a refresh of folder updates");
+ camel_folder_refresh_info(folder, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ pull();
+ }
+#endif
+ /*if (!local)
+ camel_test_nonfatal("unread counts dont seem right for imap");*/
+
+ test_folder_counts(folder, j+1, j+1);
+
+ /*if (!local)
+ camel_test_fatal();*/
+
+ push("checking it is in the right uid slot & exists");
+ uids = camel_folder_get_uids(folder);
+ check(uids != NULL);
+ check(uids->len == j+1);
+ if (uids->len > j)
+ test_folder_message(folder, uids->pdata[j]);
+ pull();
+
+ push("checking it is the right message (subject): %s", subject);
+ if (uids->len > j) {
+ info = camel_folder_get_message_info(folder, uids->pdata[j]);
+ check(info != NULL);
+ check_msg(strcmp(camel_message_info_subject(info), subject)==0,
+ "info->subject %s", camel_message_info_subject(info));
+ camel_folder_free_message_info(folder, info);
+ }
+ camel_folder_free_uids(folder, uids);
+ pull();
+
+ test_free(subject);
+
+ /*if (!local)
+ camel_test_fatal();*/
+
+ check_unref(msg, 1);
+ pull();
+ }
+
+ if (local)
+ check_unref(folder, 1);
+ else
+ check_unref(folder, 2);
+ pull();
+
+#if 0
+ push("deleting test folder, with messages in it");
+ camel_store_delete_folder(store, mailbox, ex);
+ check(camel_exception_is_set(ex));
+ camel_exception_clear(ex);
+ pull();
+#endif
+
+ push("re-opening folder");
+ folder = camel_store_get_folder(store, mailbox, flags, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ check(folder != NULL);
+
+ /* verify counts */
+ test_folder_counts(folder, 10, 10);
+
+ /* re-check uid's, after a reload */
+ uids = camel_folder_get_uids(folder);
+ check(uids != NULL);
+ check(uids->len == 10);
+ for (j=0;j<10;j++) {
+ char *subject = g_strdup_printf("Test message %d", j);
+
+ push("verify reload of %s", subject);
+ test_folder_message(folder, uids->pdata[j]);
+
+ info = camel_folder_get_message_info(folder, uids->pdata[j]);
+ check_msg(strcmp(camel_message_info_subject(info), subject)==0,
+ "info->subject %s", camel_message_info_subject(info));
+ test_free(subject);
+ camel_folder_free_message_info(folder, info);
+ pull();
+ }
+
+ push("deleting first message & expunging");
+ camel_folder_delete_message(folder, uids->pdata[0]);
+ /* deleting marks message read implictly */
+ test_folder_counts(folder, 10, 9);
+ camel_folder_expunge(folder, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ test_folder_not_message(folder, uids->pdata[0]);
+ test_folder_counts(folder, 9, 9);
+
+ camel_folder_free_uids(folder, uids);
+
+ uids = camel_folder_get_uids(folder);
+ check(uids != NULL);
+ check(uids->len == 9);
+ for (j=0;j<9;j++) {
+ char *subject = g_strdup_printf("Test message %d", j+1);
+
+ push("verify after expunge of %s", subject);
+ test_folder_message(folder, uids->pdata[j]);
+
+ info = camel_folder_get_message_info(folder, uids->pdata[j]);
+ check_msg(strcmp(camel_message_info_subject(info), subject)==0,
+ "info->subject %s", camel_message_info_subject(info));
+ test_free(subject);
+ camel_folder_free_message_info(folder, info);
+ pull();
+ }
+ pull();
+
+ push("deleting last message & expunging");
+ camel_folder_delete_message(folder, uids->pdata[8]);
+ /* sync? */
+ test_folder_counts(folder, 9, 8);
+ camel_folder_expunge(folder, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ test_folder_not_message(folder, uids->pdata[8]);
+ test_folder_counts(folder, 8, 8);
+
+ camel_folder_free_uids(folder, uids);
+
+ uids = camel_folder_get_uids(folder);
+ check(uids != NULL);
+ check(uids->len == 8);
+ for (j=0;j<8;j++) {
+ char *subject = g_strdup_printf("Test message %d", j+1);
+
+ push("verify after expunge of %s", subject);
+ test_folder_message(folder, uids->pdata[j]);
+
+ info = camel_folder_get_message_info(folder, uids->pdata[j]);
+ check_msg(strcmp(camel_message_info_subject(info), subject)==0,
+ "info->subject %s", camel_message_info_subject(info));
+ test_free(subject);
+ camel_folder_free_message_info(folder, info);
+ pull();
+ }
+ pull();
+
+ push("deleting all messages & expunging");
+ for (j=0;j<8;j++) {
+ camel_folder_delete_message(folder, uids->pdata[j]);
+ }
+ /* sync? */
+ test_folder_counts(folder, 8, 0);
+ camel_folder_expunge(folder, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ for (j=0;j<8;j++) {
+ test_folder_not_message(folder, uids->pdata[j]);
+ }
+ test_folder_counts(folder, 0, 0);
+
+ camel_folder_free_uids(folder, uids);
+ pull();
+
+ if (local)
+ check_unref(folder, 1);
+ else
+ check_unref(folder, 2);
+ pull(); /* re-opening folder */
+
+ if (g_ascii_strcasecmp(mailbox, "INBOX") != 0) {
+ push("deleting test folder, with no messages in it");
+ camel_store_delete_folder(store, mailbox, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ pull();
+ }
+
+ if (!local) {
+ push("disconneect service");
+ camel_service_disconnect((CamelService *)store, TRUE, ex);
+ check_msg(!camel_exception_is_set(ex), "%s", camel_exception_get_description(ex));
+ pull();
+ }
+
+ check_unref(store, 1);
+ camel_test_end();
+ }
+
+ camel_exception_free(ex);
+}
diff --git a/camel/tests/message/test2.c b/camel/tests/message/test2.c
new file mode 100644
index 0000000000..4584eace6a
--- /dev/null
+++ b/camel/tests/message/test2.c
@@ -0,0 +1,326 @@
+#include "camel-test.h"
+#include "messages.h"
+#include "addresses.h"
+
+/* for stat */
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <iconv.h>
+
+#include <camel/camel-internet-address.h>
+#include <camel/camel-address.h>
+
+#include "address-data.h"
+
+static char *convert(const char *in, const char *from, const char *to)
+{
+ iconv_t ic = iconv_open(to, from);
+ char *out, *outp;
+ const char *inp;
+ size_t inlen, outlen;
+
+ if (ic == (iconv_t)-1)
+ return g_strdup(in);
+
+ inlen = strlen(in);
+ outlen = inlen*5 + 16;
+
+ outp = out = g_malloc(outlen);
+ inp = in;
+
+ if (iconv(ic, &inp, &inlen, &outp, &outlen) == -1) {
+ test_free(out);
+ iconv_close(ic);
+ return g_strdup(in);
+ }
+
+ if (iconv(ic, NULL, 0, &outp, &outlen) == -1) {
+ test_free(out);
+ iconv_close(ic);
+ return g_strdup(in);
+ }
+
+ iconv_close(ic);
+
+ *outp = 0;
+
+#if 0
+ /* lets see if we can convert back again? */
+ {
+ char *nout, *noutp;
+ iconv_t ic = iconv_open(from, to);
+
+ inp = out;
+ inlen = strlen(out);
+ outlen = inlen*5 + 16;
+ noutp = nout = g_malloc(outlen);
+ if (iconv(ic, &inp, &inlen, &noutp, &outlen) == -1
+ || iconv(ic, NULL, 0, &noutp, &outlen) == -1) {
+ g_warning("Cannot convert '%s' \n from %s to %s: %s\n", in, to, from, strerror(errno));
+ }
+ iconv_close(ic);
+ }
+
+ /* and lets see what camel thinks out optimal charset is */
+ {
+ printf("Camel thinks the best encoding of '%s' is %s, although we converted from %s\n",
+ in, camel_charset_best(out, strlen(out)), from);
+ }
+#endif
+
+ return out;
+}
+
+#define to_utf8(in, type) convert(in, type, "utf-8")
+#define from_utf8(in, type) convert(in, "utf-8", type)
+
+#define ARRAY_LEN(x) (sizeof(x)/sizeof(x[0]))
+
+int main(int argc, char **argv)
+{
+ int i;
+ CamelInternetAddress *addr, *addr2;
+ char *name;
+ char *charset;
+ const char *real, *where;
+ char *enc, *enc2, *format, *format2;
+
+ camel_test_init(argc, argv);
+
+ camel_test_start("CamelInternetAddress, basics");
+
+ addr = camel_internet_address_new();
+
+ push("Test blank address");
+ check(camel_address_length(CAMEL_ADDRESS(addr)) == 0);
+ check(camel_internet_address_get(addr, 0, &real, &where) == FALSE);
+ pull();
+
+ push("Test blank clone");
+ addr2 = CAMEL_INTERNET_ADDRESS(camel_address_new_clone(CAMEL_ADDRESS(addr)));
+ test_address_compare(addr, addr2);
+ check_unref(addr2, 1);
+ pull();
+
+ push("Test add 1");
+ camel_internet_address_add(addr, "Zed", "nowhere@here.com.au");
+ check(camel_address_length(CAMEL_ADDRESS(addr)) == 1);
+ check(camel_internet_address_get(addr, 0, &real, &where) == TRUE);
+ check_msg(string_equal("Zed", real), "real = '%s'", real);
+ check(strcmp(where, "nowhere@here.com.au") == 0);
+ pull();
+
+ push("Test clone 1");
+ addr2 = CAMEL_INTERNET_ADDRESS(camel_address_new_clone(CAMEL_ADDRESS(addr)));
+ test_address_compare(addr, addr2);
+ check_unref(addr2, 1);
+ pull();
+
+ push("Test add many");
+ for (i=1;i<10;i++) {
+ char name[16], a[32];
+ sprintf(name, "Zed %d", i);
+ sprintf(a, "nowhere@here-%d.com.au", i);
+ camel_internet_address_add(addr, name, a);
+ check(camel_address_length(CAMEL_ADDRESS(addr)) == i+1);
+ check(camel_internet_address_get(addr, i, &real, &where) == TRUE);
+ check_msg(string_equal(name, real), "name = '%s' real = '%s'", name, real);
+ check(strcmp(where, a) == 0);
+ }
+ pull();
+
+ /* put a few of these in to make it look like its doing something impressive ... :) */
+ camel_test_end();
+ camel_test_start("CamelInternetAddress, search");
+
+ push("Test search");
+ camel_test_nonfatal("Address comparisons should ignore whitespace??");
+ check(camel_internet_address_find_name(addr, "Zed 1", &where) == 1);
+ check(camel_internet_address_find_name(addr, "Zed 9", &where) == 9);
+ check(camel_internet_address_find_name(addr, "Zed", &where) == 0);
+ check(camel_internet_address_find_name(addr, " Zed", &where) == 0);
+ check(camel_internet_address_find_name(addr, "Zed ", &where) == 0);
+ check(camel_internet_address_find_name(addr, " Zed ", &where) == 0);
+ check(camel_internet_address_find_name(addr, "Zed 20", &where) == -1);
+ check(camel_internet_address_find_name(addr, "", &where) == -1);
+ /* interface dont handle nulls :) */
+ /*check(camel_internet_address_find_name(addr, NULL, &where) == -1);*/
+
+ check(camel_internet_address_find_address(addr, "nowhere@here-1.com.au", &where) == 1);
+ check(camel_internet_address_find_address(addr, "nowhere@here-1 . com.au", &where) == 1);
+ check(camel_internet_address_find_address(addr, "nowhere@here-2 .com.au ", &where) == 2);
+ check(camel_internet_address_find_address(addr, " nowhere @here-3.com.au", &where) == 3);
+ check(camel_internet_address_find_address(addr, "nowhere@here-20.com.au ", &where) == -1);
+ check(camel_internet_address_find_address(addr, "", &where) == -1);
+ /*check(camel_internet_address_find_address(addr, NULL, &where) == -1);*/
+ camel_test_fatal();
+ pull();
+
+ camel_test_end();
+ camel_test_start("CamelInternetAddress, copy/cat/clone");
+
+ push("Test clone many");
+ addr2 = CAMEL_INTERNET_ADDRESS(camel_address_new_clone(CAMEL_ADDRESS(addr)));
+ test_address_compare(addr, addr2);
+ pull();
+
+ push("Test remove items");
+ camel_address_remove(CAMEL_ADDRESS(addr2), 0);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 9);
+ camel_address_remove(CAMEL_ADDRESS(addr2), 0);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 8);
+ camel_address_remove(CAMEL_ADDRESS(addr2), 5);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 7);
+ camel_address_remove(CAMEL_ADDRESS(addr2), 10);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 7);
+ camel_address_remove(CAMEL_ADDRESS(addr2), -1);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 0);
+ check_unref(addr2, 1);
+ pull();
+
+ push("Testing copy/cat");
+ push("clone + cat");
+ addr2 = CAMEL_INTERNET_ADDRESS(camel_address_new_clone(CAMEL_ADDRESS(addr)));
+ camel_address_cat(CAMEL_ADDRESS(addr2), CAMEL_ADDRESS(addr));
+ check(camel_address_length(CAMEL_ADDRESS(addr)) == 10);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 20);
+ check_unref(addr2, 1);
+ pull();
+
+ push("cat + cat + copy");
+ addr2 = camel_internet_address_new();
+ camel_address_cat(CAMEL_ADDRESS(addr2), CAMEL_ADDRESS(addr));
+ test_address_compare(addr, addr2);
+ camel_address_cat(CAMEL_ADDRESS(addr2), CAMEL_ADDRESS(addr));
+ check(camel_address_length(CAMEL_ADDRESS(addr)) == 10);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 20);
+ camel_address_copy(CAMEL_ADDRESS(addr2), CAMEL_ADDRESS(addr));
+ test_address_compare(addr, addr2);
+ check_unref(addr2, 1);
+ pull();
+
+ push("copy");
+ addr2 = camel_internet_address_new();
+ camel_address_copy(CAMEL_ADDRESS(addr2), CAMEL_ADDRESS(addr));
+ test_address_compare(addr, addr2);
+ check_unref(addr2, 1);
+ pull();
+
+ pull();
+
+ check_unref(addr, 1);
+
+ camel_test_end();
+
+ camel_test_start("CamelInternetAddress, I18N");
+
+ for (i=0;i<ARRAY_LEN(test_lines);i++) {
+ push("Testing text line %d (%s) '%s'", i, test_lines[i].type, test_lines[i].line);
+
+ addr = camel_internet_address_new();
+
+ /* first, convert to api format (utf-8) */
+ charset = test_lines[i].type;
+ name = to_utf8(test_lines[i].line, charset);
+
+ push("Address setup");
+ camel_internet_address_add(addr, name, "nobody@nowhere.com");
+ check(camel_internet_address_get(addr, 0, &real, &where) == TRUE);
+ check_msg(string_equal(name, real), "name = '%s' real = '%s'", name, real);
+ check(strcmp(where, "nobody@nowhere.com") == 0);
+ test_free(name);
+
+ check(camel_internet_address_get(addr, 1, &real, &where) == FALSE);
+ check(camel_address_length(CAMEL_ADDRESS(addr)) == 1);
+ pull();
+
+ push("Address encode/decode");
+ enc = camel_address_encode(CAMEL_ADDRESS(addr));
+
+ addr2 = camel_internet_address_new();
+ check(camel_address_decode(CAMEL_ADDRESS(addr2), enc) == 1);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 1);
+
+ enc2 = camel_address_encode(CAMEL_ADDRESS(addr2));
+ check_msg(string_equal(enc, enc2), "enc = '%s' enc2 = '%s'", enc, enc2);
+ test_free(enc2);
+
+ push("Compare addresses");
+ test_address_compare(addr, addr2);
+ pull();
+ check_unref(addr2, 1);
+ test_free(enc);
+ pull();
+
+ /* FIXME: format/unformat arne't guaranteed to be reversible, at least at the moment */
+ camel_test_nonfatal("format/unformat not (yet) reversible for all cases");
+
+ push("Address format/unformat");
+ format = camel_address_format(CAMEL_ADDRESS(addr));
+
+ addr2 = camel_internet_address_new();
+ check(camel_address_unformat(CAMEL_ADDRESS(addr2), format) == 1);
+ check(camel_address_length(CAMEL_ADDRESS(addr2)) == 1);
+
+ format2 = camel_address_format(CAMEL_ADDRESS(addr2));
+ check_msg(string_equal(format, format2), "format = '%s\n\tformat2 = '%s'", format, format2);
+ test_free(format2);
+
+ /* currently format/unformat doesn't handle ,'s and other special chars at all */
+ if (camel_address_length(CAMEL_ADDRESS(addr2)) == 1) {
+ push("Compare addresses");
+ test_address_compare(addr, addr2);
+ pull();
+ }
+
+ test_free(format);
+ pull();
+
+ camel_test_fatal();
+
+ check_unref(addr2, 1);
+
+ check_unref(addr, 1);
+ pull();
+
+ }
+
+ camel_test_end();
+
+ camel_test_start("CamelInternetAddress, I18N decode");
+
+ for (i=0;i<ARRAY_LEN(test_address);i++) {
+ push("Testing address line %d '%s'", i, test_address[i].addr);
+
+ addr = camel_internet_address_new();
+ push("checking decoded");
+ check(camel_address_decode(CAMEL_ADDRESS(addr), test_address[i].addr) == test_address[i].count);
+ format = camel_address_format(CAMEL_ADDRESS(addr));
+ check(strcmp(format, test_address[i].utf8) == 0);
+ test_free(format);
+ pull();
+
+ push("Comparing re-encoded output");
+ addr2 = CAMEL_INTERNET_ADDRESS(camel_internet_address_new());
+ enc = camel_address_encode(CAMEL_ADDRESS(addr));
+ check_msg(camel_address_decode(CAMEL_ADDRESS(addr2), enc) == test_address[i].count, "enc = '%s'", enc);
+ test_free(enc);
+ test_address_compare(addr, addr2);
+ check_unref(addr2, 1);
+ pull();
+
+ check_unref(addr, 1);
+
+ pull();
+ }
+
+ camel_test_end();
+
+ /* FIXME: Add test of decoding of broken addresses */
+
+ return 0;
+}
+
+
diff --git a/camel/tests/message/test3.c b/camel/tests/message/test3.c
new file mode 100644
index 0000000000..ac82895435
--- /dev/null
+++ b/camel/tests/message/test3.c
@@ -0,0 +1,196 @@
+/*
+ Multipart.
+*/
+
+#include "camel-test.h"
+#include "messages.h"
+
+/* for stat */
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <camel/camel-mime-message.h>
+#include <camel/camel-stream-fs.h>
+#include <camel/camel-stream-mem.h>
+#include "camel/camel-multipart.h"
+
+int main(int argc, char **argv)
+{
+ CamelMimeMessage *msg, *msg2, *msg3;
+ CamelMultipart *mp, *mp2;
+ CamelMimePart *part, *part2, *part3;
+
+ camel_test_init(argc, argv);
+
+ camel_test_start("multipart message");
+
+ push("building message");
+ msg = test_message_create_simple();
+ mp = camel_multipart_new();
+
+ /* Hrm, this should be able to set its own boundary, no? */
+ camel_multipart_set_boundary(mp, "_=,.XYZ_Kangaroo_Meat_is_!_ABADF00D");
+ check(strcmp(camel_multipart_get_boundary(mp), "_=,.XYZ_Kangaroo_Meat_is_!_ABADF00D") == 0);
+
+ camel_medium_set_content_object((CamelMedium *)msg, (CamelDataWrapper *)mp);
+ check(camel_multipart_get_number(mp) == 0);
+ check(camel_multipart_get_part(mp, 0) == NULL);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ push("adding/removing parts");
+ part = camel_mime_part_new();
+ test_message_set_content_simple(part, 0, "text/plain", "content part 1", strlen("content part 1"));
+ camel_multipart_add_part(mp, part);
+ check(CAMEL_OBJECT(part)->ref_count == 2);
+ check(camel_multipart_get_number(mp) == 1);
+ check(camel_multipart_get_part(mp, 0) == part);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ camel_multipart_remove_part(mp, part);
+ check(CAMEL_OBJECT(part)->ref_count == 1);
+ check(camel_multipart_get_number(mp) == 0);
+ check(camel_multipart_get_part(mp, 0) == NULL);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ camel_multipart_add_part_at(mp, part, 0);
+ check(CAMEL_OBJECT(part)->ref_count == 2);
+ check(camel_multipart_get_number(mp) == 1);
+ check(camel_multipart_get_part(mp, 0) == part);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ check(camel_multipart_remove_part_at(mp, 1) == NULL);
+ check(CAMEL_OBJECT(part)->ref_count == 2);
+ check(camel_multipart_get_number(mp) == 1);
+ check(camel_multipart_get_part(mp, 0) == part);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ check(camel_multipart_remove_part_at(mp, 0) == part);
+ check(CAMEL_OBJECT(part)->ref_count == 1);
+ check(camel_multipart_get_number(mp) == 0);
+ check(camel_multipart_get_part(mp, 0) == NULL);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ camel_multipart_add_part(mp, part);
+ check(CAMEL_OBJECT(part)->ref_count == 2);
+ check(camel_multipart_get_number(mp) == 1);
+ check(camel_multipart_get_part(mp, 0) == part);
+ check(camel_multipart_get_part(mp, 1) == NULL);
+
+ part2 = camel_mime_part_new();
+ test_message_set_content_simple(part2, 0, "text/plain", "content part 2", strlen("content part 2"));
+ camel_multipart_add_part(mp, part2);
+ check(CAMEL_OBJECT(part2)->ref_count == 2);
+ check(camel_multipart_get_number(mp) == 2);
+ check(camel_multipart_get_part(mp, 0) == part);
+ check(camel_multipart_get_part(mp, 1) == part2);
+
+ part3 = camel_mime_part_new();
+ test_message_set_content_simple(part3, 0, "text/plain", "content part 3", strlen("content part 3"));
+ camel_multipart_add_part_at(mp, part3, 1);
+ check(CAMEL_OBJECT(part3)->ref_count == 2);
+ check(camel_multipart_get_number(mp) == 3);
+ check(camel_multipart_get_part(mp, 0) == part);
+ check(camel_multipart_get_part(mp, 1) == part3);
+ check(camel_multipart_get_part(mp, 2) == part2);
+ pull();
+
+ push("save message to test3.msg");
+ unlink("test3.msg");
+ test_message_write_file(msg, "test3.msg");
+ pull();
+
+ push("read from test3.msg");
+ msg2 = test_message_read_file("test3.msg");
+ pull();
+
+ push("compre content of multipart");
+ mp2 = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)msg2);
+ check(mp2 != NULL);
+ check(CAMEL_IS_MULTIPART(mp2));
+ check(camel_multipart_get_number(mp2) == 3);
+
+ check(strcmp(camel_multipart_get_boundary(mp2), "_=,.XYZ_Kangaroo_Meat_is_!_ABADF00D") == 0);
+ check(mp2->preface == NULL || strlen(mp2->preface) == 0);
+
+ /* FIXME */
+ camel_test_nonfatal("postface may gain a single \\n?");
+ check_msg(mp2->postface == NULL || strlen(mp2->postface) == 0, "postface: '%s'", mp2->postface);
+ camel_test_fatal();
+
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 0))),
+ "content part 1", strlen("content part 1"));
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 1))),
+ "content part 3", strlen("content part 3"));
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 2))),
+ "content part 2", strlen("content part 2"));
+ pull();
+
+ push("writing again, & re-reading");
+ unlink("test3-2.msg");
+ test_message_write_file(msg2, "test3-2.msg");
+ msg3 = test_message_read_file("test3-2.msg");
+
+ push("comparing again");
+ mp2 = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)msg3);
+ check(mp2 != NULL);
+ check(CAMEL_IS_MULTIPART(mp2));
+ check(camel_multipart_get_number(mp2) == 3);
+
+ check(strcmp(camel_multipart_get_boundary(mp2), "_=,.XYZ_Kangaroo_Meat_is_!_ABADF00D") == 0);
+ check(mp2->preface == NULL || strlen(mp2->preface) == 0);
+
+ check_msg(mp2->postface == NULL || strlen(mp2->postface) == 0, "postface: '%s'", mp2->postface);
+
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 0))),
+ "content part 1", strlen("content part 1"));
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 1))),
+ "content part 3", strlen("content part 3"));
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 2))),
+ "content part 2", strlen("content part 2"));
+ pull();
+ pull();
+
+ check_unref(msg2, 1);
+ check_unref(msg3, 1);
+
+ push("testing pre/post text");
+ camel_multipart_set_preface(mp, "pre-text\nLines.");
+ camel_multipart_set_postface(mp, "post-text, no lines.\nOne line.\n");
+
+ check(strcmp(mp->preface, "pre-text\nLines.") == 0);
+ check(strcmp(mp->postface, "post-text, no lines.\nOne line.\n") == 0);
+
+ push("writing /re-reading");
+ unlink("test3-3.msg");
+ test_message_write_file(msg, "test3-3.msg");
+ msg2 = test_message_read_file("test3-3.msg");
+
+ mp2 = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)msg2);
+ check(mp2 != NULL);
+ check(CAMEL_IS_MULTIPART(mp2));
+ check(camel_multipart_get_number(mp2) == 3);
+
+ check(strcmp(camel_multipart_get_boundary(mp2), "_=,.XYZ_Kangaroo_Meat_is_!_ABADF00D") == 0);
+ check(mp2->preface && strcmp(mp2->preface, "pre-text\nLines.") == 0);
+ check(mp2->postface && strcmp(mp2->postface, "post-text, no lines.\nOne line.\n") == 0);
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 0))),
+ "content part 1", strlen("content part 1"));
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 1))),
+ "content part 3", strlen("content part 3"));
+ test_message_compare_content(camel_medium_get_content_object(CAMEL_MEDIUM(camel_multipart_get_part(mp2, 2))),
+ "content part 2", strlen("content part 2"));
+ pull();
+ check_unref(msg2, 1);
+ pull();
+
+ check_unref(msg, 1);
+ check_unref(mp, 1);
+ check_unref(part, 1);
+ check_unref(part2, 1);
+ check_unref(part3, 1);
+
+ camel_test_end();
+
+ return 0;
+}
diff --git a/camel/tests/mime-filter/Makefile.am b/camel/tests/mime-filter/Makefile.am
new file mode 100644
index 0000000000..91da15ca2b
--- /dev/null
+++ b/camel/tests/mime-filter/Makefile.am
@@ -0,0 +1,38 @@
+
+INCLUDES = \
+ -I$(includedir) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/intl \
+ -I$(top_srcdir)/e-util \
+ -I$(top_srcdir)/camel \
+ -I$(top_srcdir)/camel/tests/lib \
+ -DG_LOG_DOMAIN=\"evolution-tests\" \
+ -DSOURCEDIR=\"$(srcdir)\" \
+ $(CAMEL_CFLAGS)
+
+LDADD = \
+ $(top_builddir)/camel/libcamel.la \
+ $(top_builddir)/e-util/libeutil.la \
+ $(top_builddir)/camel/tests/lib/libcameltest.a \
+ $(INTLLIBS) \
+ $(EVOLUTION_MAIL_LIBS)
+
+EXTRA_DIST = \
+ crlf-1.in \
+ crlf-1.out \
+ charset-iso-2022-jp.0.in \
+ charset-iso-2022-jp.0.out \
+ charset-gb2312.0.in \
+ charset-gb2312.0.out
+
+check_PROGRAMS = \
+ test1 \
+ test-crlf \
+ test-charset \
+ test-tohtml
+
+TESTS = test1 \
+ test-crlf test-charset test-tohtml
+
+
+
diff --git a/camel/tests/mime-filter/test1.c b/camel/tests/mime-filter/test1.c
new file mode 100644
index 0000000000..a0a68db3c3
--- /dev/null
+++ b/camel/tests/mime-filter/test1.c
@@ -0,0 +1,108 @@
+/*
+ test-crlf.c
+
+ Test the CamelMimeFilterCanon class
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-test.h"
+
+#include <camel/camel-stream-fs.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-mime-filter-canon.h>
+
+#define d(x) x
+
+#define NUM_CASES 1
+#define CHUNK_SIZE 4096
+
+struct {
+ int flags;
+ char *in;
+ char *out;
+} tests[] = {
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF,
+ "From \nRussia - with love.\n\n",
+ "=46rom \r\nRussia - with love.\r\n\r\n" },
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF,
+ "From \r\nRussia - with love.\r\n\n",
+ "=46rom \r\nRussia - with love.\r\n\r\n" },
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF,
+ "Tasmiania with fur \nFrom",
+ "Tasmiania with fur \r\nFrom" },
+ { CAMEL_MIME_FILTER_CANON_FROM,
+ "Tasmiania with fur \nFrom",
+ "Tasmiania with fur \nFrom" },
+ { CAMEL_MIME_FILTER_CANON_CRLF,
+ "Tasmiania with fur \nFrom",
+ "Tasmiania with fur \r\nFrom" },
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF,
+ "Tasmiania with fur \nFrom here",
+ "Tasmiania with fur \r\n=46rom here" },
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_STRIP,
+ "Tasmiania with fur \nFrom here",
+ "Tasmiania with fur\r\n=46rom here" },
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_STRIP,
+ "Tasmiania with fur \nFrom here\n",
+ "Tasmiania with fur\r\n=46rom here\r\n" },
+ { CAMEL_MIME_FILTER_CANON_FROM|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_STRIP,
+ "Tasmiania with fur \nFrom here or there ? \n",
+ "Tasmiania with fur\r\n=46rom here or there ?\r\n" },
+};
+
+int
+main (int argc, char **argv)
+{
+ CamelStreamFilter *filter;
+ CamelMimeFilter *sh;
+ int i;
+
+ camel_test_init(argc, argv);
+
+ camel_test_start("canonicalisation filter tests");
+
+ for (i=0;i<sizeof(tests)/sizeof(tests[0]);i++) {
+ int step;
+
+ camel_test_push("Data test %d '%s'\n", i, tests[i].in);
+
+ /* try all write sizes */
+ for (step=1;step<20;step++) {
+ CamelStreamMem *out;
+ char *p;
+
+ camel_test_push("Chunk size %d\n", step);
+
+ out = (CamelStreamMem *)camel_stream_mem_new();
+ filter = camel_stream_filter_new_with_stream((CamelStream *)out);
+ sh = camel_mime_filter_canon_new(tests[i].flags);
+ check(camel_stream_filter_add(filter, sh) != -1);
+ check_unref(sh, 2);
+
+ p = tests[i].in;
+ while (*p) {
+ int w = MIN(strlen(p), step);
+
+ check(camel_stream_write((CamelStream *)filter, p, w) == w);
+ p += w;
+ }
+ camel_stream_flush((CamelStream *)filter);
+
+ check_msg(out->buffer->len == strlen(tests[i].out), "Buffer length mismatch: expected %d got %d\n or '%s' got '%.*s'", strlen(tests[i].out), out->buffer->len, tests[i].out, out->buffer->len, out->buffer->data);
+ check_msg(0 == memcmp(out->buffer->data, tests[i].out, out->buffer->len), "Buffer mismatch: expected '%s' got '%.*s'", tests[i].out, out->buffer->len, out->buffer->data);
+ check_unref(filter, 1);
+ check_unref(out, 1);
+
+ camel_test_pull();
+ }
+
+ camel_test_pull();
+ }
+
+ camel_test_end();
+
+ return 0;
+}