diff options
Diffstat (limited to 'camel')
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, " ", 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, ">", 4); + outptr += 4; + } else { + inptr--; + goto backup; + } + break; + case '&': + if ((outptr + 5) < outend) { + memcpy (outptr, "&", 5); + outptr += 5; + } else { + inptr--; + goto backup; + } + break; + case '<': + if (!(enriched->flags & IS_RICHTEXT)) { + /* text/enriched */ + if (*inptr == '<') { + if ((outptr + 4) < outend) { + memcpy (outptr, "<", 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, "<", 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; +} |